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.apache.commons.lang.StringUtils; 019import org.apache.log4j.Logger; 020import org.kuali.rice.core.api.CoreApiServiceLocator; 021import org.kuali.rice.core.api.search.SearchOperator; 022import org.kuali.rice.core.api.util.ClassLoaderUtils; 023import org.kuali.rice.core.api.util.RiceKeyConstants; 024import org.kuali.rice.core.web.format.DateFormatter; 025import org.kuali.rice.krad.datadictionary.exception.AttributeValidationException; 026import org.kuali.rice.krad.datadictionary.validation.AttributeValueReader; 027import org.kuali.rice.krad.datadictionary.validation.ValidationUtils; 028import org.kuali.rice.krad.datadictionary.validation.capability.Constrainable; 029import org.kuali.rice.krad.datadictionary.validation.capability.Formatable; 030import org.kuali.rice.krad.datadictionary.validation.constraint.Constraint; 031import org.kuali.rice.krad.datadictionary.validation.constraint.ValidCharactersConstraint; 032import org.kuali.rice.krad.datadictionary.validation.result.ConstraintValidationResult; 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; 036import org.kuali.rice.krad.util.KRADConstants; 037 038import java.lang.reflect.Method; 039import java.util.List; 040 041/** 042 * This class defines a constraint processor to ensure that attribute values are constrained to valid characters, as 043 * defined by some regular expression. Of the 044 * constraint processors written for this version, this one is potentially the most difficult to understand because it 045 * holds on to a lot of legacy processing. 046 * 047 * @author Kuali Rice Team (rice.collab@kuali.org) 048 */ 049public class ValidCharactersConstraintProcessor extends MandatoryElementConstraintProcessor<ValidCharactersConstraint> { 050 051 public static final String VALIDATE_METHOD = "validate"; 052 053 private static final Logger LOG = Logger.getLogger(ValidCharactersConstraintProcessor.class); 054 private static final String[] DATE_RANGE_ERROR_PREFIXES = {KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX, 055 KRADConstants.LOOKUP_RANGE_UPPER_BOUND_PROPERTY_PREFIX}; 056 057 private static final String CONSTRAINT_NAME = "valid characters constraint"; 058 059 /** 060 * {@inheritDoc} 061 */ 062 @Override 063 public ProcessorResult process(DictionaryValidationResult result, Object value, 064 ValidCharactersConstraint constraint, 065 AttributeValueReader attributeValueReader) throws AttributeValidationException { 066 067 return new ProcessorResult(processSingleValidCharacterConstraint(result, value, constraint, 068 attributeValueReader)); 069 } 070 071 @Override 072 public String getName() { 073 return CONSTRAINT_NAME; 074 } 075 076 /** 077 * @see org.kuali.rice.krad.datadictionary.validation.processor.ConstraintProcessor#getConstraintType() 078 */ 079 @Override 080 public Class<? extends Constraint> getConstraintType() { 081 return ValidCharactersConstraint.class; 082 } 083 084 protected ConstraintValidationResult processSingleValidCharacterConstraint(DictionaryValidationResult result, 085 Object value, ValidCharactersConstraint constraint, 086 AttributeValueReader attributeValueReader) throws AttributeValidationException { 087 088 if (constraint == null) { 089 return result.addNoConstraint(attributeValueReader, CONSTRAINT_NAME); 090 } 091 092 if (ValidationUtils.isNullOrEmpty(value)) { 093 return result.addSkipped(attributeValueReader, CONSTRAINT_NAME); 094 } 095 096 // This mix-in interface is here to allow some definitions to avoid the extra processing that goes on in KNS 097 // to decipher and validate things like date range strings -- something that looks like "02/02/2002..03/03/2003" 098 Constrainable definition = attributeValueReader.getDefinition(attributeValueReader.getAttributeName()); 099 if (definition instanceof Formatable) { 100 return doProcessFormattableValidCharConstraint(result, constraint, (Formatable) definition, value, 101 attributeValueReader); 102 } 103 104 ConstraintValidationResult constraintValidationResult = doProcessValidCharConstraint(constraint, value); 105 if (constraintValidationResult == null) { 106 return result.addSuccess(attributeValueReader, CONSTRAINT_NAME); 107 } 108 109 result.addConstraintValidationResult(attributeValueReader, constraintValidationResult); 110 constraintValidationResult.setConstraintLabelKey(constraint.getMessageKey()); 111 constraintValidationResult.setErrorParameters(constraint.getValidationMessageParamsArray()); 112 return constraintValidationResult; 113 } 114 115 protected ConstraintValidationResult doProcessFormattableValidCharConstraint(DictionaryValidationResult result, 116 ValidCharactersConstraint validCharsConstraint, Formatable definition, Object value, 117 AttributeValueReader attributeValueReader) throws AttributeValidationException { 118 String entryName = attributeValueReader.getEntryName(); 119 String attributeName = attributeValueReader.getAttributeName(); 120 121 // 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 122 List<String> parsedAttributeValues = attributeValueReader.getCleanSearchableValues(attributeName); 123 124 if (parsedAttributeValues != null) { 125 126 Class<?> formatterClass = null; 127 Boolean doValidateDateRangeOrder = null; 128 129 // 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 130 if (parsedAttributeValues.size() != 2) { 131 doValidateDateRangeOrder = Boolean.FALSE; 132 } 133 134 // Use integer to iterate since we need to track which field we're looking at 135 for (int i = 0; i < parsedAttributeValues.size(); i++) { 136 String parsedAttributeValue = parsedAttributeValues.get(i); 137 138 ConstraintValidationResult constraintValidationResult = doProcessValidCharConstraint( 139 validCharsConstraint, parsedAttributeValue); 140 141 // If this is an error then some non-null validation result will be returned 142 if (constraintValidationResult != null) { 143 constraintValidationResult.setConstraintLabelKey(validCharsConstraint.getMessageKey()); 144 constraintValidationResult.setErrorParameters( 145 validCharsConstraint.getValidationMessageParamsArray()); 146 // 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 147 if (formatterClass == null) { 148 String formatterClassName = definition.getFormatterClass(); 149 if (formatterClassName != null) { 150 formatterClass = ClassLoaderUtils.getClass(formatterClassName); 151 } 152 } 153 154 if (formatterClass != null) { 155 // Use the Boolean value being null to ensure we only do this once 156 if (doValidateDateRangeOrder == null) { 157 // 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) 158 doValidateDateRangeOrder = Boolean.valueOf(DateFormatter.class.isAssignableFrom( 159 formatterClass) && StringUtils.contains(ValidationUtils.getString(value), 160 SearchOperator.BETWEEN.toString())); 161 } 162 163 constraintValidationResult = processFormatterValidation(result, formatterClass, entryName, 164 attributeName, parsedAttributeValue, DATE_RANGE_ERROR_PREFIXES[i]); 165 166 if (constraintValidationResult != null) { 167 result.addConstraintValidationResult(attributeValueReader, constraintValidationResult); 168 return constraintValidationResult; 169 } 170 } else { 171 // Otherwise, just report the validation result (apparently the formatter can't provide any fall-through validation because it doesn't exist) 172 result.addConstraintValidationResult(attributeValueReader, constraintValidationResult); 173 return constraintValidationResult; 174 } 175 } 176 } 177 178 if (doValidateDateRangeOrder != null && doValidateDateRangeOrder.booleanValue()) { 179 ConstraintValidationResult dateOrderValidationResult = validateDateOrder(parsedAttributeValues.get(0), 180 parsedAttributeValues.get(1), entryName, attributeName); 181 182 if (dateOrderValidationResult != null) { 183 result.addConstraintValidationResult(attributeValueReader, dateOrderValidationResult); 184 return dateOrderValidationResult; 185 } 186 } 187 188 return result.addSuccess(attributeValueReader, CONSTRAINT_NAME); 189 } 190 return result.addSkipped(attributeValueReader, CONSTRAINT_NAME); 191 } 192 193 protected ConstraintValidationResult doProcessValidCharConstraint(ValidCharactersConstraint validCharsConstraint, 194 Object value) { 195 196 StringBuilder fieldValue = new StringBuilder(); 197 String validChars = validCharsConstraint.getValue(); 198 199 if (value instanceof java.sql.Date) { 200 fieldValue.append(getDateTimeService().toDateString((java.sql.Date) value)); 201 } else { 202 fieldValue.append(ValidationUtils.getString(value)); 203 } 204 205 // int typIdx = validChars.indexOf(":"); 206 // String processorType = "regex"; 207 // if (-1 == typIdx) { 208 // validChars = "[" + validChars + "]*"; 209 // } else { 210 // processorType = validChars.substring(0, typIdx); 211 // validChars = validChars.substring(typIdx + 1); 212 // } 213 214 // if ("regex".equalsIgnoreCase(processorType) && !validChars.equals(".*")) { 215 if (!fieldValue.toString().matches(validChars)) { 216 ConstraintValidationResult constraintValidationResult = new ConstraintValidationResult(CONSTRAINT_NAME); 217 constraintValidationResult.setError(RiceKeyConstants.ERROR_INVALID_FORMAT, fieldValue.toString()); 218 constraintValidationResult.setConstraintLabelKey(validCharsConstraint.getMessageKey()); 219 constraintValidationResult.setErrorParameters(validCharsConstraint.getValidationMessageParamsArray()); 220 return constraintValidationResult; 221 } 222 // } 223 224 return null; 225 } 226 227 protected ConstraintValidationResult processFormatterValidation(DictionaryValidationResult result, 228 Class<?> formatterClass, String entryName, String attributeName, String parsedAttributeValue, 229 String errorKeyPrefix) { 230 231 boolean isError = false; 232 233 try { 234 Method validatorMethod = formatterClass.getDeclaredMethod(VALIDATE_METHOD, new Class<?>[]{String.class}); 235 Object o = validatorMethod.invoke(formatterClass.newInstance(), parsedAttributeValue); 236 if (o instanceof Boolean) { 237 isError = !((Boolean) o).booleanValue(); 238 } 239 } catch (Exception e) { 240 if (LOG.isDebugEnabled()) { 241 LOG.debug(e.getMessage(), e); 242 } 243 244 isError = true; 245 } 246 247 if (isError) { 248 String errorMessageKey = getDataDictionaryService().getAttributeValidatingErrorMessageKey(entryName, 249 attributeName); 250 String[] errorMessageParameters = getDataDictionaryService().getAttributeValidatingErrorMessageParameters( 251 entryName, attributeName); 252 253 ConstraintValidationResult constraintValidationResult = new ConstraintValidationResult(CONSTRAINT_NAME); 254 constraintValidationResult.setEntryName(entryName); 255 constraintValidationResult.setAttributeName(errorKeyPrefix + attributeName); 256 constraintValidationResult.setError(errorMessageKey, errorMessageParameters); 257 258 return constraintValidationResult; 259 } 260 261 return null; 262 } 263 264 protected ConstraintValidationResult validateDateOrder(String firstDateTime, String secondDateTime, 265 String entryName, String attributeName) { 266 // this means that we only have 2 values and it's a date range. 267 java.sql.Timestamp lVal = null; 268 java.sql.Timestamp uVal = null; 269 try { 270 lVal = CoreApiServiceLocator.getDateTimeService().convertToSqlTimestamp(firstDateTime); 271 uVal = CoreApiServiceLocator.getDateTimeService().convertToSqlTimestamp(secondDateTime); 272 } catch (Exception ex) { 273 // this shouldn't happen because the tests passed above. 274 String errorMessageKey = 275 KRADServiceLocatorWeb.getDataDictionaryService().getAttributeValidatingErrorMessageKey(entryName, 276 attributeName); 277 String[] errorMessageParameters = 278 KRADServiceLocatorWeb.getDataDictionaryService().getAttributeValidatingErrorMessageParameters( 279 entryName, attributeName); 280 ConstraintValidationResult constraintValidationResult = new ConstraintValidationResult(CONSTRAINT_NAME); 281 constraintValidationResult.setEntryName(entryName); 282 constraintValidationResult.setAttributeName( 283 KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX + attributeName); 284 constraintValidationResult.setError(errorMessageKey, errorMessageParameters); 285 return constraintValidationResult; 286 } 287 288 if (lVal != null && lVal.compareTo(uVal) > 0) { // check the bounds 289 String errorMessageKey = 290 KRADServiceLocatorWeb.getDataDictionaryService().getAttributeValidatingErrorMessageKey(entryName, 291 attributeName); 292 String[] errorMessageParameters = 293 KRADServiceLocatorWeb.getDataDictionaryService().getAttributeValidatingErrorMessageParameters( 294 entryName, attributeName); 295 ConstraintValidationResult constraintValidationResult = new ConstraintValidationResult(CONSTRAINT_NAME); 296 constraintValidationResult.setEntryName(entryName); 297 constraintValidationResult.setAttributeName( 298 KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX + attributeName); 299 constraintValidationResult.setError(errorMessageKey + ".range", errorMessageParameters); 300 return constraintValidationResult; 301 } 302 303 return null; 304 } 305 306}