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