001 /** 002 * Copyright 2005-2012 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 */ 016 package org.kuali.rice.kns.service.impl; 017 018 import org.apache.commons.beanutils.PropertyUtils; 019 import org.apache.commons.lang.StringUtils; 020 import org.kuali.rice.core.api.CoreApiServiceLocator; 021 import org.kuali.rice.core.api.util.RiceKeyConstants; 022 import org.kuali.rice.core.api.util.type.TypeUtils; 023 import org.kuali.rice.core.framework.persistence.jdbc.sql.SQLUtils; 024 import org.kuali.rice.core.web.format.DateFormatter; 025 import org.kuali.rice.kns.datadictionary.MaintainableFieldDefinition; 026 import org.kuali.rice.kns.datadictionary.MaintainableItemDefinition; 027 import org.kuali.rice.kns.datadictionary.MaintainableSectionDefinition; 028 import org.kuali.rice.kns.datadictionary.MaintenanceDocumentEntry; 029 import org.kuali.rice.kns.service.DictionaryValidationService; 030 import org.kuali.rice.kns.service.KNSServiceLocator; 031 import org.kuali.rice.krad.bo.BusinessObject; 032 import org.kuali.rice.krad.datadictionary.control.ControlDefinition; 033 import org.kuali.rice.krad.document.Document; 034 import org.kuali.rice.krad.util.GlobalVariables; 035 import org.kuali.rice.krad.util.KRADConstants; 036 import org.kuali.rice.krad.util.ObjectUtils; 037 038 import java.beans.PropertyDescriptor; 039 import java.lang.reflect.Method; 040 import java.math.BigDecimal; 041 import java.util.List; 042 import java.util.regex.Pattern; 043 044 /** 045 * @author Kuali Rice Team (rice.collab@kuali.org) 046 */ 047 @Deprecated 048 public class DictionaryValidationServiceImpl extends org.kuali.rice.krad.service.impl.DictionaryValidationServiceImpl implements DictionaryValidationService { 049 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger( 050 DictionaryValidationServiceImpl.class); 051 052 /** 053 * @see org.kuali.rice.krad.service.DictionaryValidationService#validateDocumentAndUpdatableReferencesRecursively(org.kuali.rice.krad.document.Document, int, boolean, boolean) 054 * @deprecated since 2.1 055 */ 056 @Override 057 @Deprecated 058 public void validateDocumentAndUpdatableReferencesRecursively(Document document, int maxDepth, 059 boolean validateRequired, boolean chompLastLetterSFromCollectionName) { 060 // Use the KNS validation code here -- this overrides the behavior in the krad version which calls validate(...) 061 validateBusinessObject(document, validateRequired); 062 063 if (maxDepth > 0) { 064 validateUpdatabableReferencesRecursively(document, maxDepth - 1, validateRequired, 065 chompLastLetterSFromCollectionName, newIdentitySet()); 066 } 067 } 068 069 /** 070 * @see org.kuali.rice.kns.service.DictionaryValidationService#validateDocumentRecursively(org.kuali.rice.krad.document.Document, int) 071 * @deprecated since 2.0 072 */ 073 @Deprecated 074 @Override 075 public void validateDocumentRecursively(Document document, int depth) { 076 // validate primitives of document 077 validateDocument(document); 078 079 // call method to recursively find business objects and validate 080 validateBusinessObjectsFromDescriptors(document, PropertyUtils.getPropertyDescriptors(document.getClass()), 081 depth); 082 } 083 084 /** 085 * @see org.kuali.rice.kns.service.DictionaryValidationService#validateDocument(org.kuali.rice.krad.document.Document) 086 * @param document - document to validate 087 * @deprecated since 2.1.2 088 */ 089 @Deprecated 090 @Override 091 public void validateDocument(Document document) { 092 String documentEntryName = document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName(); 093 094 validatePrimitivesFromDescriptors(documentEntryName, document, PropertyUtils.getPropertyDescriptors(document.getClass()), "", true); 095 } 096 097 @Override 098 @Deprecated 099 public void validateBusinessObject(BusinessObject businessObject) { 100 validateBusinessObject(businessObject, true); 101 } 102 103 @Override 104 @Deprecated 105 public void validateBusinessObject(BusinessObject businessObject, boolean validateRequired) { 106 if (ObjectUtils.isNull(businessObject)) { 107 return; 108 } 109 try { 110 // validate the primitive attributes of the bo 111 validatePrimitivesFromDescriptors(businessObject.getClass().getName(), businessObject, 112 PropertyUtils.getPropertyDescriptors(businessObject.getClass()), "", validateRequired); 113 } catch (RuntimeException e) { 114 LOG.error(String.format("Exception while validating %s", businessObject.getClass().getName()), e); 115 throw e; 116 } 117 } 118 119 /** 120 * @deprecated since 1.1 121 */ 122 @Deprecated 123 @Override 124 public void validateBusinessObjectOnMaintenanceDocument(BusinessObject businessObject, String docTypeName) { 125 MaintenanceDocumentEntry entry = 126 KNSServiceLocator.getMaintenanceDocumentDictionaryService().getMaintenanceDocumentEntry(docTypeName); 127 for (MaintainableSectionDefinition sectionDefinition : entry.getMaintainableSections()) { 128 validateBusinessObjectOnMaintenanceDocumentHelper(businessObject, sectionDefinition.getMaintainableItems(), 129 ""); 130 } 131 } 132 133 protected void validateBusinessObjectOnMaintenanceDocumentHelper(BusinessObject businessObject, 134 List<? extends MaintainableItemDefinition> itemDefinitions, String errorPrefix) { 135 for (MaintainableItemDefinition itemDefinition : itemDefinitions) { 136 if (itemDefinition instanceof MaintainableFieldDefinition) { 137 if (getDataDictionaryService().isAttributeDefined(businessObject.getClass(), 138 itemDefinition.getName())) { 139 Object value = ObjectUtils.getPropertyValue(businessObject, itemDefinition.getName()); 140 if (value != null && StringUtils.isNotBlank(value.toString())) { 141 Class propertyType = ObjectUtils.getPropertyType(businessObject, itemDefinition.getName(), 142 persistenceStructureService); 143 if (TypeUtils.isStringClass(propertyType) || 144 TypeUtils.isIntegralClass(propertyType) || 145 TypeUtils.isDecimalClass(propertyType) || 146 TypeUtils.isTemporalClass(propertyType)) { 147 // check value format against dictionary 148 if (!TypeUtils.isTemporalClass(propertyType)) { 149 validateAttributeFormat(businessObject.getClass().getName(), itemDefinition.getName(), 150 value.toString(), errorPrefix + itemDefinition.getName()); 151 } 152 } 153 } 154 } 155 } 156 } 157 } 158 159 /** 160 * iterates through property descriptors looking for primitives types, calls validate format and required check 161 * 162 * @param entryName 163 * @param object 164 * @param propertyDescriptors 165 * @param errorPrefix 166 */ 167 @Deprecated 168 protected void validatePrimitivesFromDescriptors(String entryName, Object object, 169 PropertyDescriptor[] propertyDescriptors, String errorPrefix, boolean validateRequired) { 170 for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { 171 validatePrimitiveFromDescriptor(entryName, object, propertyDescriptor, errorPrefix, validateRequired); 172 } 173 } 174 175 /** 176 * calls validate format and required check for the given propertyDescriptor 177 * 178 * @param entryName 179 * @param object 180 * @param propertyDescriptor 181 * @param errorPrefix 182 */ 183 @Override 184 @Deprecated 185 public void validatePrimitiveFromDescriptor(String entryName, Object object, PropertyDescriptor propertyDescriptor, 186 String errorPrefix, boolean validateRequired) { 187 // validate the primitive attributes if defined in the dictionary 188 if (null != propertyDescriptor && getDataDictionaryService().isAttributeDefined(entryName, 189 propertyDescriptor.getName())) { 190 Object value = ObjectUtils.getPropertyValue(object, propertyDescriptor.getName()); 191 Class propertyType = propertyDescriptor.getPropertyType(); 192 193 if (TypeUtils.isStringClass(propertyType) || 194 TypeUtils.isIntegralClass(propertyType) || 195 TypeUtils.isDecimalClass(propertyType) || 196 TypeUtils.isTemporalClass(propertyType)) { 197 198 // check value format against dictionary 199 if (value != null && StringUtils.isNotBlank(value.toString())) { 200 if (!TypeUtils.isTemporalClass(propertyType)) { 201 validateAttributeFormat(entryName, propertyDescriptor.getName(), value.toString(), 202 errorPrefix + propertyDescriptor.getName()); 203 } 204 } else if (validateRequired) { 205 validateAttributeRequired(entryName, propertyDescriptor.getName(), value, Boolean.FALSE, 206 errorPrefix + propertyDescriptor.getName()); 207 } 208 } 209 } 210 } 211 212 /** 213 * @see org.kuali.rice.kns.service.DictionaryValidationService#validateAttributeFormat(String, String, String, String) 214 * objectClassName is the docTypeName 215 * @deprecated since 1.1 216 */ 217 @Override 218 @Deprecated 219 public void validateAttributeFormat(String objectClassName, String attributeName, String attributeInValue, 220 String errorKey) { 221 // Retrieve the field's data type, or set to the string data type if an exception occurs when retrieving the class or the DD entry. 222 String attributeDataType = null; 223 try { 224 attributeDataType = getWorkflowAttributePropertyResolutionService().determineFieldDataType( 225 (Class<? extends BusinessObject>) Class.forName( 226 getDataDictionaryService().getDataDictionary().getDictionaryObjectEntry(objectClassName) 227 .getFullClassName()), attributeName); 228 } catch (ClassNotFoundException e) { 229 attributeDataType = KRADConstants.DATA_TYPE_STRING; 230 } catch (NullPointerException e) { 231 attributeDataType = KRADConstants.DATA_TYPE_STRING; 232 } 233 234 validateAttributeFormat(objectClassName, attributeName, attributeInValue, attributeDataType, errorKey); 235 } 236 237 /** 238 * The attributeDataType parameter should be one of the data types specified by the SearchableAttribute 239 * interface; will default to DATA_TYPE_STRING if a data type other than the ones from SearchableAttribute 240 * is specified. 241 * 242 * @deprecated since 1.1 243 */ 244 @Override 245 @Deprecated 246 public void validateAttributeFormat(String objectClassName, String attributeName, String attributeInValue, 247 String attributeDataType, String errorKey) { 248 boolean checkDateBounds = false; // this is used so we can check date bounds 249 Class<?> formatterClass = null; 250 251 if (LOG.isDebugEnabled()) { 252 LOG.debug("(bo, attributeName, attributeValue) = (" + objectClassName + "," + attributeName + "," + 253 attributeInValue + ")"); 254 } 255 256 /* 257 * This will return a list of searchable attributes. so if the value is 258 * 12/07/09 .. 12/08/09 it will return [12/07/09,12/08/09] 259 */ 260 261 final List<String> attributeValues = SQLUtils.getCleanedSearchableValues(attributeInValue, attributeDataType); 262 263 if (attributeValues == null || attributeValues.isEmpty()) { 264 return; 265 } 266 267 for (String attributeValue : attributeValues) { 268 269 // FIXME: JLR : Replacing this logic with KS-style validation is trickier, since KS validation requires a DataProvider object that can 270 // look back and find other attribute values aside from the one we're working on. 271 // Also - the date stuff below is implemented very differently. 272 //validator.validateAttributeField(businessObject, fieldName); 273 274 if (StringUtils.isNotBlank(attributeValue)) { 275 Integer minLength = getDataDictionaryService().getAttributeMinLength(objectClassName, attributeName); 276 if ((minLength != null) && (minLength.intValue() > attributeValue.length())) { 277 String errorLabel = getDataDictionaryService().getAttributeErrorLabel(objectClassName, 278 attributeName); 279 GlobalVariables.getMessageMap().putError(errorKey, RiceKeyConstants.ERROR_MIN_LENGTH, 280 new String[]{errorLabel, minLength.toString()}); 281 return; 282 } 283 Integer maxLength = getDataDictionaryService().getAttributeMaxLength(objectClassName, attributeName); 284 if ((maxLength != null) && (maxLength.intValue() < attributeValue.length())) { 285 String errorLabel = getDataDictionaryService().getAttributeErrorLabel(objectClassName, 286 attributeName); 287 GlobalVariables.getMessageMap().putError(errorKey, RiceKeyConstants.ERROR_MAX_LENGTH, 288 new String[]{errorLabel, maxLength.toString()}); 289 return; 290 } 291 Pattern validationExpression = getDataDictionaryService().getAttributeValidatingExpression( 292 objectClassName, attributeName); 293 if (validationExpression != null && !validationExpression.pattern().equals(".*")) { 294 if (LOG.isDebugEnabled()) { 295 LOG.debug("(bo, attributeName, validationExpression) = (" + objectClassName + "," + 296 attributeName + "," + validationExpression + ")"); 297 } 298 299 if (!validationExpression.matcher(attributeValue).matches()) { 300 // Retrieving formatter class 301 if (formatterClass == null) { 302 // this is just a cache check... all dates ranges get called twice 303 formatterClass = getDataDictionaryService().getAttributeFormatter(objectClassName, 304 attributeName); 305 } 306 307 if (formatterClass != null) { 308 boolean valuesAreValid = true; 309 boolean isError = true; 310 String errorKeyPrefix = ""; 311 try { 312 313 // this is a special case for date ranges in order to set the proper error message 314 if (DateFormatter.class.isAssignableFrom(formatterClass)) { 315 String[] values = attributeInValue.split("\\.\\."); // is it a range 316 if (values.length == 2 && 317 attributeValues.size() == 2) { // make sure it's not like a .. b | c 318 checkDateBounds = true; // now we need to check that a <= b 319 if (attributeValues.indexOf(attributeValue) == 320 0) { // only care about lower bound 321 errorKeyPrefix = KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX; 322 } 323 } 324 } 325 326 Method validatorMethod = formatterClass.getDeclaredMethod(VALIDATE_METHOD, 327 new Class<?>[]{String.class}); 328 Object o = validatorMethod.invoke(formatterClass.newInstance(), attributeValue); 329 if (o instanceof Boolean) { 330 isError = !((Boolean) o).booleanValue(); 331 } 332 valuesAreValid &= !isError; 333 } catch (Exception e) { 334 if (LOG.isDebugEnabled()) { 335 LOG.debug(e.getMessage(), e); 336 } 337 isError = true; 338 valuesAreValid = false; 339 } 340 if (isError) { 341 checkDateBounds = false; // it's already invalid, no need to check date bounds 342 String errorMessageKey = 343 getDataDictionaryService().getAttributeValidatingErrorMessageKey( 344 objectClassName, attributeName); 345 String[] errorMessageParameters = 346 getDataDictionaryService().getAttributeValidatingErrorMessageParameters( 347 objectClassName, attributeName); 348 GlobalVariables.getMessageMap().putError(errorKeyPrefix + errorKey, errorMessageKey, 349 errorMessageParameters); 350 } 351 } else { 352 // if it fails the default validation and has no formatter class then it's still a std failure. 353 String errorMessageKey = getDataDictionaryService().getAttributeValidatingErrorMessageKey( 354 objectClassName, attributeName); 355 String[] errorMessageParameters = 356 getDataDictionaryService().getAttributeValidatingErrorMessageParameters( 357 objectClassName, attributeName); 358 GlobalVariables.getMessageMap().putError(errorKey, errorMessageKey, errorMessageParameters); 359 } 360 } 361 } 362 /*BigDecimal*/ 363 String exclusiveMin = getDataDictionaryService().getAttributeExclusiveMin(objectClassName, 364 attributeName); 365 if (exclusiveMin != null) { 366 try { 367 BigDecimal exclusiveMinBigDecimal = new BigDecimal(exclusiveMin); 368 if (exclusiveMinBigDecimal.compareTo(new BigDecimal(attributeValue)) >= 0) { 369 String errorLabel = getDataDictionaryService().getAttributeErrorLabel(objectClassName, 370 attributeName); 371 GlobalVariables.getMessageMap().putError(errorKey, RiceKeyConstants.ERROR_EXCLUSIVE_MIN, 372 // todo: Formatter for currency? 373 new String[]{errorLabel, exclusiveMin.toString()}); 374 return; 375 } 376 } catch (NumberFormatException e) { 377 // quash; this indicates that the DD contained a min for a non-numeric attribute 378 } 379 } 380 /*BigDecimal*/ 381 String inclusiveMax = getDataDictionaryService().getAttributeInclusiveMax(objectClassName, 382 attributeName); 383 if (inclusiveMax != null) { 384 try { 385 BigDecimal inclusiveMaxBigDecimal = new BigDecimal(inclusiveMax); 386 if (inclusiveMaxBigDecimal.compareTo(new BigDecimal(attributeValue)) < 0) { 387 String errorLabel = getDataDictionaryService().getAttributeErrorLabel(objectClassName, 388 attributeName); 389 GlobalVariables.getMessageMap().putError(errorKey, RiceKeyConstants.ERROR_INCLUSIVE_MAX, 390 // todo: Formatter for currency? 391 new String[]{errorLabel, inclusiveMax.toString()}); 392 return; 393 } 394 } catch (NumberFormatException e) { 395 // quash; this indicates that the DD contained a max for a non-numeric attribute 396 } 397 } 398 } 399 } 400 401 if (checkDateBounds) { 402 // this means that we only have 2 values and it's a date range. 403 java.sql.Timestamp lVal = null; 404 java.sql.Timestamp uVal = null; 405 try { 406 lVal = CoreApiServiceLocator.getDateTimeService().convertToSqlTimestamp(attributeValues.get(0)); 407 uVal = CoreApiServiceLocator.getDateTimeService().convertToSqlTimestamp(attributeValues.get(1)); 408 } catch (Exception ex) { 409 // this shouldn't happen because the tests passed above. 410 String errorMessageKey = getDataDictionaryService().getAttributeValidatingErrorMessageKey( 411 objectClassName, attributeName); 412 String[] errorMessageParameters = 413 getDataDictionaryService().getAttributeValidatingErrorMessageParameters(objectClassName, 414 attributeName); 415 GlobalVariables.getMessageMap().putError( 416 KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX + errorKey, errorMessageKey, 417 errorMessageParameters); 418 } 419 420 if (lVal != null && lVal.compareTo(uVal) > 0) { // check the bounds 421 String errorMessageKey = getDataDictionaryService().getAttributeValidatingErrorMessageKey( 422 objectClassName, attributeName); 423 String[] errorMessageParameters = 424 getDataDictionaryService().getAttributeValidatingErrorMessageParameters(objectClassName, 425 attributeName); 426 GlobalVariables.getMessageMap().putError( 427 KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX + errorKey, errorMessageKey + ".range", 428 errorMessageParameters); 429 } 430 } 431 } 432 433 // FIXME: JLR - this is now redundant and should be using the same code as the required processing elsewhere, but the control definition stuff doesn't really fit 434 // it doesn't seem to be used anywhere 435 @Override 436 @Deprecated 437 public void validateAttributeRequired(String objectClassName, String attributeName, Object attributeValue, 438 Boolean forMaintenance, String errorKey) { 439 // check if field is a required field for the business object 440 if (attributeValue == null || (attributeValue instanceof String && StringUtils.isBlank( 441 (String) attributeValue))) { 442 Boolean required = getDataDictionaryService().isAttributeRequired(objectClassName, attributeName); 443 ControlDefinition controlDef = getDataDictionaryService().getAttributeControlDefinition(objectClassName, 444 attributeName); 445 446 if (required != null && required.booleanValue() && !(controlDef != null && controlDef.isHidden())) { 447 448 // get label of attribute for message 449 String errorLabel = getDataDictionaryService().getAttributeErrorLabel(objectClassName, attributeName); 450 GlobalVariables.getMessageMap().putError(errorKey, RiceKeyConstants.ERROR_REQUIRED, errorLabel); 451 } 452 } 453 } 454 }