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 }