View Javadoc

1   /**
2    * Copyright 2005-2013 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.service.impl;
17  
18  import java.beans.PropertyDescriptor;
19  import java.lang.reflect.InvocationTargetException;
20  import java.util.ArrayList;
21  import java.util.Collection;
22  import java.util.Collections;
23  import java.util.Date;
24  import java.util.HashMap;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.TreeMap;
29  
30  import org.apache.commons.beanutils.PropertyUtils;
31  import org.apache.commons.lang.StringUtils;
32  import org.kuali.rice.core.api.config.property.ConfigurationService;
33  import org.kuali.rice.core.api.criteria.OrderByField;
34  import org.kuali.rice.core.api.criteria.OrderDirection;
35  import org.kuali.rice.core.api.criteria.Predicate;
36  import org.kuali.rice.core.api.criteria.PredicateFactory;
37  import org.kuali.rice.core.api.criteria.QueryByCriteria;
38  import org.kuali.rice.core.api.criteria.QueryResults;
39  import org.kuali.rice.core.api.mo.common.Versioned;
40  import org.kuali.rice.core.api.search.SearchOperator;
41  import org.kuali.rice.core.api.uif.RemotableQuickFinder;
42  import org.kuali.rice.core.api.util.RiceKeyConstants;
43  import org.kuali.rice.core.framework.persistence.ojb.conversion.OjbCharBooleanConversion;
44  import org.kuali.rice.krad.bo.Attachment;
45  import org.kuali.rice.krad.bo.BusinessObject;
46  import org.kuali.rice.krad.bo.InactivatableFromTo;
47  import org.kuali.rice.krad.bo.PersistableBusinessObject;
48  import org.kuali.rice.krad.bo.PersistableBusinessObjectExtension;
49  import org.kuali.rice.krad.data.CompoundKey;
50  import org.kuali.rice.krad.data.DataObjectService;
51  import org.kuali.rice.krad.data.DataObjectUtils;
52  import org.kuali.rice.krad.data.DataObjectWrapper;
53  import org.kuali.rice.krad.data.PersistenceOption;
54  import org.kuali.rice.krad.data.metadata.DataObjectAttributeRelationship;
55  import org.kuali.rice.krad.data.metadata.DataObjectCollection;
56  import org.kuali.rice.krad.data.metadata.DataObjectMetadata;
57  import org.kuali.rice.krad.data.metadata.DataObjectRelationship;
58  import org.kuali.rice.krad.data.metadata.MetadataRepository;
59  import org.kuali.rice.krad.data.provider.annotation.ExtensionFor;
60  import org.kuali.rice.krad.datadictionary.DataDictionaryEntry;
61  import org.kuali.rice.krad.datadictionary.DataObjectEntry;
62  import org.kuali.rice.krad.datadictionary.PrimitiveAttributeDefinition;
63  import org.kuali.rice.krad.datadictionary.RelationshipDefinition;
64  import org.kuali.rice.krad.datadictionary.SupportAttributeDefinition;
65  import org.kuali.rice.krad.document.Document;
66  import org.kuali.rice.krad.exception.ValidationException;
67  import org.kuali.rice.krad.lookup.LookupUtils;
68  import org.kuali.rice.krad.maintenance.MaintenanceLock;
69  import org.kuali.rice.krad.service.DataDictionaryService;
70  import org.kuali.rice.krad.service.DocumentAdHocService;
71  import org.kuali.rice.krad.service.KRADServiceLocator;
72  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
73  import org.kuali.rice.krad.service.KualiModuleService;
74  import org.kuali.rice.krad.service.LegacyDataAdapter;
75  import org.kuali.rice.krad.service.ModuleService;
76  import org.kuali.rice.krad.uif.service.ViewDictionaryService;
77  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
78  import org.kuali.rice.krad.util.ForeignKeyFieldsPopulationState;
79  import org.kuali.rice.krad.util.GlobalVariables;
80  import org.kuali.rice.krad.util.KRADConstants;
81  import org.kuali.rice.krad.util.KRADPropertyConstants;
82  import org.kuali.rice.krad.util.KRADUtils;
83  import org.kuali.rice.krad.util.LegacyUtils;
84  import org.springframework.beans.factory.annotation.Required;
85  import org.springframework.dao.IncorrectResultSizeDataAccessException;
86  
87  /**
88   *
89   */
90  public class KRADLegacyDataAdapterImpl implements LegacyDataAdapter {
91      private DataObjectService dataObjectService;
92      private MetadataRepository metadataRepository;
93      private LookupCriteriaGenerator lookupCriteriaGenerator;
94  
95      private ConfigurationService kualiConfigurationService;
96      private KualiModuleService kualiModuleService;
97      private DataDictionaryService dataDictionaryService;
98      private ViewDictionaryService viewDictionaryService;
99  
100     @Override
101     public <T> T save(T dataObject) {
102         if (dataObject instanceof Collection) {
103             Collection<Object> newList = new ArrayList<Object>(((Collection) dataObject).size());
104             for (Object obj : (Collection<?>) dataObject) {
105                 newList.add(save(obj));
106             }
107             return (T) newList;
108         } else {
109             return dataObjectService.save(dataObject);
110         }
111     }
112 
113     @Override
114     public <T> T linkAndSave(T dataObject) {
115         // This method is only used from MaintainableImpl
116         return dataObjectService.save(dataObject, PersistenceOption.LINK);
117     }
118 
119     @Override
120     public <T> T saveDocument(T document) {
121         return dataObjectService.save(document, PersistenceOption.FLUSH);
122     }
123 
124     @Override
125     public <T> T findByPrimaryKey(Class<T> clazz, Map<String, ?> primaryKeys) {
126         return dataObjectService.find(clazz, new CompoundKey(primaryKeys));
127     }
128 
129     @Override
130     public <T> T findBySinglePrimaryKey(Class<T> clazz, Object primaryKey) {
131         return dataObjectService.find(clazz, primaryKey);
132     }
133 
134     @Override
135     public void delete(Object dataObject) {
136         if (dataObject instanceof Collection) {
137             for (Object dobj : (Collection) dataObject) {
138                 delete(dobj);
139             }
140         } else {
141             dataObjectService.delete(dataObject);
142         }
143     }
144 
145     @Override
146     public void deleteMatching(Class<?> type, Map<String, ?> fieldValues) {
147         dataObjectService.deleteMatching(type, QueryByCriteria.Builder.andAttributes(fieldValues).build());
148     }
149 
150     @Override
151     public <T> T retrieve(T dataObject) {
152         Object id = null;
153         Map<String, Object> primaryKeyValues = dataObjectService.wrap(dataObject).getPrimaryKeyValues();
154         if (primaryKeyValues.isEmpty()) {
155             throw new IllegalArgumentException("Given data object has no primary key!");
156         }
157         if (primaryKeyValues.size() == 1) {
158             id = primaryKeyValues.values().iterator().next();
159         } else {
160             id = new CompoundKey(primaryKeyValues);
161         }
162         return dataObjectService.find((Class<T>) dataObject.getClass(), id);
163     }
164 
165     @Override
166     public <T> Collection<T> findAll(Class<T> clazz) {
167         // just find all objects of given type without any attribute criteria
168         return findMatching(clazz, Collections.<String, Object>emptyMap());
169     }
170 
171     @Override
172     public <T> Collection<T> findMatching(Class<T> clazz, Map<String, ?> fieldValues) {
173         QueryResults<T> result = dataObjectService.findMatching(clazz, QueryByCriteria.Builder.andAttributes(
174                 fieldValues).build());
175         return result.getResults();
176     }
177 
178     @Override
179     public <T> Collection<T> findMatchingOrderBy(Class<T> clazz, Map<String, ?> fieldValues, String sortField,
180             boolean sortAscending) {
181         OrderDirection direction = sortAscending ? OrderDirection.ASCENDING : OrderDirection.DESCENDING;
182         OrderByField orderBy = OrderByField.Builder.create(sortField, direction).build();
183         QueryResults<T> result = dataObjectService.findMatching(clazz,
184                 QueryByCriteria.Builder.andAttributes(fieldValues).setOrderByFields(orderBy).build());
185         return result.getResults();
186     }
187 
188     @Override
189     public Map<String, ?> getPrimaryKeyFieldValues(Object dataObject) {
190         return dataObjectService.wrap(dataObject).getPrimaryKeyValues();
191     }
192 
193     @Override
194     public void retrieveNonKeyFields(Object persistableObject) {
195         throw new UnsupportedOperationException("retrieveNonKeyFields not supported in KRAD");
196     }
197 
198     @Override
199     public void retrieveReferenceObject(Object persistableObject, String referenceObjectName) {
200         throw new UnsupportedOperationException("retrieveReferenceObject not supported in KRAD");
201     }
202 
203     @Override
204     public void refreshAllNonUpdatingReferences(Object persistableObject) {
205         throw new UnsupportedOperationException("refreshAllNonUpdatingReferences not supported in KRAD");
206     }
207 
208     @Override
209     public boolean isProxied(Object object) {
210         // KRAD data adapter does nothing
211         return false;
212     }
213 
214     @Override
215     public Object resolveProxy(Object o) {
216         // KRAD data adapter does nothing
217         return o;
218     }
219 
220     // Lookup methods
221 
222     @Override
223     public <T> Collection<T> findCollectionBySearchHelper(Class<T> dataObjectClass, Map<String, String> formProperties,
224             boolean unbounded, boolean allPrimaryKeyValuesPresentAndNotWildcard, Integer searchResultsLimit) {
225         return performDataObjectServiceLookup(dataObjectClass, formProperties, unbounded,
226                 allPrimaryKeyValuesPresentAndNotWildcard, searchResultsLimit);
227     }
228 
229     @Override
230     public <T> Collection<T> findCollectionBySearchHelper(Class<T> dataObjectClass, Map<String, String> formProperties,
231             List<String> wildcardAsLiteralPropertyNames, boolean unbounded, boolean allPrimaryKeyValuesPresentAndNotWildcard,
232             Integer searchResultsLimit) {
233         return performDataObjectServiceLookup(dataObjectClass, formProperties, wildcardAsLiteralPropertyNames, unbounded,
234                 allPrimaryKeyValuesPresentAndNotWildcard, searchResultsLimit);
235     }
236 
237     /**
238      * Our new DataObjectService-based lookup implementation
239      *
240      * @param dataObjectClass the dataobject class
241      * @param formProperties the incoming lookup form properties
242      * @param unbounded whether the search is unbounded
243      * @param searchResultsLimit the searchResultsLimit; null implies use of default KNS value if set for the class
244      * @param <T> the data object type
245      * @return collection of lookup results
246      */
247     protected <T> Collection<T> performDataObjectServiceLookup(Class<T> dataObjectClass,
248             Map<String, String> formProperties, boolean unbounded, boolean allPrimaryKeyValuesPresentAndNotWildcard,
249             Integer searchResultsLimit) {
250         if (!unbounded && searchResultsLimit == null) {
251             // use KRAD LookupUtils.getSearchResultsLimit instead of KNS version. we have no LookupForm, so pass null, only the class will be used
252             //searchResultsLimit = LookupUtils.getSearchResultsLimit(example, null);
253             searchResultsLimit = org.kuali.rice.kns.lookup.LookupUtils.getSearchResultsLimit(dataObjectClass);
254         }
255         QueryByCriteria.Builder query = lookupCriteriaGenerator.generateCriteria(dataObjectClass, formProperties,
256                 allPrimaryKeyValuesPresentAndNotWildcard);
257         if (!unbounded && searchResultsLimit != null) {
258             query.setMaxResults(searchResultsLimit);
259         }
260 
261         Collection<T> results = dataObjectService.findMatching(dataObjectClass, query.build()).getResults();
262         return filterCurrentDataObjects(dataObjectClass, results, formProperties);
263     }
264 
265     /**
266      * Our newer DataObjectService-based lookup implementation
267      *
268      * @param dataObjectClass the dataobject class
269      * @param formProperties the incoming lookup form properties
270      * @param wildcardAsLiteralPropertyNames list of the lookup properties with wildcard characters disabled
271      * @param unbounded whether the search is unbounded
272      * @param searchResultsLimit the searchResultsLimit; null implies use of default KNS value if set for the class
273      * @param <T> the data object type
274      * @return collection of lookup results
275      */
276     protected <T> Collection<T> performDataObjectServiceLookup(Class<T> dataObjectClass,
277             Map<String, String> formProperties, List<String> wildcardAsLiteralPropertyNames,
278             boolean unbounded, boolean allPrimaryKeyValuesPresentAndNotWildcard, Integer searchResultsLimit) {
279         if (!unbounded && searchResultsLimit == null) {
280             // use KRAD LookupUtils.getSearchResultsLimit instead of KNS version. we have no LookupForm, so pass null, only the class will be used
281             //searchResultsLimit = LookupUtils.getSearchResultsLimit(example, null);
282             searchResultsLimit = org.kuali.rice.kns.lookup.LookupUtils.getSearchResultsLimit(dataObjectClass);
283         }
284 
285         QueryByCriteria.Builder query = lookupCriteriaGenerator.generateCriteria(dataObjectClass, formProperties,
286                 wildcardAsLiteralPropertyNames, allPrimaryKeyValuesPresentAndNotWildcard);
287         if (!unbounded && searchResultsLimit != null) {
288             query.setMaxResults(searchResultsLimit);
289         }
290 
291         Collection<T> results = dataObjectService.findMatching(dataObjectClass, query.build()).getResults();
292         return filterCurrentDataObjects(dataObjectClass, results, formProperties);
293     }
294 
295     protected <T> Collection<T> filterCurrentDataObjects(Class<T> dataObjectClass, Collection<T> unfiltered,
296             Map<String, String> formProps) {
297         if (InactivatableFromTo.class.isAssignableFrom(dataObjectClass)) {
298             Boolean currentSpecifier = lookupCriteriaCurrentSpecifier(formProps);
299             if (currentSpecifier != null) {
300                 List<InactivatableFromTo> onlyCurrent =
301                         KRADServiceLocator.getInactivateableFromToService().filterOutNonCurrent(new ArrayList(
302                                 unfiltered), new Date(LookupUtils.getActiveDateTimestampForCriteria(formProps)
303                                 .getTime()));
304                 if (currentSpecifier) {
305                     return (Collection<T>) onlyCurrent;
306                 } else {
307                     unfiltered.removeAll(onlyCurrent);
308                     return unfiltered;
309                 }
310             }
311         }
312         return unfiltered;
313     }
314 
315     protected Boolean lookupCriteriaCurrentSpecifier(Map<String, String> formProps) {
316         String value = formProps.get(KRADPropertyConstants.CURRENT);
317         if (StringUtils.isNotBlank(value)) {
318             // FIXME: use something more portable than this direct OJB converter
319             String currentBooleanStr = (String) new OjbCharBooleanConversion().javaToSql(value);
320             if (OjbCharBooleanConversion.DATABASE_BOOLEAN_TRUE_STRING_REPRESENTATION.equals(currentBooleanStr)) {
321                 return Boolean.TRUE;
322             } else if (OjbCharBooleanConversion.DATABASE_BOOLEAN_FALSE_STRING_REPRESENTATION.equals(
323                     currentBooleanStr)) {
324                 return Boolean.FALSE;
325             }
326         }
327         return null;
328     }
329 
330     @Override
331     public <T> T findObjectBySearch(Class<T> type, Map<String, String> formProps) {
332         // This is the strictly Lookup-compatible way of constructing the criteria
333         // from a map of properties, which performs some minor logic such as only including
334         // non-blank and "writable" properties, as well as minor type coercions of string values
335         QueryByCriteria.Builder queryByCriteria = lookupCriteriaGenerator.createObjectCriteriaFromMap(type, formProps);
336         List<T> results = dataObjectService.findMatching(type, queryByCriteria.build()).getResults();
337         if (results.isEmpty()) {
338             return null;
339         }
340         if (results.size() != 1) {
341             // this behavior is different from the legacy OJB behavior in that it throws an exception if more than
342             // one result from such a single object query
343             throw new IncorrectResultSizeDataAccessException("Incorrect number of results returned when finding object",
344                     1, results.size());
345         }
346         return results.get(0);
347     }
348 
349     /**
350      * Returns whether all primary key values are specified in the lookup  map and do not contain any wildcards
351      *
352      * @param boClass the bo class to lookup
353      * @param formProps the incoming form/lookup properties
354      * @return whether all primary key values are specified in the lookup  map and do not contain any wildcards
355      */
356     @Override
357     public boolean allPrimaryKeyValuesPresentAndNotWildcard(Class<?> boClass, Map<String, String> formProps) {
358         List<String> pkFields = listPrimaryKeyFieldNames(boClass);
359         Iterator<String> pkIter = pkFields.iterator();
360         boolean returnVal = true;
361         while (returnVal && pkIter.hasNext()) {
362             String pkName = pkIter.next();
363             String pkValue = formProps.get(pkName);
364 
365             if (StringUtils.isBlank(pkValue)) {
366                 returnVal = false;
367             } else {
368                 for (SearchOperator op : SearchOperator.QUERY_CHARACTERS) {
369                     if (pkValue.contains(op.op())) {
370                         returnVal = false;
371                         break;
372                     }
373                 }
374             }
375         }
376         return returnVal;
377     }
378 
379     @Override
380     public Attachment getAttachmentByNoteId(Long noteId) {
381         // noteIdentifier is the PK of Attachment, so just look up by PK
382         return dataObjectService.find(Attachment.class, noteId);
383     }
384 
385     @Override
386     public void deleteLocks(String documentNumber) {
387         dataObjectService.deleteMatching(MaintenanceLock.class, QueryByCriteria.Builder.forAttribute(
388                     "documentNumber", documentNumber).build());
389     }
390 
391     @Override
392     public String getLockingDocumentNumber(String lockingRepresentation, String documentNumber) {
393         String lockingDocNumber = "";
394 
395         // build the query criteria
396         List<Predicate> predicates = new ArrayList<Predicate>();
397         predicates.add(PredicateFactory.equal("lockingRepresentation", lockingRepresentation));
398 
399         // if a docHeaderId is specified, then it will be excluded from the
400         // locking representation test.
401         if (StringUtils.isNotBlank(documentNumber)) {
402             predicates.add(PredicateFactory.notEqual(KRADPropertyConstants.DOCUMENT_NUMBER, documentNumber));
403         }
404 
405         QueryByCriteria.Builder qbc = QueryByCriteria.Builder.create();
406         qbc.setPredicates(PredicateFactory.and(predicates.toArray(new Predicate[predicates.size()])));
407 
408         // attempt to retrieve a document based off this criteria
409         List<MaintenanceLock> results = dataObjectService.findMatching(MaintenanceLock.class, qbc.build())
410                 .getResults();
411         if (results.size() > 1) {
412             throw new IllegalStateException(
413                     "Expected single result querying for MaintenanceLock. LockRep: " + lockingRepresentation);
414         }
415 
416         // if a document was found, then there's already one out there pending,
417         // and we consider it 'locked' and we return the docnumber.
418         if (!results.isEmpty()) {
419             lockingDocNumber = results.get(0).getDocumentNumber();
420         }
421         return lockingDocNumber;
422     }
423 
424     @Override
425     public void storeLocks(List<MaintenanceLock> maintenanceLocks) {
426         if (maintenanceLocks.isEmpty()) {
427             return;
428         }
429         for (MaintenanceLock maintenanceLock : maintenanceLocks) {
430             dataObjectService.save(maintenanceLock);
431         }
432     }
433 
434     @Override
435     public List<String> listPrimaryKeyFieldNames(Class<?> type) {
436         List<String> keys = new ArrayList<String>();
437         if (metadataRepository.contains(type)) {
438             keys = metadataRepository.getMetadata(type).getPrimaryKeyAttributeNames();
439         }
440         return keys;
441     }
442 
443     /**
444      * LookupServiceImpl calls BusinessObjectMetaDataService to listPrimaryKeyFieldNames.
445      * The BusinessObjectMetaDataService goes beyond the PersistenceStructureService to consult
446      * the associated ModuleService in determining the primary key field names.
447      * TODO: Do we need both listPrimaryKeyFieldNames/persistenceStructureService and
448      * listPrimaryKeyFieldNamesConsultingAllServices/businesObjectMetaDataService or
449      * can the latter superset be used for the former?
450      *
451      * @param type the data object class
452      * @return list of primary key field names, consulting persistence structure service, module service and
453      *         datadictionary
454      */
455     protected List<String> listPrimaryKeyFieldNamesConsultingAllServices(Class<?> type) {
456         List<String> keys = new ArrayList<String>();
457         if (metadataRepository.contains(type)) {
458             keys = metadataRepository.getMetadata(type).getPrimaryKeyAttributeNames();
459         }
460         return keys;
461     }
462 
463     @Override
464     public Class<?> determineCollectionObjectType(Class<?> containingType, String collectionPropertyName) {
465         final Class<?> collectionObjectType;
466         if (metadataRepository.contains(containingType)) {
467             DataObjectMetadata metadata = metadataRepository.getMetadata(containingType);
468             DataObjectCollection collection = metadata.getCollection(collectionPropertyName);
469             if (collection == null) {
470                 throw new IllegalArgumentException(
471                         "Failed to locate a collection property with the given name: " + collectionPropertyName);
472             }
473             collectionObjectType = collection.getRelatedType();
474         } else {
475             throw new IllegalArgumentException(
476                     "Given containing class is not a valid data object, no metadata could be located for "
477                             + containingType.getName());
478         }
479         return collectionObjectType;
480 
481     }
482 
483     @Override
484     public boolean hasReference(Class<?> boClass, String referenceName) {
485         throw new UnsupportedOperationException("hasReference not valid for KRAD data operation");
486     }
487 
488     @Override
489     public boolean hasCollection(Class<?> boClass, String collectionName) {
490         throw new UnsupportedOperationException("hasCollection not valid for KRAD data operation");
491     }
492 
493     @Override
494     public boolean isExtensionAttribute(Class<?> boClass, String attributePropertyName, Class<?> propertyType) {
495         DataObjectMetadata metadata = metadataRepository.getMetadata(boClass);
496         if (metadata != null) {
497             DataObjectRelationship relationship = metadata.getRelationship(attributePropertyName);
498             if (relationship != null) {
499                 Class<?> relatedType = relationship.getRelatedType();
500                 // right now, the only way to tell if an attribute is an extension is to check this annotation, the
501                 // metadata repository does not currently store any such info that we can glom onto
502                 ExtensionFor annotation = relatedType.getAnnotation(ExtensionFor.class);
503                 if (annotation != null) {
504                     return annotation.value().equals(boClass);
505                 }
506             }
507         }
508         return false;
509     }
510 
511     @Override
512     public Class<?> getExtensionAttributeClass(Class<?> boClass, String attributePropertyName) {
513         DataObjectMetadata metadata = metadataRepository.getMetadata(boClass);
514         if (metadata != null) {
515             DataObjectRelationship relationship = metadata.getRelationship(attributePropertyName);
516             if (relationship != null) {
517                 return relationship.getRelatedType();
518             }
519         }
520         return null;
521     }
522 
523 
524     @Override
525     public Map<String, ?> getPrimaryKeyFieldValuesDOMDS(Object dataObject) {
526         return dataObjectService.wrap(dataObject).getPrimaryKeyValues();
527     }
528 
529     @Override
530     public boolean equalsByPrimaryKeys(Object do1, Object do2) {
531         return dataObjectService.wrap(do1).equalsByPrimaryKey(do2);
532     }
533 
534     @Override
535     public PersistableBusinessObject toPersistableBusinessObject(Object object) {
536         throw new UnsupportedOperationException("toPersistableBusinessObject not valid for KRAD data operation");
537     }
538 
539     @Override
540     public void materializeAllSubObjects(Object object) {
541         // for now, do nothing if this is not a legacy object, we'll eliminate the concept of materializing
542         // sub objects in this fashion in the new data layer, will enter a jira to re-examine this
543     }
544 
545     @Override
546     /**
547      * Recursively calls getPropertyTypeChild if nested property to allow it to properly look it up
548      */
549     public Class<?> getPropertyType(Object object, String propertyName) {
550         DataObjectWrapper wrappedObject = dataObjectService.wrap(object);
551         if(DataObjectUtils.isNestedAttribute(propertyName)){
552            return wrappedObject.getPropertyTypeNullSafe(wrappedObject.getWrappedClass(),propertyName);
553         }
554         return wrappedObject.getPropertyType(propertyName);
555     }
556 
557     @Override
558     public PersistableBusinessObjectExtension getExtension(
559             Class<? extends PersistableBusinessObject> businessObjectClass) throws InstantiationException, IllegalAccessException {
560         throw new UnsupportedOperationException("getExtension not supported in KRAD");
561     }
562 
563     @Override
564     public void refreshReferenceObject(PersistableBusinessObject businessObject, String referenceObjectName) {
565         throw new UnsupportedOperationException("refreshReferenceObject not supported in KRAD");
566     }
567 
568     @Override
569     public boolean isLockable(Object object) {
570         return isPersistable(object.getClass());
571     }
572 
573     @Override
574     public void verifyVersionNumber(Object dataObject) {
575         DataObjectMetadata metadata = metadataRepository.getMetadata(dataObject.getClass());
576         if (metadata == null) {
577             throw new IllegalArgumentException("Given data object class could not be loaded from metadata repository: "
578                 + dataObject.getClass());
579         }
580         if (metadata.isSupportsOptimisticLocking()) {
581             if (dataObject instanceof Versioned) {
582                 Map<String, ?> keyPropertyValues = dataObjectService.wrap(dataObject).getPrimaryKeyValues();
583                 Object persistableDataObject = dataObjectService.find(dataObject.getClass(), new CompoundKey(
584                         keyPropertyValues));
585                 // if it's null that means that this is an insert, not an update
586                 if (persistableDataObject != null) {
587                         Long databaseVersionNumber = ((Versioned) persistableDataObject).getVersionNumber();
588                         Long documentVersionNumber = ((Versioned) dataObject).getVersionNumber();
589                         if (databaseVersionNumber != null && !(databaseVersionNumber.equals(documentVersionNumber))) {
590                             GlobalVariables.getMessageMap().putError(KRADConstants.GLOBAL_ERRORS,
591                                     RiceKeyConstants.ERROR_VERSION_MISMATCH);
592                             throw new ValidationException(
593                                     "Version mismatch between the local business object and the database business object");
594                         }
595                     }
596                 }
597             }
598         }
599 
600         @Override
601         public RemotableQuickFinder.Builder createQuickFinder(Class<?> containingClass, String attributeName) {
602             return createQuickFinderNew(containingClass, attributeName);
603         }
604 
605         /**
606          * New implementation of createQuickFinder which uses the new MetadataRepository.
607          */
608         protected RemotableQuickFinder.Builder createQuickFinderNew(Class<?> containingClass, String attributeName) {
609             if (metadataRepository.contains(containingClass)) {
610 
611                 String lookupClassName = null;
612             Map<String, String> fieldConversions = new HashMap<String, String>();
613             Map<String, String> lookupParameters = new HashMap<String, String>();
614 
615             DataObjectMetadata metadata = metadataRepository.getMetadata(containingClass);
616             DataObjectRelationship relationship = metadata.getRelationshipByLastAttributeInRelationship(attributeName);
617             if (relationship != null) {
618                 DataObjectMetadata lookupClassMetadata = metadataRepository.getMetadata(
619                         relationship.getRelatedType());
620                 lookupClassName = lookupClassMetadata.getClass().getName();
621                 for (DataObjectAttributeRelationship attributeRelationship : relationship.getAttributeRelationships()) {
622 
623                     // for field conversions, we map from the child attribute name to the parent attribute name because
624                     // whenever the value is returned from the object being looked up (child in this case) we want to
625                     // map the result back to the corresponding attributes on the "parent" object
626                     fieldConversions.put(attributeRelationship.getChildAttributeName(),
627                             attributeRelationship.getParentAttributeName());
628 
629                     // for lookup parameters, we need to map the other direction since we are passing parameters *from* our parent
630                     // object *to* the child object
631                     lookupParameters.put(attributeRelationship.getParentAttributeName(),
632                             attributeRelationship.getChildAttributeName());
633                 }
634                 // in the legacy implementation of this, if there was a "userVisibleIdentifierKey" defined on
635                 // the relationship, it would only add the lookup parameter for that key
636                 //
637                 // In krad-data, we recognize that related objects have business keys and we use that information
638                 // to alter the lookup parameters (only) to pass the business key field(s) to the lookup
639                 if (lookupClassMetadata.hasDistinctBusinessKey()) {
640                     lookupParameters.clear();
641                     for (String businessKeyAttributeName : lookupClassMetadata.getBusinessKeyAttributeNames()) {
642                         lookupParameters.put(relationship.getName() + "." + businessKeyAttributeName,
643                                 businessKeyAttributeName);
644                     }
645                 }
646             } else {
647                 // check for primary display attribute attribute and if match build lookup to target class using primary key fields
648                 String primaryDisplayAttributeName = metadata.getPrimaryDisplayAttributeName();
649                 if (StringUtils.equals(primaryDisplayAttributeName, attributeName)) {
650                     lookupClassName = containingClass.getName();
651                     List<String> primaryKeyAttributes = metadata.getPrimaryKeyAttributeNames();
652                     for (String primaryKeyAttribute : primaryKeyAttributes) {
653                         fieldConversions.put(primaryKeyAttribute, primaryKeyAttribute);
654                         if (!StringUtils.equals(primaryKeyAttribute, attributeName)) {
655                             lookupParameters.put(primaryKeyAttribute, primaryKeyAttribute);
656                         }
657                     }
658                 }
659             }
660 
661             if (StringUtils.isNotBlank(lookupClassName)) {
662                 String baseUrl = kualiConfigurationService.getPropertyValueAsString(KRADConstants.KRAD_LOOKUP_URL_KEY);
663                 RemotableQuickFinder.Builder builder = RemotableQuickFinder.Builder.create(baseUrl, lookupClassName);
664                 builder.setLookupParameters(lookupParameters);
665                 builder.setFieldConversions(fieldConversions);
666                 return builder;
667             }
668 
669         }
670         return null;
671     }
672 
673     @Override
674     public boolean isReferenceUpdatable(Class<?> type, String referenceName) {
675         if (metadataRepository.contains(type)) {
676             DataObjectRelationship relationship = metadataRepository.getMetadata(type).getRelationship(referenceName);
677             if (relationship != null) {
678                 return relationship.isSavedWithParent();
679             }
680         }
681         return false;
682     }
683 
684     @SuppressWarnings("rawtypes")
685     @Override
686     public Map<String, Class> listReferenceObjectFields(Class<?> type) {
687         Map<String, Class> referenceNameToTypeMap = new HashMap<String, Class>();
688         if (metadataRepository.contains(type)) {
689             List<DataObjectRelationship> relationships = metadataRepository.getMetadata(type).getRelationships();
690             for (DataObjectRelationship rel : relationships) {
691                 referenceNameToTypeMap.put(rel.getName(), rel.getRelatedType());
692             }
693         }
694         return referenceNameToTypeMap;
695     }
696 
697     @Override
698     public boolean isCollectionUpdatable(Class<?> type, String collectionName) {
699         if (metadataRepository.contains(type)) {
700             DataObjectCollection collection = metadataRepository.getMetadata(type).getCollection(collectionName);
701             if (collection != null) {
702                 return collection.isSavedWithParent();
703             }
704         }
705         return false;
706     }
707 
708     @Override
709     public Map<String, Class> listCollectionObjectTypes(Class<?> type) {
710         Map<String, Class> collectionNameToTypeMap = new HashMap<String, Class>();
711         if (metadataRepository.contains(type)) {
712             List<DataObjectCollection> collections = metadataRepository.getMetadata(type).getCollections();
713             for (DataObjectCollection coll : collections) {
714                 collectionNameToTypeMap.put(coll.getName(), coll.getRelatedType());
715             }
716         }
717         return collectionNameToTypeMap;
718     }
719 
720     @Override
721     public BusinessObject getReferenceIfExists(BusinessObject bo, String referenceName) {
722         throw new UnsupportedOperationException("getReferenceIfExists not supported in KRAD");
723     }
724 
725     @Override
726     public boolean allForeignKeyValuesPopulatedForReference(PersistableBusinessObject bo, String referenceName) {
727         throw new UnsupportedOperationException("allForeignKeyValuesPopulatedForReference not supported in KRAD");
728     }
729 
730     /**
731      * gets the relationship that the attribute represents on the class
732      *
733      * @param c - the class to which the attribute belongs
734      * @param attributeName - property name for the attribute
735      * @return a relationship definition for the attribute
736      */
737     @Override
738     public RelationshipDefinition getDictionaryRelationship(Class<?> c, String attributeName) {
739         DataDictionaryEntry entryBase =
740                 KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getDictionaryObjectEntry(
741                         c.getName());
742         if (entryBase == null) {
743             return null;
744         }
745 
746         RelationshipDefinition relationship = null;
747 
748         List<RelationshipDefinition> ddRelationships = entryBase.getRelationships();
749 
750         int minKeys = Integer.MAX_VALUE;
751         for (RelationshipDefinition def : ddRelationships) {
752             // favor key sizes of 1 first
753             if (def.getPrimitiveAttributes().size() == 1) {
754                 for (PrimitiveAttributeDefinition primitive : def.getPrimitiveAttributes()) {
755                     if (primitive.getSourceName().equals(attributeName) || def.getObjectAttributeName().equals(
756                             attributeName)) {
757                         relationship = def;
758                         minKeys = 1;
759                         break;
760                     }
761                 }
762             } else if (def.getPrimitiveAttributes().size() < minKeys) {
763                 for (PrimitiveAttributeDefinition primitive : def.getPrimitiveAttributes()) {
764                     if (primitive.getSourceName().equals(attributeName) || def.getObjectAttributeName().equals(
765                             attributeName)) {
766                         relationship = def;
767                         minKeys = def.getPrimitiveAttributes().size();
768                         break;
769                     }
770                 }
771             }
772         }
773 
774         // check the support attributes
775         if (relationship == null) {
776             for (RelationshipDefinition def : ddRelationships) {
777                 if (def.hasIdentifier()) {
778                     if (def.getIdentifier().getSourceName().equals(attributeName)) {
779                         relationship = def;
780                     }
781                 }
782             }
783         }
784 
785         return relationship;
786     }
787 
788     /**
789      * @see org.kuali.rice.krad.service.LegacyDataAdapter
790      */
791     @Override
792     public String getTitleAttribute(Class<?> dataObjectClass) {
793         String titleAttribute = null;
794         DataObjectEntry entry = getDataObjectEntry(dataObjectClass);
795         if (entry != null) {
796             titleAttribute = entry.getTitleAttribute();
797         }
798         return titleAttribute;
799     }
800 
801     /**
802      * @param dataObjectClass
803      * @return DataObjectEntry for the given dataObjectClass, or null if
804      *         there is none
805      * @throws IllegalArgumentException if the given Class is null
806      */
807     protected DataObjectEntry getDataObjectEntry(Class<?> dataObjectClass) {
808         if (dataObjectClass == null) {
809             throw new IllegalArgumentException("invalid (null) dataObjectClass");
810         }
811 
812         DataObjectEntry entry = dataDictionaryService.getDataDictionary().getDataObjectEntry(dataObjectClass.getName());
813 
814         return entry;
815     }
816 
817     /**
818      * @see org.kuali.rice.krad.service.LegacyDataAdapter#areNotesSupported(java.lang.Class)
819      */
820     @Override
821     public boolean areNotesSupported(Class<?> dataObjectClass) {
822         boolean hasNotesSupport = false;
823 
824         DataObjectEntry entry = getDataObjectEntry(dataObjectClass);
825         if (entry != null) {
826             hasNotesSupport = entry.isBoNotesEnabled();
827         }
828 
829         return hasNotesSupport;
830     }
831 
832     /**
833      * Grabs primary key fields and sorts them if sort field names is true
834      *
835      * @param dataObject
836      * @param sortFieldNames
837      * @return Map of sorted primary key field values
838      */
839     public Map<String, ?> getPrimaryKeyFieldValues(Object dataObject, boolean sortFieldNames) {
840         Map<String, Object> keyFieldValues = (Map<String, Object>) getPrimaryKeyFieldValues(dataObject);
841         if (sortFieldNames) {
842             Map<String, Object> sortedKeyFieldValues = new TreeMap<String, Object>();
843             sortedKeyFieldValues.putAll(keyFieldValues);
844             return sortedKeyFieldValues;
845         }
846         return keyFieldValues;
847     }
848 
849     /**
850      * @see org.kuali.rice.krad.service.DataObjectMetaDataService#getDataObjectIdentifierString
851      */
852     @Override
853     public String getDataObjectIdentifierString(Object dataObject) {
854         String identifierString = "";
855 
856         if (dataObject == null) {
857             identifierString = "Null";
858             return identifierString;
859         }
860 
861         Class<?> dataObjectClass = dataObject.getClass();
862         // build identifier string from primary key values
863         Map<String, ?> primaryKeyFieldValues = getPrimaryKeyFieldValues(dataObject, true);
864         for (Map.Entry<String, ?> primaryKeyValue : primaryKeyFieldValues.entrySet()) {
865             if (primaryKeyValue.getValue() == null) {
866                 identifierString += "Null";
867             } else {
868                 identifierString += primaryKeyValue.getValue();
869             }
870             identifierString += ":";
871         }
872         return StringUtils.removeEnd(identifierString, ":");
873     }
874 
875     @Override
876     public Class<?> getInquiryObjectClassIfNotTitle(Object dataObject, String propertyName) {
877         DataObjectMetadata objectMetadata =
878                 KRADServiceLocator.getDataObjectService().getMetadataRepository().getMetadata(
879                         dataObject.getClass());
880         if(objectMetadata != null){
881             org.kuali.rice.krad.data.metadata.DataObjectRelationship dataObjectRelationship =
882                     objectMetadata.getRelationship(propertyName);
883             if (dataObjectRelationship != null) {
884                 return dataObjectRelationship.getRelatedType();
885             }
886         }
887         return null;
888     }
889 
890     @Override
891     public Map<String, String> getInquiryParameters(Object dataObject, List<String> keys, String propertyName) {
892         Map<String, String> inquiryParameters = new HashMap<String, String>();
893         DataObjectMetadata objectMetadata =
894                 KRADServiceLocator.getDataObjectService().getMetadataRepository().getMetadata(
895                         dataObject.getClass());
896         org.kuali.rice.krad.data.metadata.DataObjectRelationship dataObjectRelationship =
897                 objectMetadata.getRelationshipByLastAttributeInRelationship(propertyName);
898         for (String keyName : keys) {
899             String keyConversion = keyName;
900             if (dataObjectRelationship != null) {
901                 keyConversion = dataObjectRelationship.getParentAttributeNameRelatedToChildAttributeName(keyName);
902             } else if (DataObjectUtils.isNestedAttribute(propertyName)) {
903                 String nestedAttributePrefix = DataObjectUtils.getNestedAttributePrefix(propertyName);
904                 keyConversion = nestedAttributePrefix + "." + keyName;
905             }
906             inquiryParameters.put(keyConversion, keyName);
907         }
908         return inquiryParameters;
909     }
910 
911     @Override
912     public boolean hasLocalLookup(Class<?> dataObjectClass) {
913         return viewDictionaryService.isLookupable(dataObjectClass);
914     }
915 
916     @Override
917     public boolean hasLocalInquiry(Class<?> dataObjectClass) {
918         return viewDictionaryService.isInquirable(dataObjectClass);
919     }
920 
921     @Override
922     public org.kuali.rice.krad.bo.DataObjectRelationship getDataObjectRelationship(Object dataObject,
923             Class<?> dataObjectClass, String attributeName, String attributePrefix, boolean keysOnly,
924             boolean supportsLookup, boolean supportsInquiry) {
925         RelationshipDefinition ddReference = getDictionaryRelationship(dataObjectClass, attributeName);
926 
927         org.kuali.rice.krad.bo.DataObjectRelationship relationship = null;
928         DataObjectAttributeRelationship rel = null;
929         if (DataObjectUtils.isNestedAttribute(attributeName)) {
930             if (ddReference != null) {
931                 if (classHasSupportedFeatures(ddReference.getTargetClass(), supportsLookup, supportsInquiry)) {
932                     relationship = populateRelationshipFromDictionaryReference(dataObjectClass, ddReference,
933                             attributePrefix, keysOnly);
934 
935                     return relationship;
936                 }
937             }
938             // recurse down to the next object to find the relationship
939             String localPrefix = StringUtils.substringBefore(attributeName, ".");
940             String localAttributeName = StringUtils.substringAfter(attributeName, ".");
941             if (dataObject == null) {
942                 try {
943                     dataObject = KRADUtils.createNewObjectFromClass(dataObjectClass);
944                 } catch (RuntimeException e) {
945                     // found interface or abstract class, just swallow exception and return a null relationship
946                     return null;
947                 }
948             }
949 
950             Object nestedObject = ObjectPropertyUtils.getPropertyValue(dataObject, localPrefix);
951             Class<?> nestedClass = null;
952             if (nestedObject == null) {
953                 nestedClass = ObjectPropertyUtils.getPropertyType(dataObject, localPrefix);
954             } else {
955                 nestedClass = nestedObject.getClass();
956             }
957 
958             String fullPrefix = localPrefix;
959             if (StringUtils.isNotBlank(attributePrefix)) {
960                 fullPrefix = attributePrefix + "." + localPrefix;
961             }
962 
963             relationship = getDataObjectRelationship(nestedObject, nestedClass, localAttributeName, fullPrefix,
964                     keysOnly, supportsLookup, supportsInquiry);
965 
966             return relationship;
967         }
968 
969         // non-nested reference, get persistence relationships first
970         int maxSize = Integer.MAX_VALUE;
971 
972         if (isPersistable(dataObjectClass)) {
973             DataObjectMetadata metadata = metadataRepository.getMetadata(dataObjectClass);
974             DataObjectRelationship dataObjectRelationship = metadata.getRelationship(attributeName);
975 
976             if(dataObjectRelationship != null){
977                 List<DataObjectAttributeRelationship> attributeRelationships = dataObjectRelationship.getAttributeRelationships();
978                 for (DataObjectAttributeRelationship dataObjectAttributeRelationship : attributeRelationships){
979                     if(classHasSupportedFeatures(dataObjectRelationship.getRelatedType(),supportsLookup,supportsInquiry)){
980                         maxSize = attributeRelationships.size();
981                         relationship = transformToDeprecatedDataObjectRelationship(dataObjectClass,
982                                 attributeName,attributePrefix,dataObjectRelationship.getRelatedType(),dataObjectAttributeRelationship);
983 
984                         break;
985                     }
986                 }
987             }
988 
989         } else {
990             ModuleService moduleService = kualiModuleService.getResponsibleModuleService(dataObjectClass);
991             if (moduleService != null && moduleService.isExternalizable(dataObjectClass)) {
992                 relationship = getRelationshipMetadata(dataObjectClass, attributeName, attributePrefix);
993                 if ((relationship != null) && classHasSupportedFeatures(relationship.getRelatedClass(), supportsLookup,
994                         supportsInquiry)) {
995                     return relationship;
996                 } else {
997                     return null;
998                 }
999             }
1000         }
1001 
1002 
1003 
1004         if (ddReference != null && ddReference.getPrimitiveAttributes().size() < maxSize) {
1005             if (classHasSupportedFeatures(ddReference.getTargetClass(), supportsLookup, supportsInquiry)) {
1006                 relationship = populateRelationshipFromDictionaryReference(dataObjectClass, ddReference, null,
1007                         keysOnly);
1008             }
1009         }
1010         return relationship;
1011     }
1012 
1013     protected  org.kuali.rice.krad.bo.DataObjectRelationship transformToDeprecatedDataObjectRelationship(Class<?> dataObjectClass,
1014             String attributeName, String attributePrefix, Class<?> relatedObjectClass, DataObjectAttributeRelationship relationship){
1015         org.kuali.rice.krad.bo.DataObjectRelationship rel = new org.kuali.rice.krad.bo.DataObjectRelationship(dataObjectClass,
1016                 attributeName, relatedObjectClass);
1017         if(StringUtils.isBlank(attributePrefix)){
1018             rel.getParentToChildReferences().put(relationship.getParentAttributeName(),relationship.getChildAttributeName());
1019         } else {
1020             rel.getParentToChildReferences().put(attributePrefix + "." + relationship.getParentAttributeName(),relationship.getChildAttributeName());
1021         }
1022 
1023         return rel;
1024     }
1025 
1026     protected org.kuali.rice.krad.bo.DataObjectRelationship populateRelationshipFromDictionaryReference(Class<?> dataObjectClass,
1027             RelationshipDefinition ddReference, String attributePrefix, boolean keysOnly) {
1028         org.kuali.rice.krad.bo.DataObjectRelationship relationship = new org.kuali.rice.krad.bo.DataObjectRelationship(dataObjectClass,
1029                 ddReference.getObjectAttributeName(), ddReference.getTargetClass());
1030 
1031         for (PrimitiveAttributeDefinition def : ddReference.getPrimitiveAttributes()) {
1032             if (StringUtils.isNotBlank(attributePrefix)) {
1033                 relationship.getParentToChildReferences().put(attributePrefix + "." + def.getSourceName(),
1034                         def.getTargetName());
1035             } else {
1036                 relationship.getParentToChildReferences().put(def.getSourceName(), def.getTargetName());
1037             }
1038         }
1039 
1040         if (!keysOnly) {
1041             for (SupportAttributeDefinition def : ddReference.getSupportAttributes()) {
1042                 if (StringUtils.isNotBlank(attributePrefix)) {
1043                     relationship.getParentToChildReferences().put(attributePrefix + "." + def.getSourceName(),
1044                             def.getTargetName());
1045                     if (def.isIdentifier()) {
1046                         relationship.setUserVisibleIdentifierKey(attributePrefix + "." + def.getSourceName());
1047                     }
1048                 } else {
1049                     relationship.getParentToChildReferences().put(def.getSourceName(), def.getTargetName());
1050                     if (def.isIdentifier()) {
1051                         relationship.setUserVisibleIdentifierKey(def.getSourceName());
1052                     }
1053                 }
1054             }
1055         }
1056 
1057         return relationship;
1058     }
1059 
1060     @Override
1061 	public boolean isPersistable(Class<?> dataObjectClass) {
1062         return metadataRepository.contains(dataObjectClass);
1063     }
1064 
1065 
1066 
1067     protected org.kuali.rice.krad.bo.DataObjectRelationship getRelationshipMetadata(Class<?> dataObjectClass,
1068             String attributeName, String attributePrefix) {
1069 
1070         RelationshipDefinition relationshipDefinition = getDictionaryRelationship(dataObjectClass, attributeName);
1071         if (relationshipDefinition == null) {
1072             return null;
1073         }
1074 
1075         org.kuali.rice.krad.bo.DataObjectRelationship dataObjectRelationship =
1076                 new org.kuali.rice.krad.bo.DataObjectRelationship(relationshipDefinition.getSourceClass(),
1077                         relationshipDefinition.getObjectAttributeName(), relationshipDefinition.getTargetClass());
1078 
1079         if (!StringUtils.isEmpty(attributePrefix)) {
1080             attributePrefix += ".";
1081         }
1082 
1083         List<PrimitiveAttributeDefinition> primitives = relationshipDefinition.getPrimitiveAttributes();
1084         for (PrimitiveAttributeDefinition primitiveAttributeDefinition : primitives) {
1085             dataObjectRelationship.getParentToChildReferences().put(
1086                     attributePrefix + primitiveAttributeDefinition.getSourceName(),
1087                     primitiveAttributeDefinition.getTargetName());
1088         }
1089 
1090         return dataObjectRelationship;
1091     }
1092 
1093     protected boolean classHasSupportedFeatures(Class relationshipClass, boolean supportsLookup,
1094             boolean supportsInquiry) {
1095         boolean hasSupportedFeatures = true;
1096         if (supportsLookup && !getViewDictionaryService().isLookupable(relationshipClass)) {
1097             hasSupportedFeatures = false;
1098         }
1099         if (supportsInquiry && !getViewDictionaryService().isInquirable(relationshipClass)) {
1100             hasSupportedFeatures = false;
1101         }
1102 
1103         return hasSupportedFeatures;
1104     }
1105 
1106     @Override
1107     public ForeignKeyFieldsPopulationState getForeignKeyFieldsPopulationState(Object dataObject, String referenceName) {
1108         DataObjectWrapper<Object> dataObjectWrapper = dataObjectService.wrap(dataObject);
1109         return new ForeignKeyFieldsPopulationState(dataObjectWrapper.areAllPrimaryKeyAttributesPopulated(),
1110                 dataObjectWrapper.areAnyPrimaryKeyAttributesPopulated(),
1111                 dataObjectWrapper.getUnpopulatedPrimaryKeyAttributeNames());
1112     }
1113 
1114     @Override
1115     public Map<String, String> getForeignKeysForReference(Class<?> clazz, String attributeName) {
1116         if (metadataRepository.contains(clazz)) {
1117             DataObjectRelationship relationship = metadataRepository.getMetadata(clazz).getRelationship(attributeName);
1118             List<DataObjectAttributeRelationship> attributeRelationships = relationship.getAttributeRelationships();
1119             Map<String, String> parentChildKeyRelationships = new HashMap<String, String>(
1120                     attributeRelationships.size());
1121             for (DataObjectAttributeRelationship doar : attributeRelationships) {
1122                 parentChildKeyRelationships.put(doar.getParentAttributeName(), doar.getChildAttributeName());
1123             }
1124             return parentChildKeyRelationships;
1125         }
1126         return Collections.emptyMap();
1127     }
1128 
1129     @Override
1130     public void setObjectPropertyDeep(Object bo, String propertyName, Class type,
1131             Object propertyValue) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException{
1132         DataObjectWrapper<Object> dataObjectWrapper = dataObjectService.wrap(bo);
1133         // Base return cases to avoid null pointers & infinite loops
1134         if (KRADUtils.isNull(bo) || !PropertyUtils.isReadable(bo, propertyName) || (propertyValue != null && propertyValue.equals(
1135                 dataObjectWrapper.getPropertyValueNullSafe(propertyName))) || (type != null && !type.equals(KRADUtils.easyGetPropertyType(bo,
1136                 propertyName)))) {
1137             return;
1138         }
1139         // Set the property in the BO
1140         KRADUtils.setObjectProperty(bo, propertyName, type, propertyValue);
1141 
1142         // Now drill down and check nested BOs and BO lists
1143         PropertyDescriptor[] propertyDescriptors = PropertyUtils.getPropertyDescriptors(bo.getClass());
1144         for (int i = 0; i < propertyDescriptors.length; i++) {
1145 
1146             PropertyDescriptor propertyDescriptor = propertyDescriptors[i];
1147 
1148             // Business Objects
1149             if (propertyDescriptor.getPropertyType() != null && (BusinessObject.class).isAssignableFrom(
1150                     propertyDescriptor.getPropertyType()) && PropertyUtils.isReadable(bo,
1151                     propertyDescriptor.getName())) {
1152                 Object nestedBo = dataObjectWrapper.getPropertyValueNullSafe(propertyDescriptor.getName());
1153                 if (nestedBo instanceof BusinessObject) {
1154                     setObjectPropertyDeep(nestedBo, propertyName, type, propertyValue);
1155                 }
1156             }
1157 
1158             // Lists
1159             else if (propertyDescriptor.getPropertyType() != null && (List.class).isAssignableFrom(
1160                     propertyDescriptor.getPropertyType()) && dataObjectWrapper.getPropertyValueNullSafe(propertyDescriptor.getName())
1161                     != null) {
1162 
1163                 List propertyList = (List) dataObjectWrapper.getPropertyValueNullSafe(propertyDescriptor.getName());
1164                 for (Object listedBo : propertyList) {
1165                     if (listedBo != null && listedBo instanceof BusinessObject) {
1166                         setObjectPropertyDeep(listedBo, propertyName, type, propertyValue);
1167                     }
1168                 } // end for
1169             }
1170         } // end for
1171     }
1172 
1173     @Override
1174     public boolean hasPrimaryKeyFieldValues(Object dataObject) {
1175         DataObjectWrapper<Object> dataObjectWrapper = dataObjectService.wrap(dataObject);
1176         return dataObjectWrapper.areAllPrimaryKeyAttributesPopulated();
1177     }
1178 
1179     @Override
1180 	public Class materializeClassForProxiedObject(Object object){
1181         if(object == null){
1182             return null;
1183         }
1184         if(LegacyUtils.isKradDataManaged(object.getClass())){
1185             Object o = resolveProxy(object);
1186             if(o != null){
1187                 return o.getClass();
1188             }
1189         }
1190         return object.getClass();
1191     }
1192 
1193     @Override
1194 	public Object getNestedValue(Object bo, String fieldName){
1195         if (bo == null) {
1196             throw new IllegalArgumentException("The bo passed in was null.");
1197         }
1198         if (StringUtils.isBlank(fieldName)) {
1199             throw new IllegalArgumentException("The fieldName passed in was blank.");
1200         }
1201         return DataObjectUtils.getNestedValue(bo,fieldName);
1202     }
1203 
1204 
1205     @Override
1206 	public Object createNewObjectFromClass(Class clazz){
1207         if (clazz == null) {
1208             throw new RuntimeException("BO class was passed in as null");
1209         }
1210         return DataObjectUtils.createNewObjectFromClass(clazz);
1211     }
1212 
1213     @Override
1214     public boolean isNull(Object object){
1215         return DataObjectUtils.isNull(object);
1216     }
1217 
1218     @Override
1219 	public void setObjectProperty(Object bo, String propertyName, Class propertyType,
1220             Object propertyValue) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException{
1221         DataObjectUtils.setObjectValue(bo,propertyName,propertyValue);
1222     }
1223 
1224     @Override
1225     public <T extends Document> T findByDocumentHeaderId(Class<T> documentClass, String id) {
1226         T document = KRADServiceLocator.getDataObjectService().find(documentClass, id);
1227         // original KNS code always did this addAdHocs nonsense, so we'll do the same to preserve behavior
1228         ((DocumentAdHocService) KRADServiceLocatorWeb.getService("documentAdHocService")).addAdHocs(document);
1229         return document;
1230     }
1231 
1232     @Override
1233 	public <T extends Document> List<T> findByDocumentHeaderIds(Class<T> documentClass, List<String> ids) {
1234         List<T> documents = new ArrayList<T>();
1235         for (String id : ids) {
1236             documents.add(findByDocumentHeaderId(documentClass, id));
1237         }
1238         return documents;
1239     }
1240 
1241     @Required
1242     public void setDataObjectService(DataObjectService dataObjectService) {
1243         this.dataObjectService = dataObjectService;
1244     }
1245 
1246     @Required
1247     public void setMetadataRepository(MetadataRepository metadataRepository) {
1248         this.metadataRepository = metadataRepository;
1249     }
1250 
1251     @Required
1252     public void setLookupCriteriaGenerator(LookupCriteriaGenerator lookupCriteriaGenerator) {
1253         this.lookupCriteriaGenerator = lookupCriteriaGenerator;
1254     }
1255 
1256     @Required
1257     public void setKualiConfigurationService(ConfigurationService kualiConfigurationService) {
1258         this.kualiConfigurationService = kualiConfigurationService;
1259     }
1260 
1261     @Required
1262     public void setKualiModuleService(KualiModuleService kualiModuleService) {
1263         this.kualiModuleService = kualiModuleService;
1264     }
1265 
1266     @Required
1267     public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
1268         this.dataDictionaryService = dataDictionaryService;
1269     }
1270 
1271     public ViewDictionaryService getViewDictionaryService() {
1272         return viewDictionaryService;
1273     }
1274 
1275     public void setViewDictionaryService(ViewDictionaryService viewDictionaryService) {
1276         this.viewDictionaryService = viewDictionaryService;
1277     }
1278 
1279 
1280 }