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