View Javadoc

1   /**
2    * Copyright 2005-2011 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.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/ecl2.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 org.apache.commons.lang.StringUtils;
19  import org.apache.log4j.Logger;
20  import org.kuali.rice.core.api.CoreApiServiceLocator;
21  import org.kuali.rice.core.api.search.SearchOperator;
22  import org.kuali.rice.core.api.util.ClassLoaderUtils;
23  import org.kuali.rice.core.api.util.RiceKeyConstants;
24  import org.kuali.rice.core.web.format.DateFormatter;
25  import org.kuali.rice.krad.datadictionary.exception.AttributeValidationException;
26  import org.kuali.rice.krad.datadictionary.validation.AttributeValueReader;
27  import org.kuali.rice.krad.datadictionary.validation.ValidationUtils;
28  import org.kuali.rice.krad.datadictionary.validation.capability.Constrainable;
29  import org.kuali.rice.krad.datadictionary.validation.capability.Formatable;
30  import org.kuali.rice.krad.datadictionary.validation.constraint.Constraint;
31  import org.kuali.rice.krad.datadictionary.validation.constraint.ValidCharactersConstraint;
32  import org.kuali.rice.krad.datadictionary.validation.result.ConstraintValidationResult;
33  import org.kuali.rice.krad.datadictionary.validation.result.DictionaryValidationResult;
34  import org.kuali.rice.krad.datadictionary.validation.result.ProcessorResult;
35  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
36  import org.kuali.rice.krad.util.KRADConstants;
37  
38  import java.lang.reflect.Method;
39  import java.util.List;
40  
41  /**
42   * This class defines a constraint processor to ensure that attribute values are constrained to valid characters, as defined by some regular expression. Of the 
43   * constraint processors written for this version, this one is potentially the most difficult to understand because it holds on to a lot of legacy processing.
44   * 
45   * @author Kuali Rice Team (rice.collab@kuali.org) 
46   */
47  public class ValidCharactersConstraintProcessor extends MandatoryElementConstraintProcessor<ValidCharactersConstraint> {
48  
49  	public static final String VALIDATE_METHOD = "validate";
50  	
51  	private static final Logger LOG = Logger.getLogger(ValidCharactersConstraintProcessor.class);
52  	private static final String[] DATE_RANGE_ERROR_PREFIXES = { KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX, KRADConstants.LOOKUP_RANGE_UPPER_BOUND_PROPERTY_PREFIX };
53      
54  	private static final String CONSTRAINT_NAME = "valid characters constraint";
55  	
56  	/**
57  	 * @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)
58  	 */
59  	@Override
60  	public ProcessorResult process(DictionaryValidationResult result, Object value, ValidCharactersConstraint constraint, AttributeValueReader attributeValueReader)	throws AttributeValidationException {
61  		
62      	return new ProcessorResult(processSingleValidCharacterConstraint(result, value, constraint, attributeValueReader));
63  	}
64  
65  	@Override 
66  	public String getName() {
67  		return CONSTRAINT_NAME;
68  	}
69  	
70  	/**
71  	 * @see org.kuali.rice.krad.datadictionary.validation.processor.ConstraintProcessor#getConstraintType()
72  	 */
73  	@Override
74  	public Class<? extends Constraint> getConstraintType() {
75  		return ValidCharactersConstraint.class;
76  	}
77  	
78  	
79  	protected ConstraintValidationResult processSingleValidCharacterConstraint(DictionaryValidationResult result, Object value, ValidCharactersConstraint constraint, AttributeValueReader attributeValueReader) throws AttributeValidationException {
80  		
81  		if (constraint == null) 
82  			return result.addNoConstraint(attributeValueReader, CONSTRAINT_NAME);
83  		
84  		if (ValidationUtils.isNullOrEmpty(value))
85  			return result.addSkipped(attributeValueReader, CONSTRAINT_NAME);
86  		
87  		// This mix-in interface is here to allow some definitions to avoid the extra processing that goes on in KNS
88  		// to decipher and validate things like date range strings -- something that looks like "02/02/2002..03/03/2003"
89  		Constrainable definition = attributeValueReader.getDefinition(attributeValueReader.getAttributeName());
90      	if (definition instanceof Formatable) {
91      		return doProcessFormattableValidCharConstraint(result, constraint, (Formatable)definition, value, attributeValueReader);
92      	} 
93      	
94      	ConstraintValidationResult constraintValidationResult = doProcessValidCharConstraint(constraint, value);
95      	if (constraintValidationResult == null)
96      		return result.addSuccess(attributeValueReader, CONSTRAINT_NAME);
97      	
98      	result.addConstraintValidationResult(attributeValueReader, constraintValidationResult);
99      	return constraintValidationResult;
100 	}
101 	
102     protected ConstraintValidationResult doProcessFormattableValidCharConstraint(DictionaryValidationResult result, ValidCharactersConstraint validCharsConstraint, Formatable definition, Object value, AttributeValueReader attributeValueReader) throws AttributeValidationException {
103     	String entryName = attributeValueReader.getEntryName();
104     	String attributeName = attributeValueReader.getAttributeName();
105     	
106     	// This is a strange KNS thing for validating searchable fields -- they sometimes come in a date range format, for example 2/12/2010..2/14/2010, and need to be split up
107 		List<String> parsedAttributeValues = attributeValueReader.getCleanSearchableValues(attributeName);
108 		
109 		if (parsedAttributeValues != null) {
110 			
111 			Class<?> formatterClass = null;
112 			Boolean doValidateDateRangeOrder = null;
113 			
114 			// It can't be a date range if it's more than two fields, for example "a .. b | c" is not a date range -- this saves us a tiny bit of processing later
115 			if (parsedAttributeValues.size() != 2)
116 				doValidateDateRangeOrder = Boolean.FALSE;
117 			
118 			// Use integer to iterate since we need to track which field we're looking at
119 			for (int i=0;i<parsedAttributeValues.size();i++) {
120 				String parsedAttributeValue = parsedAttributeValues.get(i);
121 				
122 				ConstraintValidationResult constraintValidationResult = doProcessValidCharConstraint(validCharsConstraint, parsedAttributeValue);
123 		
124 				// If this is an error then some non-null validation result will be returned
125 				if (constraintValidationResult != null) {
126 					// Another strange KNS thing -- if the validation fails (not sure why only in that case) then some further error checking is done using the formatter, if one exists
127 					if (formatterClass == null) {
128     					String formatterClassName = definition.getFormatterClass();
129     					if (formatterClassName != null)
130     						formatterClass = ClassLoaderUtils.getClass(formatterClassName);
131 					}
132 					
133 					if (formatterClass != null) {
134 						// Use the Boolean value being null to ensure we only do this once
135 						if (doValidateDateRangeOrder == null) {
136 							// We only want to validate a date range if we're dealing with something that has a date formatter on it and that looks like an actual range (is made up of 2 values with a between operator between them)
137     						doValidateDateRangeOrder = Boolean.valueOf(DateFormatter.class.isAssignableFrom(formatterClass) && StringUtils.contains(ValidationUtils.getString(value), SearchOperator
138                                     .BETWEEN.toString()));
139 						}
140 						
141 						constraintValidationResult = processFormatterValidation(result, formatterClass, entryName, attributeName, parsedAttributeValue, DATE_RANGE_ERROR_PREFIXES[i]);
142 						
143 						if (constraintValidationResult != null) {
144 							result.addConstraintValidationResult(attributeValueReader, constraintValidationResult);
145 							return constraintValidationResult;
146 						}
147 					} else {
148 						// Otherwise, just report the validation result (apparently the formatter can't provide any fall-through validation because it doesn't exist)
149 						result.addConstraintValidationResult(attributeValueReader, constraintValidationResult);
150 						return constraintValidationResult;
151 					}
152 				}
153 			}
154 			
155 	    	if (doValidateDateRangeOrder != null && doValidateDateRangeOrder.booleanValue()) {
156 	    		ConstraintValidationResult dateOrderValidationResult = validateDateOrder(parsedAttributeValues.get(0), parsedAttributeValues.get(1), entryName, attributeName);
157 	    		
158 	    		if (dateOrderValidationResult != null) {
159 	    			result.addConstraintValidationResult(attributeValueReader, dateOrderValidationResult);
160 					return dateOrderValidationResult;
161 	    		}
162 	    	}
163 	    	
164 	    	return result.addSuccess(attributeValueReader, CONSTRAINT_NAME);
165 		}
166 		return result.addSkipped(attributeValueReader, CONSTRAINT_NAME);
167     }
168 	
169     protected ConstraintValidationResult doProcessValidCharConstraint(ValidCharactersConstraint validCharsConstraint, Object value) {
170 
171         StringBuilder fieldValue = new StringBuilder();
172         String validChars = validCharsConstraint.getValue();
173 
174         fieldValue.append(ValidationUtils.getString(value));
175 
176 //        int typIdx = validChars.indexOf(":");
177 //        String processorType = "regex";
178 //        if (-1 == typIdx) {
179 //            validChars = "[" + validChars + "]*";
180 //        } else {
181 //            processorType = validChars.substring(0, typIdx);
182 //            validChars = validChars.substring(typIdx + 1);
183 //        }
184 
185 //        if ("regex".equalsIgnoreCase(processorType) && !validChars.equals(".*")) {
186             if (!fieldValue.toString().matches(validChars)) {
187             	ConstraintValidationResult constraintValidationResult = new ConstraintValidationResult(CONSTRAINT_NAME);
188             	if (validCharsConstraint.getLabelKey() != null) {
189             		// FIXME: This shouldn't surface label key itself to the user - it should look up the label key, but this needs to be implemented in Rice
190             		constraintValidationResult.setError(RiceKeyConstants.ERROR_CUSTOM, validCharsConstraint.getLabelKey());
191             		return constraintValidationResult;
192             	} 
193             	
194             	constraintValidationResult.setError(RiceKeyConstants.ERROR_INVALID_FORMAT, fieldValue.toString());
195             	return constraintValidationResult;
196             }
197 //        }
198         
199         return null;
200     }
201 
202     protected ConstraintValidationResult processFormatterValidation(DictionaryValidationResult result, Class<?> formatterClass, String entryName, String attributeName, String parsedAttributeValue, String errorKeyPrefix) {
203     	
204     	boolean isError = false;
205     	
206     	try {
207     		Method validatorMethod = formatterClass.getDeclaredMethod(VALIDATE_METHOD, new Class<?>[] {String.class});
208     		Object o = validatorMethod.invoke(formatterClass.newInstance(), parsedAttributeValue);
209     		if (o instanceof Boolean) {
210     			isError = !((Boolean)o).booleanValue();
211     		}
212     	} catch (Exception e) {
213     		if ( LOG.isDebugEnabled() ) 
214     			LOG.debug(e.getMessage(), e);
215 
216     		isError = true;
217     	}
218 
219     	if (isError) {
220     		String errorMessageKey = getDataDictionaryService().getAttributeValidatingErrorMessageKey(entryName, attributeName);
221     		String[] errorMessageParameters = getDataDictionaryService().getAttributeValidatingErrorMessageParameters(entryName, attributeName);
222     		
223     		ConstraintValidationResult constraintValidationResult = new ConstraintValidationResult(CONSTRAINT_NAME);
224 			constraintValidationResult.setEntryName(entryName);
225 			constraintValidationResult.setAttributeName(errorKeyPrefix + attributeName);
226 			constraintValidationResult.setError(errorMessageKey, errorMessageParameters);
227     		
228 			return constraintValidationResult;
229     	}
230 			
231 		return null;
232     }
233     
234 	protected ConstraintValidationResult validateDateOrder(String firstDateTime, String secondDateTime, String entryName, String attributeName) {
235 		// this means that we only have 2 values and it's a date range.
236 		java.sql.Timestamp lVal = null;
237 		java.sql.Timestamp uVal = null;
238 		try {
239 			lVal = CoreApiServiceLocator.getDateTimeService().convertToSqlTimestamp(firstDateTime);
240 			uVal = CoreApiServiceLocator.getDateTimeService().convertToSqlTimestamp(secondDateTime);
241 		} catch (Exception ex){
242 			// this shouldn't happen because the tests passed above.
243 			String errorMessageKey = KRADServiceLocatorWeb.getDataDictionaryService().getAttributeValidatingErrorMessageKey(entryName, attributeName);
244 			String[] errorMessageParameters = KRADServiceLocatorWeb.getDataDictionaryService().getAttributeValidatingErrorMessageParameters(entryName, attributeName);
245 			ConstraintValidationResult constraintValidationResult = new ConstraintValidationResult(CONSTRAINT_NAME);
246 			constraintValidationResult.setEntryName(entryName);
247 			constraintValidationResult.setAttributeName(
248                     KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX + attributeName);
249 			constraintValidationResult.setError(errorMessageKey, errorMessageParameters);
250 			return constraintValidationResult;
251 		}
252 
253 		if (lVal != null && lVal.compareTo(uVal) > 0){ // check the bounds
254 			String errorMessageKey = KRADServiceLocatorWeb.getDataDictionaryService().getAttributeValidatingErrorMessageKey(entryName, attributeName);
255 			String[] errorMessageParameters = KRADServiceLocatorWeb.getDataDictionaryService().getAttributeValidatingErrorMessageParameters(entryName, attributeName);
256 			ConstraintValidationResult constraintValidationResult = new ConstraintValidationResult(CONSTRAINT_NAME);
257 			constraintValidationResult.setEntryName(entryName);
258 			constraintValidationResult.setAttributeName(
259                     KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX + attributeName);
260 			constraintValidationResult.setError(errorMessageKey + ".range", errorMessageParameters);
261 			return constraintValidationResult;
262 		}
263 		
264 		return null;
265 	}
266     
267 }