001/**
002 * Copyright 2005-2018 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krad.datadictionary.validation.processor;
017
018import org.kuali.rice.core.api.util.RiceKeyConstants;
019import org.kuali.rice.krad.datadictionary.exception.AttributeValidationException;
020import org.kuali.rice.krad.datadictionary.validation.AttributeValueReader;
021import org.kuali.rice.krad.datadictionary.validation.ValidationUtils;
022import org.kuali.rice.krad.datadictionary.validation.ValidationUtils.Result;
023import org.kuali.rice.krad.datadictionary.validation.constraint.CollectionSizeConstraint;
024import org.kuali.rice.krad.datadictionary.validation.constraint.Constraint;
025import org.kuali.rice.krad.datadictionary.validation.result.ConstraintValidationResult;
026import org.kuali.rice.krad.datadictionary.validation.result.DictionaryValidationResult;
027import org.kuali.rice.krad.datadictionary.validation.result.ProcessorResult;
028
029import java.util.Collection;
030
031/**
032 * This class validates attributes that are collection size constrained - ones that can only have between x and y
033 * number
034 *
035 * @author Kuali Rice Team (rice.collab@kuali.org)
036 */
037public class CollectionSizeConstraintProcessor implements CollectionConstraintProcessor<Collection<?>, CollectionSizeConstraint> {
038
039    private static final String CONSTRAINT_NAME = "collection size constraint";
040
041    /**
042     * @see org.kuali.rice.krad.datadictionary.validation.processor.ConstraintProcessor#process(org.kuali.rice.krad.datadictionary.validation.result.DictionaryValidationResult,
043     *      Object, org.kuali.rice.krad.datadictionary.validation.constraint.Constraint,
044     *      org.kuali.rice.krad.datadictionary.validation.AttributeValueReader)
045     */
046    @Override
047    public ProcessorResult process(DictionaryValidationResult result, Collection<?> collection,
048            CollectionSizeConstraint constraint,
049            AttributeValueReader attributeValueReader) throws AttributeValidationException {
050
051        // To accommodate the needs of other processors, the ConstraintProcessor.process() method returns a list of ConstraintValidationResult objects
052        // but since a definition that is collection size constrained only provides a single max and minimum, there is effectively a single constraint
053        // being imposed.
054        return new ProcessorResult(processSingleCollectionSizeConstraint(result, collection, constraint,
055                attributeValueReader));
056    }
057
058    @Override
059    public String getName() {
060        return CONSTRAINT_NAME;
061    }
062
063    /**
064     * @see org.kuali.rice.krad.datadictionary.validation.processor.ConstraintProcessor#getConstraintType()
065     */
066    @Override
067    public Class<? extends Constraint> getConstraintType() {
068        return CollectionSizeConstraint.class;
069    }
070
071    /**
072     * @see org.kuali.rice.krad.datadictionary.validation.processor.ConstraintProcessor#isOptional()
073     */
074    @Override
075    public boolean isOptional() {
076        return false;
077    }
078
079    protected ConstraintValidationResult processSingleCollectionSizeConstraint(DictionaryValidationResult result,
080            Collection<?> collection, CollectionSizeConstraint constraint,
081            AttributeValueReader attributeValueReader) throws AttributeValidationException {
082        Integer sizeOfCollection = new Integer(0);
083        if (collection != null) {
084            sizeOfCollection = Integer.valueOf(collection.size());
085        }
086
087        Integer maxOccurances = constraint.getMaximumNumberOfElements();
088        Integer minOccurances = constraint.getMinimumNumberOfElements();
089
090        Result lessThanMax = ValidationUtils.isLessThanOrEqual(sizeOfCollection, maxOccurances);
091        Result greaterThanMin = ValidationUtils.isGreaterThanOrEqual(sizeOfCollection, minOccurances);
092
093        // 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 invalid
094        if (lessThanMax != Result.INVALID && greaterThanMin != Result.INVALID) {
095            // Of course, if they're both undefined then we didn't actually have a real constraint
096            if (lessThanMax == Result.UNDEFINED && greaterThanMin == Result.UNDEFINED) {
097                return result.addNoConstraint(attributeValueReader, CONSTRAINT_NAME);
098            }
099
100            // In this case, we've succeeded
101            return result.addSuccess(attributeValueReader, CONSTRAINT_NAME);
102        }
103
104        String maxErrorParameter = maxOccurances != null ? maxOccurances.toString() : null;
105        String minErrorParameter = minOccurances != null ? minOccurances.toString() : null;
106
107        // If both comparisons happened then if either comparison failed we can show the end user the expected range on both sides.
108        if (lessThanMax != Result.UNDEFINED && greaterThanMin != Result.UNDEFINED) {
109            return result.addError(attributeValueReader, CONSTRAINT_NAME, RiceKeyConstants.ERROR_QUANTITY_RANGE,
110                    minErrorParameter, maxErrorParameter);
111        }
112        // If it's the max comparison that fails, then just tell the end user what the max can be
113        else if (lessThanMax == Result.INVALID) {
114            return result.addError(attributeValueReader, CONSTRAINT_NAME, RiceKeyConstants.ERROR_MAX_OCCURS,
115                    maxErrorParameter);
116        }
117        // Otherwise, just tell them what the min can be
118        else {
119            return result.addError(attributeValueReader, CONSTRAINT_NAME, RiceKeyConstants.ERROR_MIN_OCCURS,
120                    minErrorParameter);
121        }
122
123        // Obviously the last else above is unnecessary, since anything after it is dead code, but keeping it seems clearer than dropping it
124    }
125
126}