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