001/**
002 * Copyright 2005-2016 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.uif.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 * This object processes 'case constraints', which are constraints that are imposed only in specific cases, for
039 * example,
040 * when a value is
041 * equal to some constant, or greater than some limit.
042 *
043 * @author Kuali Rice Team (rice.collab@kuali.org)
044 */
045public class CaseConstraintProcessor extends MandatoryElementConstraintProcessor<CaseConstraint> {
046
047    private static final String CONSTRAINT_NAME = "case constraint";
048
049    /**
050     * @see org.kuali.rice.krad.datadictionary.validation.processor.ConstraintProcessor#process(DictionaryValidationResult,
051     *      Object, org.kuali.rice.krad.datadictionary.validation.capability.Constrainable,
052     *      org.kuali.rice.krad.datadictionary.validation.AttributeValueReader)
053     */
054    @Override
055    public ProcessorResult process(DictionaryValidationResult result, Object value, CaseConstraint caseConstraint,
056            AttributeValueReader attributeValueReader) throws AttributeValidationException {
057
058        // Don't process this constraint if it's null
059        if (null == caseConstraint) {
060            return new ProcessorResult(result.addNoConstraint(attributeValueReader, CONSTRAINT_NAME));
061        }
062        AttributeValueReader constraintAttributeReader = attributeValueReader.clone();
063
064        String operator = (ValidationUtils.hasText(caseConstraint.getOperator())) ? caseConstraint.getOperator() :
065                "EQUALS";
066        AttributeValueReader fieldPathReader = (ValidationUtils.hasText(caseConstraint.getPropertyName())) ?
067                getChildAttributeValueReader(caseConstraint.getPropertyName(), attributeValueReader) :
068                attributeValueReader;
069
070        Constrainable caseField = (null != fieldPathReader) ? fieldPathReader.getDefinition(
071                fieldPathReader.getAttributeName()) : null;
072        Object fieldValue = (null != fieldPathReader) ? fieldPathReader.getValue(fieldPathReader.getAttributeName()) :
073                value;
074        DataType fieldDataType = (null != caseField && caseField instanceof DataTypeConstraint) ?
075                ((DataTypeConstraint) caseField).getDataType() : null;
076
077        // Default to a string comparison
078        if (fieldDataType == null) {
079            fieldDataType = DataType.STRING;
080        }
081
082        // If fieldValue is null then skip Case check
083        if (null == fieldValue) {
084            // FIXME: not sure if the definition and attribute value reader should change under this case
085            return new ProcessorResult(result.addSkipped(attributeValueReader, CONSTRAINT_NAME), caseField,
086                    fieldPathReader);
087        }
088
089        List<Constraint> constraints = new ArrayList<Constraint>();
090        // Extract value for field Key
091        for (WhenConstraint wc : caseConstraint.getWhenConstraint()) {
092            evaluateWhenConstraint(fieldValue, fieldDataType, operator, caseConstraint, wc, attributeValueReader,
093                    constraints);
094        }
095        if (!constraints.isEmpty()) {
096            return new ProcessorResult(result.addSuccess(attributeValueReader, CONSTRAINT_NAME), null,
097                    constraintAttributeReader, constraints);
098        }
099
100        // Assuming that not finding any case constraints is equivalent to 'skipping' the constraint
101        return new ProcessorResult(result.addSkipped(attributeValueReader, CONSTRAINT_NAME));
102    }
103
104    private void evaluateWhenConstraint(Object fieldValue, DataType fieldDataType, String operator,
105            CaseConstraint caseConstraint, WhenConstraint wc, AttributeValueReader attributeValueReader,
106            List<Constraint> constraints) {
107        if (ValidationUtils.hasText(wc.getValuePath())) {
108            Object whenValue = null;
109
110            AttributeValueReader whenValueReader = getChildAttributeValueReader(wc.getValuePath(),
111                    attributeValueReader);
112            whenValue = whenValueReader.getValue(whenValueReader.getAttributeName());
113
114            if (ValidationUtils.compareValues(fieldValue, whenValue, fieldDataType, operator,
115                    caseConstraint.isCaseSensitive(), dateTimeService) && null != wc.getConstraint()) {
116                constraints.add(wc.getConstraint());
117            }
118        } else {
119            List<Object> whenValueList = wc.getValues();
120
121            for (Object whenValue : whenValueList) {
122                if (ValidationUtils.compareValues(fieldValue, whenValue, fieldDataType, operator,
123                        caseConstraint.isCaseSensitive(), dateTimeService) && null != wc.getConstraint()) {
124                    constraints.add(wc.getConstraint());
125                    break;
126                }
127            }
128        }
129    }
130
131    @Override
132    public String getName() {
133        return CONSTRAINT_NAME;
134    }
135
136    /**
137     * @see org.kuali.rice.krad.datadictionary.validation.processor.ConstraintProcessor#getConstraintType()
138     */
139    @Override
140    public Class<? extends Constraint> getConstraintType() {
141        return CaseConstraint.class;
142    }
143
144    private AttributeValueReader getChildAttributeValueReader(String key,
145            AttributeValueReader attributeValueReader) throws AttributeValidationException {
146        String[] lookupPathTokens = ValidationUtils.getPathTokens(key);
147
148        AttributeValueReader localAttributeValueReader = attributeValueReader;
149        for (int i = 0; i < lookupPathTokens.length; i++) {
150            for (Constrainable definition : localAttributeValueReader.getDefinitions()) {
151                String attributeName = definition.getName();
152                if (attributeName.equals(lookupPathTokens[i])) {
153                    if (i == lookupPathTokens.length - 1) {
154                        localAttributeValueReader.setAttributeName(attributeName);
155                        return localAttributeValueReader;
156                    }
157                    if (definition instanceof HierarchicallyConstrainable) {
158                        String childEntryName = ((HierarchicallyConstrainable) definition).getChildEntryName();
159                        DataDictionaryEntry entry = KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary()
160                                .getDictionaryObjectEntry(childEntryName);
161                        Object value = attributeValueReader.getValue(attributeName);
162                        attributeValueReader.setAttributeName(attributeName);
163                        String attributePath = attributeValueReader.getPath();
164                        localAttributeValueReader = new DictionaryObjectAttributeValueReader(value, childEntryName,
165                                entry, attributePath);
166                    }
167                    break;
168                }
169            }
170        }
171        return null;
172    }
173
174}