Coverage Report - org.kuali.rice.krad.datadictionary.validation.processor.RangeConstraintProcessor
 
Classes in this File Line Coverage Branch Coverage Complexity
RangeConstraintProcessor
0%
0/38
0%
0/34
4.429
 
 1  
 /*
 2  
  * Copyright 2011 The Kuali Foundation
 3  
  *
 4  
  * Licensed under the Educational Community License, Version 1.0 (the "License");
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  *
 8  
  * http://www.opensource.org/licenses/ecl1.php
 9  
  *
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  */
 16  
 package org.kuali.rice.krad.datadictionary.validation.processor;
 17  
 
 18  
 import java.math.BigDecimal;
 19  
 import java.util.Date;
 20  
 
 21  
 import org.kuali.rice.core.util.RiceKeyConstants;
 22  
 import org.kuali.rice.krad.datadictionary.exception.AttributeValidationException;
 23  
 import org.kuali.rice.krad.datadictionary.validation.AttributeValueReader;
 24  
 import org.kuali.rice.krad.datadictionary.validation.DataType;
 25  
 import org.kuali.rice.krad.datadictionary.validation.ValidationUtils;
 26  
 import org.kuali.rice.krad.datadictionary.validation.ValidationUtils.Result;
 27  
 import org.kuali.rice.krad.datadictionary.validation.capability.RangeConstrainable;
 28  
 import org.kuali.rice.krad.datadictionary.validation.constraint.Constraint;
 29  
 import org.kuali.rice.krad.datadictionary.validation.constraint.RangeConstraint;
 30  
 import org.kuali.rice.krad.datadictionary.validation.result.ConstraintValidationResult;
 31  
 import org.kuali.rice.krad.datadictionary.validation.result.DictionaryValidationResult;
 32  
 import org.kuali.rice.krad.datadictionary.validation.result.ProcessorResult;
 33  
 
 34  
 /**
 35  
  * This class enforces range constraints - that is, constraints that keep a number or a date within a specific range. An attribute 
 36  
  * that is {@link RangeConstrainable} will expose a minimum and maximum value, and these will be validated against the passed
 37  
  * value in the code below. 
 38  
  * 
 39  
  * @author Kuali Rice Team (rice.collab@kuali.org) 
 40  
  */
 41  0
 public class RangeConstraintProcessor extends MandatoryElementConstraintProcessor<RangeConstraint> {
 42  
 
 43  
         private static final String CONSTRAINT_NAME = "range constraint";
 44  
 
 45  
         /**
 46  
          * @see org.kuali.rice.krad.datadictionary.validation.processor.ConstraintProcessor#process(DictionaryValidationResult, Object, org.kuali.rice.krad.datadictionary.validation.capability.Validatable, org.kuali.rice.krad.datadictionary.validation.AttributeValueReader)
 47  
          */
 48  
         @Override
 49  
         public ProcessorResult process(DictionaryValidationResult result, Object value, RangeConstraint constraint, AttributeValueReader attributeValueReader) throws AttributeValidationException {
 50  
                 
 51  
                 // Since any given definition that is range constrained only expressed a single min and max, it means that there is only a single constraint to impose
 52  0
                 return new ProcessorResult(processSingleRangeConstraint(result, value, constraint, attributeValueReader));
 53  
         }
 54  
         
 55  
         @Override 
 56  
         public String getName() {
 57  0
                 return CONSTRAINT_NAME;
 58  
         }
 59  
 
 60  
         /**
 61  
          * @see org.kuali.rice.krad.datadictionary.validation.processor.ConstraintProcessor#getConstraintType()
 62  
          */
 63  
         @Override
 64  
         public Class<? extends Constraint> getConstraintType() {
 65  0
                 return RangeConstraint.class;
 66  
         }
 67  
         
 68  
         protected ConstraintValidationResult processSingleRangeConstraint(DictionaryValidationResult result, Object value, RangeConstraint constraint, AttributeValueReader attributeValueReader) throws AttributeValidationException {
 69  
                 // Can't process any range constraints on null values
 70  0
                 if (ValidationUtils.isNullOrEmpty(value))
 71  0
                         return result.addSkipped(attributeValueReader, CONSTRAINT_NAME);
 72  
                 
 73  
         
 74  
                 // This is necessary because sometimes we'll be getting a string, for example, that represents a date. 
 75  0
                 DataType dataType = constraint.getDataType();
 76  0
                 Object typedValue = value;
 77  
 
 78  0
                 if (dataType != null) {
 79  0
                         typedValue = ValidationUtils.convertToDataType(value, dataType, dateTimeService);
 80  
                 }        
 81  
 
 82  
                 // TODO: decide if there is any reason why the following would be insufficient - i.e. if something numeric could still be cast to String at this point
 83  0
                 if (typedValue instanceof Date)
 84  0
                         return validateRange(result, (Date)typedValue, constraint, attributeValueReader);
 85  0
                 else if (typedValue instanceof Number)
 86  0
                         return validateRange(result, (Number)typedValue, constraint, attributeValueReader);
 87  
                 
 88  0
                 return result.addSkipped(attributeValueReader, CONSTRAINT_NAME);
 89  
         }
 90  
         
 91  
         protected ConstraintValidationResult validateRange(DictionaryValidationResult result, Date value, RangeConstraint constraint, AttributeValueReader attributeValueReader) throws IllegalArgumentException {        
 92  
 
 93  0
                 Date date = value != null ? ValidationUtils.getDate(value, dateTimeService) : null;
 94  
 
 95  0
         String inclusiveMaxText = constraint.getInclusiveMax();
 96  0
         String exclusiveMinText = constraint.getExclusiveMin();
 97  
 
 98  0
         Date inclusiveMax = inclusiveMaxText != null ? ValidationUtils.getDate(inclusiveMaxText, dateTimeService) : null;
 99  0
         Date exclusiveMin = exclusiveMinText != null ? ValidationUtils.getDate(exclusiveMinText, dateTimeService) : null;
 100  
         
 101  0
                 return isInRange(result, date, inclusiveMax, inclusiveMaxText, exclusiveMin, exclusiveMinText, attributeValueReader);
 102  
         }
 103  
         
 104  
         protected ConstraintValidationResult validateRange(DictionaryValidationResult result, Number value, RangeConstraint constraint, AttributeValueReader attributeValueReader) throws IllegalArgumentException {
 105  
 
 106  
                 // TODO: JLR - need a code review of the conversions below to make sure this is the best way to ensure accuracy across all numerics
 107  
         // This will throw NumberFormatException if the value is 'NaN' or infinity... probably shouldn't be a NFE but something more intelligible at a higher level
 108  0
         BigDecimal number = value != null ? new BigDecimal(value.toString()) : null;
 109  
 
 110  0
         String inclusiveMaxText = constraint.getInclusiveMax();
 111  0
         String exclusiveMinText = constraint.getExclusiveMin();
 112  
         
 113  0
         BigDecimal inclusiveMax = inclusiveMaxText != null ? new BigDecimal(inclusiveMaxText) : null;
 114  0
         BigDecimal exclusiveMin = exclusiveMinText != null ? new BigDecimal(exclusiveMinText) : null;
 115  
         
 116  0
                 return isInRange(result, number, inclusiveMax, inclusiveMaxText, exclusiveMin, exclusiveMinText, attributeValueReader);
 117  
         }
 118  
 
 119  
         private <T> ConstraintValidationResult isInRange(DictionaryValidationResult result, T value, Comparable<T> inclusiveMax, String inclusiveMaxText, Comparable<T> exclusiveMin, String exclusiveMinText, AttributeValueReader attributeValueReader) {
 120  
         // What we want to know is that the maximum value is greater than or equal to the number passed (the number can be equal to the max, i.e. it's 'inclusive')
 121  0
         Result lessThanMax = ValidationUtils.isLessThanOrEqual(value, inclusiveMax); 
 122  
         // On the other hand, since the minimum is exclusive, we just want to make sure it's less than the number (the number can't be equal to the min, i.e. it's 'exclusive')
 123  0
         Result greaterThanMin = ValidationUtils.isGreaterThan(value, exclusiveMin); 
 124  
           
 125  
         // It's okay for one end of the range to be undefined - that's not an error. It's only an error if one of them is actually invalid. 
 126  0
         if (lessThanMax != Result.INVALID && greaterThanMin != Result.INVALID) { 
 127  
                 // Of course, if they're both undefined then we didn't actually have a real constraint
 128  0
                 if (lessThanMax == Result.UNDEFINED && greaterThanMin == Result.UNDEFINED)
 129  0
                         return result.addNoConstraint(attributeValueReader, CONSTRAINT_NAME);
 130  
                 
 131  
                 // In this case, we've succeeded
 132  0
                 return result.addSuccess(attributeValueReader, CONSTRAINT_NAME);
 133  
         }
 134  
         
 135  
         // If both comparisons happened then if either comparison failed we can show the end user the expected range on both sides.
 136  0
         if (lessThanMax != Result.UNDEFINED && greaterThanMin != Result.UNDEFINED) 
 137  0
                 return result.addError(attributeValueReader, CONSTRAINT_NAME, RiceKeyConstants.ERROR_OUT_OF_RANGE, exclusiveMinText, inclusiveMaxText);
 138  
         // If it's the max comparison that fails, then just tell the end user what the max can be
 139  0
         else if (lessThanMax == Result.INVALID)
 140  0
                 return result.addError(attributeValueReader, CONSTRAINT_NAME, RiceKeyConstants.ERROR_INCLUSIVE_MAX, inclusiveMaxText);
 141  
         // Otherwise, just tell them what the min can be
 142  
         else 
 143  0
                 return result.addError(attributeValueReader, CONSTRAINT_NAME, RiceKeyConstants.ERROR_EXCLUSIVE_MIN, exclusiveMinText);
 144  
         }
 145  
         
 146  
 }