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.krad.service.impl; 017 018 import org.apache.commons.beanutils.PropertyUtils; 019 import org.apache.commons.lang.ArrayUtils; 020 import org.apache.commons.lang.StringUtils; 021 import org.kuali.rice.core.api.mo.common.active.MutableInactivatable; 022 import org.kuali.rice.core.api.util.RiceKeyConstants; 023 import org.kuali.rice.krad.bo.BusinessObject; 024 import org.kuali.rice.krad.bo.PersistableBusinessObject; 025 import org.kuali.rice.krad.datadictionary.AttributeDefinition; 026 import org.kuali.rice.krad.datadictionary.CollectionDefinition; 027 import org.kuali.rice.krad.datadictionary.ComplexAttributeDefinition; 028 import org.kuali.rice.krad.datadictionary.DataDictionaryEntry; 029 import org.kuali.rice.krad.datadictionary.DataDictionaryEntryBase; 030 import org.kuali.rice.krad.datadictionary.DataObjectEntry; 031 import org.kuali.rice.krad.datadictionary.ReferenceDefinition; 032 import org.kuali.rice.krad.datadictionary.exception.AttributeValidationException; 033 import org.kuali.rice.krad.datadictionary.validation.AttributeValueReader; 034 import org.kuali.rice.krad.datadictionary.validation.DictionaryObjectAttributeValueReader; 035 import org.kuali.rice.krad.datadictionary.validation.ErrorLevel; 036 import org.kuali.rice.krad.datadictionary.validation.SingleAttributeValueReader; 037 import org.kuali.rice.krad.datadictionary.validation.capability.Constrainable; 038 import org.kuali.rice.krad.datadictionary.validation.constraint.Constraint; 039 import org.kuali.rice.krad.datadictionary.validation.constraint.provider.ConstraintProvider; 040 import org.kuali.rice.krad.datadictionary.validation.processor.CollectionConstraintProcessor; 041 import org.kuali.rice.krad.datadictionary.validation.processor.ConstraintProcessor; 042 import org.kuali.rice.krad.datadictionary.validation.result.ConstraintValidationResult; 043 import org.kuali.rice.krad.datadictionary.validation.result.DictionaryValidationResult; 044 import org.kuali.rice.krad.datadictionary.validation.result.ProcessorResult; 045 import org.kuali.rice.krad.document.Document; 046 import org.kuali.rice.krad.document.TransactionalDocument; 047 import org.kuali.rice.krad.exception.ObjectNotABusinessObjectRuntimeException; 048 import org.kuali.rice.krad.service.BusinessObjectService; 049 import org.kuali.rice.krad.service.DataDictionaryService; 050 import org.kuali.rice.krad.service.DictionaryValidationService; 051 import org.kuali.rice.krad.service.DocumentDictionaryService; 052 import org.kuali.rice.krad.service.KRADServiceLocatorInternal; 053 import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 054 import org.kuali.rice.krad.service.PersistenceService; 055 import org.kuali.rice.krad.service.PersistenceStructureService; 056 import org.kuali.rice.krad.util.GlobalVariables; 057 import org.kuali.rice.krad.util.MessageMap; 058 import org.kuali.rice.krad.util.ObjectUtils; 059 import org.kuali.rice.krad.workflow.service.WorkflowAttributePropertyResolutionService; 060 061 import java.beans.PropertyDescriptor; 062 import java.lang.reflect.InvocationTargetException; 063 import java.util.Arrays; 064 import java.util.Collection; 065 import java.util.IdentityHashMap; 066 import java.util.Iterator; 067 import java.util.LinkedList; 068 import java.util.List; 069 import java.util.Map; 070 import java.util.Queue; 071 import java.util.Set; 072 073 /** 074 * Validates Documents, Business Objects, and Attributes against the data dictionary. Including min, max lengths, and 075 * validating expressions. This is the default, Kuali delivered implementation. 076 * 077 * KULRICE - 3355 Modified to prevent infinite looping (to maxDepth) scenario when a parent references a child which 078 * references a parent 079 * 080 * @author Kuali Rice Team (rice.collab@kuali.org) 081 */ 082 public class DictionaryValidationServiceImpl implements DictionaryValidationService { 083 private static org.apache.log4j.Logger LOG = 084 org.apache.log4j.Logger.getLogger(DictionaryValidationServiceImpl.class); 085 086 /** 087 * Constant defines a validation method for an attribute value. 088 * <p>Value is "validate" 089 */ 090 public static final String VALIDATE_METHOD = "validate"; 091 092 protected DataDictionaryService dataDictionaryService; 093 protected BusinessObjectService businessObjectService; 094 protected PersistenceService persistenceService; 095 protected DocumentDictionaryService documentDictionaryService; 096 protected WorkflowAttributePropertyResolutionService workflowAttributePropertyResolutionService; 097 protected PersistenceStructureService persistenceStructureService; 098 099 @SuppressWarnings("unchecked") 100 private List<CollectionConstraintProcessor> collectionConstraintProcessors; 101 @SuppressWarnings("unchecked") 102 private List<ConstraintProvider> constraintProviders; 103 @SuppressWarnings("unchecked") 104 private List<ConstraintProcessor> elementConstraintProcessors; 105 106 /** 107 * creates a new IdentitySet. 108 * 109 * @return a new Set 110 */ 111 private static Set<BusinessObject> newIdentitySet() { 112 return java.util.Collections.newSetFromMap(new IdentityHashMap<BusinessObject, Boolean>()); 113 } 114 115 /** 116 * @see org.kuali.rice.krad.service.DictionaryValidationService#validate(java.lang.Object) 117 */ 118 public DictionaryValidationResult validate(Object object) { 119 return validate(object, object.getClass().getName(), true); 120 } 121 122 /** 123 * @see org.kuali.rice.krad.service.DictionaryValidationService#validate(java.lang.Object, boolean) 124 */ 125 public DictionaryValidationResult validate(Object object, boolean doOptionalProcessing) { 126 return validate(object, object.getClass().getName(), doOptionalProcessing); 127 } 128 129 /** 130 * @see org.kuali.rice.krad.service.DictionaryValidationService#validate(java.lang.Object, java.lang.String) 131 */ 132 public DictionaryValidationResult validate(Object object, String entryName) { 133 return validate(object, entryName, true); 134 } 135 136 /** 137 * @see org.kuali.rice.krad.service.DictionaryValidationService#validate(java.lang.Object, java.lang.String, 138 * boolean) 139 */ 140 public DictionaryValidationResult validate(Object object, String entryName, boolean doOptionalProcessing) { 141 return validate(object, entryName, (String) null, doOptionalProcessing); 142 } 143 144 /** 145 * @see org.kuali.rice.krad.service.DictionaryValidationService#validate(java.lang.Object, java.lang.String, 146 * java.lang.String) 147 */ 148 public DictionaryValidationResult validate(Object object, String entryName, String attributeName) { 149 return validate(object, entryName, attributeName, true); 150 } 151 152 /** 153 * @see org.kuali.rice.krad.service.DictionaryValidationService#validate(java.lang.Object, java.lang.String, 154 * java.lang.String, boolean) 155 */ 156 public DictionaryValidationResult validate(Object object, String entryName, String attributeName, 157 boolean doOptionalProcessing) { 158 DataDictionaryEntry entry = getDataDictionaryService().getDataDictionary().getDictionaryObjectEntry(entryName); 159 AttributeValueReader attributeValueReader = new DictionaryObjectAttributeValueReader(object, entryName, entry); 160 attributeValueReader.setAttributeName(attributeName); 161 return validate(attributeValueReader, doOptionalProcessing); 162 } 163 164 public DictionaryValidationResult validate(Object object, String entryName, DataDictionaryEntry entry, 165 boolean doOptionalProcessing) { 166 AttributeValueReader attributeValueReader = new DictionaryObjectAttributeValueReader(object, entryName, entry); 167 return validate(attributeValueReader, doOptionalProcessing); 168 } 169 170 public void validate(String entryName, String attributeName, Object attributeValue) { 171 validate(entryName, attributeName, attributeValue, true); 172 } 173 174 public void validate(String entryName, String attributeName, Object attributeValue, boolean doOptionalProcessing) { 175 AttributeDefinition attributeDefinition = 176 getDataDictionaryService().getAttributeDefinition(entryName, attributeName); 177 178 if (attributeDefinition == null) { 179 // FIXME: JLR - this is what the code was doing effectively already, but seems weird not to throw an exception here if you try to validate 180 // something that doesn't have an attribute definition 181 return; 182 } 183 184 SingleAttributeValueReader attributeValueReader = 185 new SingleAttributeValueReader(attributeValue, entryName, attributeName, attributeDefinition); 186 validate(attributeValueReader, doOptionalProcessing); 187 } 188 189 /** 190 * @see org.kuali.rice.krad.service.DictionaryValidationService#validateDocument(org.kuali.rice.krad.document.Document) 191 */ 192 @Override 193 public void validateDocument(Document document) { 194 String documentEntryName = document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName(); 195 196 validate(document, documentEntryName); 197 } 198 199 /** 200 * @see org.kuali.rice.krad.service.DictionaryValidationService#validateDocumentAttribute(org.kuali.rice.krad.document.Document, 201 * java.lang.String, java.lang.String) 202 */ 203 @Override 204 public void validateDocumentAttribute(Document document, String attributeName, String errorPrefix) { 205 String documentEntryName = document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName(); 206 207 validate(document, documentEntryName, attributeName, true); 208 } 209 210 /** 211 * @see org.kuali.rice.krad.service.DictionaryValidationService#validateDocumentAndUpdatableReferencesRecursively(org.kuali.rice.krad.document.Document, int, boolean) 212 */ 213 @Override 214 public void validateDocumentAndUpdatableReferencesRecursively(Document document, int maxDepth, 215 boolean validateRequired) { 216 validateDocumentAndUpdatableReferencesRecursively(document, maxDepth, validateRequired, false); 217 } 218 /** 219 * @see org.kuali.rice.krad.service.DictionaryValidationService#validateDocumentAndUpdatableReferencesRecursively(org.kuali.rice.krad.document.Document, int, boolean, boolean) 220 */ 221 @Override 222 public void validateDocumentAndUpdatableReferencesRecursively(Document document, int maxDepth, 223 boolean validateRequired, boolean chompLastLetterSFromCollectionName) { 224 String documentEntryName = document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName(); 225 validate(document, documentEntryName); 226 227 if (maxDepth > 0) { 228 validateUpdatabableReferencesRecursively(document, maxDepth - 1, validateRequired, 229 chompLastLetterSFromCollectionName, newIdentitySet()); 230 } 231 } 232 233 private void validateUpdatabableReferencesRecursively(BusinessObject businessObject, int maxDepth, 234 boolean validateRequired, boolean chompLastLetterSFromCollectionName, Set<BusinessObject> processedBOs) { 235 // if null or already processed, return 236 if (ObjectUtils.isNull(businessObject) || processedBOs.contains(businessObject)) { 237 return; 238 } 239 processedBOs.add(businessObject); // add bo to list to prevent excessive looping 240 Map<String, Class> references = 241 persistenceStructureService.listReferenceObjectFields(businessObject.getClass()); 242 for (String referenceName : references.keySet()) { 243 if (persistenceStructureService.isReferenceUpdatable(businessObject.getClass(), referenceName)) { 244 Object referenceObj = ObjectUtils.getPropertyValue(businessObject, referenceName); 245 246 if (ObjectUtils.isNull(referenceObj) || !(referenceObj instanceof PersistableBusinessObject)) { 247 continue; 248 } 249 250 BusinessObject referenceBusinessObject = (BusinessObject) referenceObj; 251 GlobalVariables.getMessageMap().addToErrorPath(referenceName); 252 validateBusinessObject(referenceBusinessObject, validateRequired); 253 if (maxDepth > 0) { 254 validateUpdatabableReferencesRecursively(referenceBusinessObject, maxDepth - 1, validateRequired, 255 chompLastLetterSFromCollectionName, processedBOs); 256 } 257 GlobalVariables.getMessageMap().removeFromErrorPath(referenceName); 258 } 259 } 260 Map<String, Class> collections = 261 persistenceStructureService.listCollectionObjectTypes(businessObject.getClass()); 262 for (String collectionName : collections.keySet()) { 263 if (persistenceStructureService.isCollectionUpdatable(businessObject.getClass(), collectionName)) { 264 Object listObj = ObjectUtils.getPropertyValue(businessObject, collectionName); 265 266 if (ObjectUtils.isNull(listObj)) { 267 continue; 268 } 269 270 if (!(listObj instanceof List)) { 271 if (LOG.isInfoEnabled()) { 272 LOG.info("The reference named " + collectionName + " of BO class " + 273 businessObject.getClass().getName() + 274 " should be of type java.util.List to be validated properly."); 275 } 276 continue; 277 } 278 279 List list = (List) listObj; 280 281 //should we materialize the proxied collection or just skip validation here assuming an unmaterialized objects are valid? 282 ObjectUtils.materializeObjects(list); 283 284 for (int i = 0; i < list.size(); i++) { 285 final Object o = list.get(i); 286 if (ObjectUtils.isNotNull(o) && o instanceof PersistableBusinessObject) { 287 final BusinessObject element = (BusinessObject) o; 288 289 final String errorPathAddition; 290 if (chompLastLetterSFromCollectionName) { 291 errorPathAddition = 292 StringUtils.chomp(collectionName, "s") + "[" + Integer.toString(i) + "]"; 293 } else { 294 errorPathAddition = collectionName + "[" + Integer.toString(i) + "]"; 295 } 296 297 GlobalVariables.getMessageMap().addToErrorPath(errorPathAddition); 298 validateBusinessObject(element, validateRequired); 299 if (maxDepth > 0) { 300 validateUpdatabableReferencesRecursively(element, maxDepth - 1, validateRequired, 301 chompLastLetterSFromCollectionName, processedBOs); 302 } 303 GlobalVariables.getMessageMap().removeFromErrorPath(errorPathAddition); 304 } 305 } 306 } 307 } 308 } 309 310 /** 311 * @see org.kuali.rice.krad.service.DictionaryValidationService#isBusinessObjectValid(org.kuali.rice.krad.bo.BusinessObject) 312 */ 313 public boolean isBusinessObjectValid(BusinessObject businessObject) { 314 return isBusinessObjectValid(businessObject, null); 315 } 316 317 /** 318 * @see org.kuali.rice.krad.service.DictionaryValidationService#isBusinessObjectValid(org.kuali.rice.krad.bo.BusinessObject, 319 * String) 320 */ 321 public boolean isBusinessObjectValid(BusinessObject businessObject, String prefix) { 322 final MessageMap errorMap = GlobalVariables.getMessageMap(); 323 int originalErrorCount = errorMap.getErrorCount(); 324 325 errorMap.addToErrorPath(prefix); 326 validateBusinessObject(businessObject); 327 errorMap.removeFromErrorPath(prefix); 328 329 return errorMap.getErrorCount() == originalErrorCount; 330 } 331 332 /** 333 * @param businessObject - business object to validate 334 */ 335 public void validateBusinessObjectsRecursively(BusinessObject businessObject, int depth) { 336 if (ObjectUtils.isNull(businessObject)) { 337 return; 338 } 339 340 // validate primitives and any specific bo validation 341 validateBusinessObject(businessObject); 342 343 // call method to recursively find business objects and validate 344 validateBusinessObjectsFromDescriptors(businessObject, 345 PropertyUtils.getPropertyDescriptors(businessObject.getClass()), depth); 346 } 347 348 /** 349 * @see org.kuali.rice.krad.service.DictionaryValidationService#validateBusinessObject(org.kuali.rice.krad.bo.BusinessObject) 350 */ 351 @Override 352 public void validateBusinessObject(BusinessObject businessObject) { 353 validateBusinessObject(businessObject, true); 354 } 355 356 /** 357 * @see org.kuali.rice.krad.service.DictionaryValidationService#validateBusinessObject(org.kuali.rice.krad.bo.BusinessObject, 358 * boolean) 359 */ 360 @Override 361 public void validateBusinessObject(BusinessObject businessObject, boolean validateRequired) { 362 if (ObjectUtils.isNull(businessObject)) { 363 return; 364 } 365 366 validate(businessObject, businessObject.getClass().getName(), validateRequired); 367 } 368 369 /** 370 * iterates through the property discriptors looking for business objects or lists of business objects. calls 371 * validate method 372 * for each bo found 373 * 374 * @param object 375 * @param propertyDescriptors 376 */ 377 protected void validateBusinessObjectsFromDescriptors(Object object, PropertyDescriptor[] propertyDescriptors, 378 int depth) { 379 for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { 380 // validate the properties that are descended from BusinessObject 381 if (propertyDescriptor.getPropertyType() != null && 382 PersistableBusinessObject.class.isAssignableFrom(propertyDescriptor.getPropertyType()) && 383 ObjectUtils.getPropertyValue(object, propertyDescriptor.getName()) != null) { 384 BusinessObject bo = (BusinessObject) ObjectUtils.getPropertyValue(object, propertyDescriptor.getName()); 385 if (depth == 0) { 386 GlobalVariables.getMessageMap().addToErrorPath(propertyDescriptor.getName()); 387 validateBusinessObject(bo); 388 GlobalVariables.getMessageMap().removeFromErrorPath(propertyDescriptor.getName()); 389 } else { 390 validateBusinessObjectsRecursively(bo, depth - 1); 391 } 392 } 393 394 /* 395 * if property is a List, then walk the list and do the validation on each contained object that is a descendent of 396 * BusinessObject 397 */ 398 else if (propertyDescriptor.getPropertyType() != null && 399 (List.class).isAssignableFrom(propertyDescriptor.getPropertyType()) && 400 ObjectUtils.getPropertyValue(object, propertyDescriptor.getName()) != null) { 401 List propertyList = (List) ObjectUtils.getPropertyValue(object, propertyDescriptor.getName()); 402 for (int j = 0; j < propertyList.size(); j++) { 403 if (propertyList.get(j) != null && propertyList.get(j) instanceof PersistableBusinessObject) { 404 if (depth == 0) { 405 GlobalVariables.getMessageMap().addToErrorPath( 406 StringUtils.chomp(propertyDescriptor.getName(), "s") + "[" + 407 (new Integer(j)).toString() + "]"); 408 validateBusinessObject((BusinessObject) propertyList.get(j)); 409 GlobalVariables.getMessageMap().removeFromErrorPath( 410 StringUtils.chomp(propertyDescriptor.getName(), "s") + "[" + 411 (new Integer(j)).toString() + "]"); 412 } else { 413 validateBusinessObjectsRecursively((BusinessObject) propertyList.get(j), depth - 1); 414 } 415 } 416 } 417 } 418 } 419 } 420 421 /** 422 * calls validate format and required check for the given propertyDescriptor 423 * 424 * @param entryName 425 * @param object 426 * @param propertyDescriptor 427 * @param errorPrefix 428 * @deprecated since 1.1 429 */ 430 @Deprecated 431 public void validatePrimitiveFromDescriptor(String entryName, Object object, PropertyDescriptor propertyDescriptor, 432 String errorPrefix, boolean validateRequired) { 433 434 // validate the primitive attributes if defined in the dictionary 435 if (null != propertyDescriptor) { 436 validate(object, entryName, propertyDescriptor.getName(), validateRequired); 437 } 438 } 439 440 /** 441 * @see org.kuali.rice.krad.service.DictionaryValidationService#validateReferenceExists(org.kuali.rice.krad.bo.BusinessObject, 442 * org.kuali.rice.krad.datadictionary.ReferenceDefinition) 443 */ 444 public boolean validateReferenceExists(BusinessObject bo, ReferenceDefinition reference) { 445 return validateReferenceExists(bo, reference.getAttributeName()); 446 } 447 448 /** 449 * @see org.kuali.rice.krad.service.DictionaryValidationService#validateReferenceExists(org.kuali.rice.krad.bo.BusinessObject, 450 * java.lang.String) 451 */ 452 public boolean validateReferenceExists(BusinessObject bo, String referenceName) { 453 454 // attempt to retrieve the specified object from the db 455 BusinessObject referenceBo = businessObjectService.getReferenceIfExists(bo, referenceName); 456 457 // if it isn't there, then it doesn't exist, return false 458 if (ObjectUtils.isNotNull(referenceBo)) { 459 return true; 460 } 461 462 // otherwise, it is there, return true 463 return false; 464 } 465 466 /** 467 * @see org.kuali.rice.krad.service.DictionaryValidationService#validateReferenceIsActive(org.kuali.rice.krad.bo.BusinessObject, 468 * org.kuali.rice.krad.datadictionary.ReferenceDefinition) 469 */ 470 public boolean validateReferenceIsActive(BusinessObject bo, ReferenceDefinition reference) { 471 return validateReferenceIsActive(bo, reference.getAttributeName()); 472 } 473 474 /** 475 * @see org.kuali.rice.krad.service.DictionaryValidationService#validateReferenceIsActive(org.kuali.rice.krad.bo.BusinessObject, String) 476 */ 477 public boolean validateReferenceIsActive(BusinessObject bo, String referenceName) { 478 479 // attempt to retrieve the specified object from the db 480 BusinessObject referenceBo = businessObjectService.getReferenceIfExists(bo, referenceName); 481 if (referenceBo == null) { 482 return false; 483 } 484 if (!(referenceBo instanceof MutableInactivatable) || ((MutableInactivatable) referenceBo).isActive()) { 485 return true; 486 } 487 488 return false; 489 } 490 491 /** 492 * @see org.kuali.rice.krad.service.DictionaryValidationService#validateReferenceExistsAndIsActive(org.kuali.rice.krad.bo.BusinessObject, 493 * org.kuali.rice.krad.datadictionary.ReferenceDefinition) 494 */ 495 public boolean validateReferenceExistsAndIsActive(BusinessObject bo, ReferenceDefinition reference) { 496 boolean success = true; 497 // intelligently use the fieldname from the reference, or get it out 498 // of the dataDictionaryService 499 String displayFieldName; 500 if (reference.isDisplayFieldNameSet()) { 501 displayFieldName = reference.getDisplayFieldName(); 502 } else { 503 Class<?> boClass = 504 reference.isCollectionReference() ? reference.getCollectionBusinessObjectClass() : bo.getClass(); 505 displayFieldName = 506 dataDictionaryService.getAttributeLabel(boClass, reference.getAttributeToHighlightOnFail()); 507 } 508 509 if (reference.isCollectionReference()) { 510 success = validateCollectionReferenceExistsAndIsActive(bo, reference, displayFieldName, 511 StringUtils.split(reference.getCollection(), "."), null); 512 } else { 513 success = validateReferenceExistsAndIsActive(bo, reference.getAttributeName(), 514 reference.getAttributeToHighlightOnFail(), displayFieldName); 515 } 516 return success; 517 } 518 519 /** 520 * @param bo the object to get the collection from 521 * @param reference the <code>ReferenceDefinition</code> of the collection to validate 522 * @param displayFieldName the name of the field 523 * @param intermediateCollections array containing the path to the collection as tokens 524 * @param pathToAttributeI the rebuilt path to the ReferenceDefinition.attributeToHighlightOnFail which includes the 525 * index of 526 * each subcollection 527 * @return 528 */ 529 private boolean validateCollectionReferenceExistsAndIsActive(BusinessObject bo, ReferenceDefinition reference, 530 String displayFieldName, String[] intermediateCollections, String pathToAttributeI) { 531 boolean success = true; 532 Collection<PersistableBusinessObject> referenceCollection; 533 String collectionName = intermediateCollections[0]; 534 // remove current collection from intermediates 535 intermediateCollections = (String[]) ArrayUtils.removeElement(intermediateCollections, collectionName); 536 try { 537 referenceCollection = (Collection) PropertyUtils.getProperty(bo, collectionName); 538 } catch (Exception e) { 539 throw new RuntimeException(e); 540 } 541 int pos = 0; 542 Iterator<PersistableBusinessObject> iterator = referenceCollection.iterator(); 543 while (iterator.hasNext()) { 544 String pathToAttribute = 545 StringUtils.defaultString(pathToAttributeI) + collectionName + "[" + (pos++) + "]."; 546 // keep drilling down until we reach the nested collection we want 547 if (intermediateCollections.length > 0) { 548 success &= validateCollectionReferenceExistsAndIsActive(iterator.next(), reference, displayFieldName, 549 intermediateCollections, pathToAttribute); 550 } else { 551 String attributeToHighlightOnFail = pathToAttribute + reference.getAttributeToHighlightOnFail(); 552 success &= validateReferenceExistsAndIsActive(iterator.next(), reference.getAttributeName(), 553 attributeToHighlightOnFail, displayFieldName); 554 } 555 } 556 557 return success; 558 } 559 560 /** 561 * @see org.kuali.rice.krad.service.DictionaryValidationService#validateReferenceExistsAndIsActive(org.kuali.rice.krad.bo.BusinessObject, String, String, String) 562 */ 563 564 public boolean validateReferenceExistsAndIsActive(BusinessObject bo, String referenceName, 565 String attributeToHighlightOnFail, String displayFieldName) { 566 567 // if we're dealing with a nested attribute, we need to resolve down to the BO where the primitive attribute is located 568 // this is primarily to deal with the case of a defaultExistenceCheck that uses an "extension", i.e referenceName 569 // would be extension.attributeName 570 if (ObjectUtils.isNestedAttribute(referenceName)) { 571 String nestedAttributePrefix = ObjectUtils.getNestedAttributePrefix(referenceName); 572 String nestedAttributePrimitive = ObjectUtils.getNestedAttributePrimitive(referenceName); 573 Object nestedObject = ObjectUtils.getPropertyValue(bo, nestedAttributePrefix); 574 if (!(nestedObject instanceof BusinessObject)) { 575 throw new ObjectNotABusinessObjectRuntimeException( 576 "Attribute requested (" + nestedAttributePrefix + ") is of class: " + "'" + 577 nestedObject.getClass().getName() + "' and is not a " + 578 "descendent of BusinessObject."); 579 } 580 return validateReferenceExistsAndIsActive((BusinessObject) nestedObject, nestedAttributePrimitive, 581 attributeToHighlightOnFail, displayFieldName); 582 } 583 584 boolean success = true; 585 boolean exists; 586 boolean active; 587 588 boolean fkFieldsPopulated = true; 589 // need to check for DD relationship FKs 590 List<String> fkFields = 591 getDataDictionaryService().getRelationshipSourceAttributes(bo.getClass().getName(), referenceName); 592 if (fkFields != null) { 593 for (String fkFieldName : fkFields) { 594 Object fkFieldValue = null; 595 try { 596 fkFieldValue = PropertyUtils.getProperty(bo, fkFieldName); 597 } 598 // if we cant retrieve the field value, then 599 // it doesnt have a value 600 catch (IllegalAccessException e) { 601 fkFieldsPopulated = false; 602 } catch (InvocationTargetException e) { 603 fkFieldsPopulated = false; 604 } catch (NoSuchMethodException e) { 605 fkFieldsPopulated = false; 606 } 607 608 // test the value 609 if (fkFieldValue == null) { 610 fkFieldsPopulated = false; 611 } else if (String.class.isAssignableFrom(fkFieldValue.getClass())) { 612 if (StringUtils.isBlank((String) fkFieldValue)) { 613 fkFieldsPopulated = false; 614 } 615 } 616 } 617 } else if (bo instanceof PersistableBusinessObject) { // if no DD relationship exists, check the persistence service 618 fkFieldsPopulated = persistenceService 619 .allForeignKeyValuesPopulatedForReference((PersistableBusinessObject) bo, referenceName); 620 } 621 622 // only bother if all the fk fields have values 623 if (fkFieldsPopulated) { 624 625 // do the existence test 626 exists = validateReferenceExists(bo, referenceName); 627 if (exists) { 628 629 // do the active test, if appropriate 630 if (!(bo instanceof MutableInactivatable) || ((MutableInactivatable) bo).isActive()) { 631 active = validateReferenceIsActive(bo, referenceName); 632 if (!active) { 633 GlobalVariables.getMessageMap() 634 .putError(attributeToHighlightOnFail, RiceKeyConstants.ERROR_INACTIVE, 635 displayFieldName); 636 success &= false; 637 } 638 } 639 } else { 640 GlobalVariables.getMessageMap() 641 .putError(attributeToHighlightOnFail, RiceKeyConstants.ERROR_EXISTENCE, displayFieldName); 642 success &= false; 643 } 644 } 645 return success; 646 } 647 648 /** 649 * @see org.kuali.rice.krad.service.DictionaryValidationService#validateDefaultExistenceChecks(org.kuali.rice.krad.bo.BusinessObject) 650 */ 651 public boolean validateDefaultExistenceChecks(BusinessObject bo) { 652 boolean success = true; 653 654 // get a collection of all the referenceDefinitions setup for this object 655 Collection references = getDocumentDictionaryService().getDefaultExistenceChecks(bo.getClass()); 656 657 // walk through the references, doing the tests on each 658 for (Iterator iter = references.iterator(); iter.hasNext(); ) { 659 ReferenceDefinition reference = (ReferenceDefinition) iter.next(); 660 661 // do the existence and validation testing 662 success &= validateReferenceExistsAndIsActive(bo, reference); 663 } 664 return success; 665 } 666 667 /** 668 * @see org.kuali.rice.krad.service.DictionaryValidationService#validateDefaultExistenceChecksForNewCollectionItem(org.kuali.rice.krad.bo.BusinessObject, 669 * org.kuali.rice.krad.bo.BusinessObject, java.lang.String) 670 */ 671 public boolean validateDefaultExistenceChecksForNewCollectionItem(BusinessObject bo, 672 BusinessObject newCollectionItem, String collectionName) { 673 boolean success = true; 674 675 if (StringUtils.isNotBlank(collectionName)) { 676 // get a collection of all the referenceDefinitions setup for this object 677 Collection references = getDocumentDictionaryService().getDefaultExistenceChecks(bo.getClass()); 678 679 // walk through the references, doing the tests on each 680 for (Iterator iter = references.iterator(); iter.hasNext(); ) { 681 ReferenceDefinition reference = (ReferenceDefinition) iter.next(); 682 if (collectionName != null && collectionName.equals(reference.getCollection())) { 683 String displayFieldName; 684 if (reference.isDisplayFieldNameSet()) { 685 displayFieldName = reference.getDisplayFieldName(); 686 } else { 687 Class boClass = 688 reference.isCollectionReference() ? reference.getCollectionBusinessObjectClass() : 689 bo.getClass(); 690 displayFieldName = dataDictionaryService 691 .getAttributeLabel(boClass, reference.getAttributeToHighlightOnFail()); 692 } 693 694 success &= validateReferenceExistsAndIsActive(newCollectionItem, reference.getAttributeName(), 695 reference.getAttributeToHighlightOnFail(), displayFieldName); 696 } 697 } 698 } 699 700 return success; 701 } 702 703 /** 704 * @see org.kuali.rice.krad.service.DictionaryValidationService#validateDefaultExistenceChecksForTransDoc(org.kuali.rice.krad.document.TransactionalDocument) 705 */ 706 public boolean validateDefaultExistenceChecksForTransDoc(TransactionalDocument document) { 707 boolean success = true; 708 709 // get a collection of all the referenceDefinitions setup for this object 710 Collection references = getDocumentDictionaryService().getDefaultExistenceChecks(document); 711 712 // walk through the references, doing the tests on each 713 for (Iterator iter = references.iterator(); iter.hasNext(); ) { 714 ReferenceDefinition reference = (ReferenceDefinition) iter.next(); 715 716 // do the existence and validation testing 717 success &= validateReferenceExistsAndIsActive(document, reference); 718 } 719 return success; 720 } 721 722 /** 723 * @see org.kuali.rice.krad.service.DictionaryValidationService#validateDefaultExistenceChecksForNewCollectionItem(org.kuali.rice.krad.document.TransactionalDocument, org.kuali.rice.krad.bo.BusinessObject, String) 724 */ 725 public boolean validateDefaultExistenceChecksForNewCollectionItem(TransactionalDocument document, 726 BusinessObject newCollectionItem, String collectionName) { 727 boolean success = true; 728 if (StringUtils.isNotBlank(collectionName)) { 729 // get a collection of all the referenceDefinitions setup for this object 730 Collection references = getDocumentDictionaryService().getDefaultExistenceChecks(document); 731 732 // walk through the references, doing the tests on each 733 for (Iterator iter = references.iterator(); iter.hasNext(); ) { 734 ReferenceDefinition reference = (ReferenceDefinition) iter.next(); 735 if (collectionName != null && collectionName.equals(reference.getCollection())) { 736 String displayFieldName; 737 if (reference.isDisplayFieldNameSet()) { 738 displayFieldName = reference.getDisplayFieldName(); 739 } else { 740 Class boClass = 741 reference.isCollectionReference() ? reference.getCollectionBusinessObjectClass() : 742 document.getClass(); 743 displayFieldName = dataDictionaryService 744 .getAttributeLabel(boClass, reference.getAttributeToHighlightOnFail()); 745 } 746 747 success &= validateReferenceExistsAndIsActive(newCollectionItem, reference.getAttributeName(), 748 reference.getAttributeToHighlightOnFail(), displayFieldName); 749 } 750 } 751 } 752 return success; 753 } 754 755 /* 756 * 1.1 validation methods 757 */ 758 759 /* 760 * This is the top-level validation method for all attribute value readers 761 */ 762 public DictionaryValidationResult validate(AttributeValueReader valueReader, boolean doOptionalProcessing) { 763 DictionaryValidationResult result = new DictionaryValidationResult(); 764 765 if (valueReader.getAttributeName() == null) { 766 validateObject(result, valueReader, doOptionalProcessing, true); 767 } else { 768 validateAttribute(result, valueReader, doOptionalProcessing); 769 } 770 771 if (result.getNumberOfErrors() > 0) { 772 for (Iterator<ConstraintValidationResult> iterator = result.iterator(); iterator.hasNext(); ) { 773 ConstraintValidationResult constraintValidationResult = iterator.next(); 774 if (constraintValidationResult.getStatus().getLevel() >= ErrorLevel.WARN.getLevel()){ 775 String attributePath = constraintValidationResult.getAttributePath(); 776 if (attributePath == null || attributePath.isEmpty()){ 777 attributePath = constraintValidationResult.getAttributeName(); 778 } 779 if(constraintValidationResult.getConstraintLabelKey() != null){ 780 GlobalVariables.getMessageMap().putError(attributePath, 781 constraintValidationResult.getConstraintLabelKey(), 782 constraintValidationResult.getErrorParameters()); 783 } 784 else{ 785 GlobalVariables.getMessageMap().putError(attributePath, 786 constraintValidationResult.getErrorKey(), 787 constraintValidationResult.getErrorParameters()); 788 } 789 } 790 } 791 } 792 793 return result; 794 } 795 796 private void processElementConstraints(DictionaryValidationResult result, Object value, Constrainable definition, 797 AttributeValueReader attributeValueReader, boolean doOptionalProcessing) { 798 processConstraints(result, elementConstraintProcessors, value, definition, attributeValueReader, 799 doOptionalProcessing); 800 } 801 802 private void processCollectionConstraints(DictionaryValidationResult result, Collection<?> collection, 803 Constrainable definition, AttributeValueReader attributeValueReader, boolean doOptionalProcessing) { 804 processConstraints(result, collectionConstraintProcessors, collection, definition, attributeValueReader, 805 doOptionalProcessing); 806 } 807 808 @SuppressWarnings("unchecked") 809 private void processConstraints(DictionaryValidationResult result, 810 List<? extends ConstraintProcessor> constraintProcessors, Object value, Constrainable definition, 811 AttributeValueReader attributeValueReader, boolean doOptionalProcessing) { 812 //TODO: Implement custom validators 813 814 if (constraintProcessors != null) { 815 Constrainable selectedDefinition = definition; 816 AttributeValueReader selectedAttributeValueReader = attributeValueReader; 817 818 // First - take the constrainable definition and get its constraints 819 820 Queue<Constraint> constraintQueue = new LinkedList<Constraint>(); 821 822 // Using a for loop to iterate through constraint processors because ordering is important 823 for (ConstraintProcessor<Object, Constraint> processor : constraintProcessors) { 824 825 // Let the calling method opt out of any optional processing 826 if (!doOptionalProcessing && processor.isOptional()) { 827 result.addSkipped(attributeValueReader, processor.getName()); 828 continue; 829 } 830 831 Class<? extends Constraint> constraintType = processor.getConstraintType(); 832 833 // Add all of the constraints for this constraint type for all providers to the queue 834 for (ConstraintProvider constraintProvider : constraintProviders) { 835 if (constraintProvider.isSupported(selectedDefinition)) { 836 Collection<Constraint> constraintList = 837 constraintProvider.getConstraints(selectedDefinition, constraintType); 838 if (constraintList != null) 839 constraintQueue.addAll(constraintList); 840 } 841 } 842 843 // If there are no constraints provided for this definition, then just skip it 844 if (constraintQueue.isEmpty()) { 845 result.addSkipped(attributeValueReader, processor.getName()); 846 continue; 847 } 848 849 Collection<Constraint> additionalConstraints = new LinkedList<Constraint>(); 850 851 // This loop is functionally identical to a for loop, but it has the advantage of letting us keep the queue around 852 // and populate it with any new constraints contributed by the processor 853 while (!constraintQueue.isEmpty()) { 854 855 Constraint constraint = constraintQueue.poll(); 856 857 // If this constraint is not one that this process handles, then skip and add to the queue for the next processor; 858 // obviously this would be redundant (we're only looking at constraints that this processor can process) except that 859 // the previous processor might have stuck a new constraint (or constraints) on the queue 860 if (!constraintType.isInstance(constraint)) { 861 result.addSkipped(attributeValueReader, processor.getName()); 862 additionalConstraints.add(constraint); 863 continue; 864 } 865 866 ProcessorResult processorResult = 867 processor.process(result, value, constraint, selectedAttributeValueReader); 868 869 Collection<Constraint> processorResultContraints = processorResult.getConstraints(); 870 if (processorResultContraints != null && processorResultContraints.size() > 0) 871 constraintQueue.addAll(processorResultContraints); 872 873 // Change the selected definition to whatever was returned from the processor 874 if (processorResult.isDefinitionProvided()) 875 selectedDefinition = processorResult.getDefinition(); 876 // Change the selected attribute value reader to whatever was returned from the processor 877 if (processorResult.isAttributeValueReaderProvided()) 878 selectedAttributeValueReader = processorResult.getAttributeValueReader(); 879 880 } 881 882 // After iterating through all the constraints for this processor, add the ones that werent consumed by this processor to the queue 883 constraintQueue.addAll(additionalConstraints); 884 } 885 } 886 } 887 888 private void setFieldError(String entryName, String attributeName, String key, String... args) { 889 if (getDataDictionaryService() == null) 890 return; 891 892 String errorLabel = getDataDictionaryService().getAttributeErrorLabel(entryName, attributeName); 893 // FIXME: There's got to be a cleaner way of doing this. 894 List<String> list = new LinkedList<String>(); 895 list.add(errorLabel); 896 list.addAll(Arrays.asList(args)); 897 String[] array = new String[list.size()]; 898 array = list.toArray(array); 899 GlobalVariables.getMessageMap().putError(attributeName, key, array); 900 } 901 902 private void validateAttribute(DictionaryValidationResult result, AttributeValueReader attributeValueReader, 903 boolean checkIfRequired) throws AttributeValidationException { 904 Constrainable definition = attributeValueReader.getDefinition(attributeValueReader.getAttributeName()); 905 validateAttribute(result, definition, attributeValueReader, checkIfRequired); 906 } 907 908 private void validateAttribute(DictionaryValidationResult result, Constrainable definition, 909 AttributeValueReader attributeValueReader, boolean checkIfRequired) throws AttributeValidationException { 910 911 if (definition == null) 912 throw new AttributeValidationException( 913 "Unable to validate constraints for attribute \"" + attributeValueReader.getAttributeName() + 914 "\" on entry \"" + attributeValueReader.getEntryName() + 915 "\" because no attribute definition can be found."); 916 917 Object value = attributeValueReader.getValue(); 918 919 processElementConstraints(result, value, definition, attributeValueReader, checkIfRequired); 920 } 921 922 private void validateObject(DictionaryValidationResult result, AttributeValueReader attributeValueReader, 923 boolean doOptionalProcessing, boolean processAttributes) throws AttributeValidationException { 924 925 // If the entry itself is constrainable then the attribute value reader will return it here and we'll need to check if it has any constraints 926 Constrainable objectEntry = attributeValueReader.getEntry(); 927 processElementConstraints(result, attributeValueReader.getObject(), objectEntry, attributeValueReader, 928 doOptionalProcessing); 929 930 List<Constrainable> definitions = attributeValueReader.getDefinitions(); 931 932 // Exit if the attribute value reader has no child definitions 933 if (null == definitions) 934 return; 935 936 //Process all attribute definitions (unless being skipped) 937 if (processAttributes){ 938 for (Constrainable definition : definitions) { 939 String attributeName = definition.getName(); 940 attributeValueReader.setAttributeName(attributeName); 941 942 if (attributeValueReader.isReadable()) { 943 Object value = attributeValueReader.getValue(attributeName); 944 945 processElementConstraints(result, value, definition, attributeValueReader, doOptionalProcessing); 946 } 947 } 948 } 949 950 //Process any constraints that may be defined on complex attributes 951 if (objectEntry instanceof DataDictionaryEntryBase) { 952 List<ComplexAttributeDefinition> complexAttrDefinitions = 953 ((DataDictionaryEntryBase) objectEntry).getComplexAttributes(); 954 955 if (complexAttrDefinitions != null) { 956 for (ComplexAttributeDefinition complexAttrDefinition : complexAttrDefinitions) { 957 String attributeName = complexAttrDefinition.getName(); 958 attributeValueReader.setAttributeName(attributeName); 959 960 if (attributeValueReader.isReadable()) { 961 Object value = attributeValueReader.getValue(); 962 963 DataDictionaryEntry childEntry = complexAttrDefinition.getDataObjectEntry(); 964 if (value != null) { 965 AttributeValueReader nestedAttributeValueReader = new DictionaryObjectAttributeValueReader( 966 value, childEntry.getFullClassName(), childEntry, attributeValueReader.getPath()); 967 nestedAttributeValueReader.setAttributeName(attributeValueReader.getAttributeName()); 968 //Validate nested object, however skip attribute definition porcessing on 969 //nested object entry, since they have already been processed above. 970 validateObject(result, nestedAttributeValueReader, doOptionalProcessing, false); 971 } 972 973 processElementConstraints(result, value, complexAttrDefinition, attributeValueReader, 974 doOptionalProcessing); 975 } 976 } 977 } 978 } 979 980 //FIXME: I think we may want to use a new CollectionConstrainable interface instead to obtain from 981 //DictionaryObjectAttributeValueReader 982 DataObjectEntry entry = (DataObjectEntry) attributeValueReader.getEntry(); 983 if (entry != null) { 984 for (CollectionDefinition collectionDefinition : entry.getCollections()) { 985 //TODO: Do we need to be able to handle simple collections (ie. String, etc) 986 987 String childEntryName = collectionDefinition.getDataObjectClass(); 988 String attributeName = collectionDefinition.getName(); 989 attributeValueReader.setAttributeName(attributeName); 990 991 if (attributeValueReader.isReadable()) { 992 Collection<?> collectionObject = attributeValueReader.getValue(); 993 DataDictionaryEntry childEntry = childEntryName != null ? 994 getDataDictionaryService().getDataDictionary().getDictionaryObjectEntry(childEntryName) : 995 null; 996 if (collectionObject != null) { 997 int index = 0; 998 for (Object value : collectionObject) { 999 //NOTE: This path is only correct for collections that guarantee order 1000 String objectAttributePath = attributeValueReader.getPath() + "[" + index + "]"; 1001 1002 //FIXME: It's inefficient to be creating new attribute reader for each item in collection 1003 AttributeValueReader nestedAttributeValueReader = new DictionaryObjectAttributeValueReader( 1004 value, childEntryName, childEntry, objectAttributePath); 1005 validateObject(result, nestedAttributeValueReader, doOptionalProcessing, true); 1006 index++; 1007 } 1008 } 1009 1010 processCollectionConstraints(result, collectionObject, collectionDefinition, attributeValueReader, 1011 doOptionalProcessing); 1012 } 1013 } 1014 } 1015 } 1016 1017 /** 1018 * @return Returns the dataDictionaryService. 1019 */ 1020 public DataDictionaryService getDataDictionaryService() { 1021 return dataDictionaryService; 1022 } 1023 1024 /** 1025 * @param dataDictionaryService The dataDictionaryService to set. 1026 */ 1027 public void setDataDictionaryService(DataDictionaryService dataDictionaryService) { 1028 this.dataDictionaryService = dataDictionaryService; 1029 } 1030 1031 /** 1032 * Sets the businessObjectService attribute value. 1033 * 1034 * @param businessObjectService The businessObjectService to set. 1035 */ 1036 public void setBusinessObjectService(BusinessObjectService businessObjectService) { 1037 this.businessObjectService = businessObjectService; 1038 } 1039 1040 /** 1041 * Sets the persistenceService attribute value. 1042 * 1043 * @param persistenceService The persistenceService to set. 1044 */ 1045 public void setPersistenceService(PersistenceService persistenceService) { 1046 this.persistenceService = persistenceService; 1047 } 1048 1049 public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) { 1050 this.persistenceStructureService = persistenceStructureService; 1051 } 1052 1053 protected WorkflowAttributePropertyResolutionService getWorkflowAttributePropertyResolutionService() { 1054 if (workflowAttributePropertyResolutionService == null) { 1055 workflowAttributePropertyResolutionService = 1056 KRADServiceLocatorInternal.getWorkflowAttributePropertyResolutionService(); 1057 } 1058 return workflowAttributePropertyResolutionService; 1059 } 1060 1061 /** 1062 * @return the collectionConstraintProcessors 1063 */ 1064 @SuppressWarnings("unchecked") 1065 public List<CollectionConstraintProcessor> getCollectionConstraintProcessors() { 1066 return this.collectionConstraintProcessors; 1067 } 1068 1069 /** 1070 * @param collectionConstraintProcessors the collectionConstraintProcessors to set 1071 */ 1072 @SuppressWarnings("unchecked") 1073 public void setCollectionConstraintProcessors(List<CollectionConstraintProcessor> collectionConstraintProcessors) { 1074 this.collectionConstraintProcessors = collectionConstraintProcessors; 1075 } 1076 1077 /** 1078 * @return the constraintProviders 1079 */ 1080 @SuppressWarnings("unchecked") 1081 public List<ConstraintProvider> getConstraintProviders() { 1082 return this.constraintProviders; 1083 } 1084 1085 /** 1086 * @param constraintProviders the constraintProviders to set 1087 */ 1088 @SuppressWarnings("unchecked") 1089 public void setConstraintProviders(List<ConstraintProvider> constraintProviders) { 1090 this.constraintProviders = constraintProviders; 1091 } 1092 1093 /** 1094 * @return the elementConstraintProcessors 1095 */ 1096 @SuppressWarnings("unchecked") 1097 public List<ConstraintProcessor> getElementConstraintProcessors() { 1098 return this.elementConstraintProcessors; 1099 } 1100 1101 /** 1102 * @param elementConstraintProcessors the elementConstraintProcessors to set 1103 */ 1104 @SuppressWarnings("unchecked") 1105 public void setElementConstraintProcessors(List<ConstraintProcessor> elementConstraintProcessors) { 1106 this.elementConstraintProcessors = elementConstraintProcessors; 1107 } 1108 1109 public DocumentDictionaryService getDocumentDictionaryService() { 1110 if (documentDictionaryService == null) { 1111 this.documentDictionaryService = KRADServiceLocatorWeb.getDocumentDictionaryService(); 1112 } 1113 return documentDictionaryService; 1114 } 1115 1116 public void setDocumentDictionaryService(DocumentDictionaryService documentDictionaryService) { 1117 this.documentDictionaryService = documentDictionaryService; 1118 } 1119 }