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