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 java.util.ArrayList;
019import java.util.List;
020
021import org.kuali.rice.core.api.data.DataType;
022import org.kuali.rice.krad.datadictionary.DataDictionaryEntry;
023import org.kuali.rice.krad.datadictionary.exception.AttributeValidationException;
024import org.kuali.rice.krad.datadictionary.validation.AttributeValueReader;
025import org.kuali.rice.krad.datadictionary.validation.DictionaryObjectAttributeValueReader;
026import org.kuali.rice.krad.datadictionary.validation.ValidationUtils;
027import org.kuali.rice.krad.datadictionary.validation.capability.Constrainable;
028import org.kuali.rice.krad.datadictionary.validation.capability.HierarchicallyConstrainable;
029import org.kuali.rice.krad.datadictionary.validation.constraint.CaseConstraint;
030import org.kuali.rice.krad.datadictionary.validation.constraint.Constraint;
031import org.kuali.rice.krad.datadictionary.validation.constraint.DataTypeConstraint;
032import org.kuali.rice.krad.datadictionary.validation.constraint.WhenConstraint;
033import org.kuali.rice.krad.datadictionary.validation.result.DictionaryValidationResult;
034import org.kuali.rice.krad.datadictionary.validation.result.ProcessorResult;
035import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
036
037/**
038 * CaseConstraintProcessor processes 'case constraints', which are constraints that are imposed only in specific cases
039 *
040 * <p>For example, when a value is equal to some constant, or greater than some limit.</p>
041 *
042 * @author Kuali Rice Team (rice.collab@kuali.org)
043 */
044public class CaseConstraintProcessor extends MandatoryElementConstraintProcessor<CaseConstraint> {
045
046    private static final String CONSTRAINT_NAME = "case constraint";
047
048    /**
049     * @see org.kuali.rice.krad.datadictionary.validation.processor.ConstraintProcessor#process(org.kuali.rice.krad.datadictionary.validation.result.DictionaryValidationResult,
050     *      Object, org.kuali.rice.krad.datadictionary.validation.constraint.Constraint,
051     *      org.kuali.rice.krad.datadictionary.validation.AttributeValueReader)
052     */
053    @Override
054    public ProcessorResult process(DictionaryValidationResult result, Object value, CaseConstraint caseConstraint,
055            AttributeValueReader attributeValueReader) throws AttributeValidationException {
056
057        // Don't process this constraint if it's null
058        if (null == caseConstraint) {
059            return new ProcessorResult(result.addNoConstraint(attributeValueReader, CONSTRAINT_NAME));
060        }
061        AttributeValueReader constraintAttributeReader = attributeValueReader.clone();
062
063        String operator = (ValidationUtils.hasText(caseConstraint.getOperator())) ? caseConstraint.getOperator() :
064                "EQUALS";
065        AttributeValueReader fieldPathReader = (ValidationUtils.hasText(caseConstraint.getPropertyName())) ?
066                getChildAttributeValueReader(caseConstraint.getPropertyName(), attributeValueReader) :
067                attributeValueReader;
068
069        Constrainable caseField = (null != fieldPathReader) ? fieldPathReader.getDefinition(
070                fieldPathReader.getAttributeName()) : null;
071        Object fieldValue = (null != fieldPathReader) ? fieldPathReader.getValue(fieldPathReader.getAttributeName()) :
072                value;
073        DataType fieldDataType = (null != caseField && caseField instanceof DataTypeConstraint) ?
074                ((DataTypeConstraint) caseField).getDataType() : null;
075
076        // Default to a string comparison
077        if (fieldDataType == null) {
078            fieldDataType = DataType.STRING;
079        }
080
081        // If fieldValue is null then skip Case check
082        if (null == fieldValue) {
083            // FIXME: not sure if the definition and attribute value reader should change under this case
084            return new ProcessorResult(result.addSkipped(attributeValueReader, CONSTRAINT_NAME), caseField,
085                    fieldPathReader);
086        }
087
088        List<Constraint> constraints = new ArrayList<Constraint>();
089        // Extract value for field Key
090        for (WhenConstraint wc : caseConstraint.getWhenConstraint()) {
091            evaluateWhenConstraint(fieldValue, fieldDataType, operator, caseConstraint, wc, attributeValueReader,
092                    constraints);
093        }
094        if (!constraints.isEmpty()) {
095            return new ProcessorResult(result.addSuccess(attributeValueReader, CONSTRAINT_NAME), null,
096                    constraintAttributeReader, constraints);
097        }
098
099        // Assuming that not finding any case constraints is equivalent to 'skipping' the constraint
100        return new ProcessorResult(result.addSkipped(attributeValueReader, CONSTRAINT_NAME));
101    }
102
103    /**
104     * evaluates the provided {@link WhenConstraint}
105     *
106     * @param fieldValue - the value of the field
107     * @param fieldDataType - the data type of the field which caseConstraint's propertyName refers to
108     * @param operator - the relationship to check between the fieldValue and the value provided in the whenConstraint
109     * @param caseConstraint - the case constraint containing the provided whenConstraint
110     * @param wc - the whenConstraint to evaluate
111     * @param attributeValueReader - provides access to the attribute being validated
112     * @param constraints - the constraints to populate as discovered in the provided whenConstraint
113     */
114    private void evaluateWhenConstraint(Object fieldValue, DataType fieldDataType, String operator,
115            CaseConstraint caseConstraint, WhenConstraint wc, AttributeValueReader attributeValueReader,
116            List<Constraint> constraints) {
117        if (ValidationUtils.hasText(wc.getValuePath())) {
118            Object whenValue = null;
119
120            //String originalName = attributeValueReader.getAttributeName();
121            AttributeValueReader whenValueReader = getChildAttributeValueReader(wc.getValuePath(),
122                    attributeValueReader);
123            whenValue = whenValueReader.getValue(whenValueReader.getAttributeName());
124
125            if (ValidationUtils.compareValues(fieldValue, whenValue, fieldDataType, operator,
126                    caseConstraint.isCaseSensitive(), dateTimeService) && null != wc.getConstraint()) {
127                constraints.add(wc.getConstraint());
128            }
129            //whenValueReader.setAttributeName(originalName);
130        } else {
131            List<Object> whenValueList = wc.getValues();
132
133            for (Object whenValue : whenValueList) {
134                if (ValidationUtils.compareValues(fieldValue, whenValue, fieldDataType, operator,
135                        caseConstraint.isCaseSensitive(), dateTimeService) && null != wc.getConstraint()) {
136                    constraints.add(wc.getConstraint());
137                    break;
138                }
139            }
140        }
141    }
142
143    @Override
144    public String getName() {
145        return CONSTRAINT_NAME;
146    }
147
148    /**
149     * @see org.kuali.rice.krad.datadictionary.validation.processor.ConstraintProcessor#getConstraintType()
150     */
151    @Override
152    public Class<? extends Constraint> getConstraintType() {
153        return CaseConstraint.class;
154    }
155
156    /**
157     * provides access to the attribute specified in a whenConstraint
158     *
159     * @param key - a string representation of specifically which attribute (at some depth) is being accessed
160     * @param attributeValueReader - provides access to the attribute being validated
161     * @return an attribute value reader for the path represented by the key param
162     * @throws AttributeValidationException
163     */
164    private AttributeValueReader getChildAttributeValueReader(String key,
165            AttributeValueReader attributeValueReader) throws AttributeValidationException {
166        String[] lookupPathTokens = ValidationUtils.getPathTokens(key);
167
168        AttributeValueReader localAttributeValueReader = attributeValueReader.clone();
169        for (int i = 0; i < lookupPathTokens.length; i++) {
170            for (Constrainable definition : localAttributeValueReader.getDefinitions()) {
171                String attributeName = definition.getName();
172                if (attributeName.equals(lookupPathTokens[i])) {
173                    if (i == lookupPathTokens.length - 1) {
174                        localAttributeValueReader.setAttributeName(attributeName);
175                        return localAttributeValueReader;
176                    }
177                    if (definition instanceof HierarchicallyConstrainable) {
178                        String childEntryName = ((HierarchicallyConstrainable) definition).getChildEntryName();
179                        DataDictionaryEntry entry = KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary()
180                                .getDictionaryObjectEntry(childEntryName);
181                        Object value = attributeValueReader.getValue(attributeName);
182                        attributeValueReader.setAttributeName(attributeName);
183                        String attributePath = attributeValueReader.getPath();
184                        localAttributeValueReader = new DictionaryObjectAttributeValueReader(value, childEntryName,
185                                entry, attributePath);
186                    }
187                    break;
188                }
189            }
190        }
191        return null;
192    }
193
194}