View Javadoc
1   package org.kuali.rice.krad.service.impl;
2   
3   import org.apache.commons.lang.StringUtils;
4   import org.apache.commons.lang.reflect.FieldUtils;
5   import org.eclipse.persistence.indirection.ValueHolder;
6   import org.kuali.rice.core.api.config.property.ConfigurationService;
7   import org.kuali.rice.core.api.criteria.QueryByCriteria;
8   import org.kuali.rice.core.api.datetime.DateTimeService;
9   import org.kuali.rice.core.api.exception.RiceRuntimeException;
10  import org.kuali.rice.core.api.mo.common.GloballyUnique;
11  import org.kuali.rice.core.api.mo.common.Versioned;
12  import org.kuali.rice.core.api.search.SearchOperator;
13  import org.kuali.rice.core.api.uif.RemotableQuickFinder;
14  import org.kuali.rice.core.api.util.RiceKeyConstants;
15  import org.kuali.rice.core.framework.persistence.ojb.conversion.OjbCharBooleanConversion;
16  import org.kuali.rice.core.framework.persistence.platform.DatabasePlatform;
17  import org.kuali.rice.krad.bo.*;
18  import org.kuali.rice.krad.dao.DocumentDao;
19  import org.kuali.rice.krad.dao.LookupDao;
20  import org.kuali.rice.krad.dao.MaintenanceDocumentDao;
21  import org.kuali.rice.krad.data.DataObjectService;
22  import org.kuali.rice.krad.datadictionary.*;
23  import org.kuali.rice.krad.document.Document;
24  import org.kuali.rice.krad.exception.ValidationException;
25  import org.kuali.rice.krad.lookup.LookupUtils;
26  import org.kuali.rice.krad.service.*;
27  import org.kuali.rice.krad.uif.UifPropertyPaths;
28  import org.kuali.rice.krad.uif.service.ViewDictionaryService;
29  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
30  import org.kuali.rice.krad.util.*;
31  import org.springframework.beans.PropertyAccessorUtils;
32  import org.springframework.beans.factory.annotation.Required;
33  
34  import java.lang.reflect.Field;
35  import java.lang.reflect.InvocationTargetException;
36  import java.util.*;
37  import java.util.concurrent.ConcurrentHashMap;
38  import java.util.concurrent.ConcurrentMap;
39  import java.util.regex.Matcher;
40  import java.util.regex.Pattern;
41  
42  /**
43   * Created by angelind on 11/6/14.
44   *
45   * Overridden by 'Sheik Salahudeen'
46   * Overridden for fixing the document fetching issue in KRAD Transaction document with OJB.
47   * Modified method names: findByPrimaryKey,findBySinglePrimaryKey,findMatching,findMatchingOrderBy
48   * Changes description : Removed the type casting to (Class<BusinessObject>).
49   */
50  @Deprecated
51  public class KNSLegacyDataAdapterImpl implements LegacyDataAdapter {
52      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(
53              KNSLegacyDataAdapterImpl.class);
54  
55      private static final Pattern VALUE_HOLDER_FIELD_PATTERN = Pattern.compile("^_persistence_(.*)_vh$");
56  
57      private final ConcurrentMap<Class<?>, List<ValueHolderFieldPair>> valueHolderFieldCache =
58              new ConcurrentHashMap<Class<?>, List<ValueHolderFieldPair>>(8, 0.9f, 1);
59  
60      private BusinessObjectService businessObjectService;
61      private PersistenceService persistenceService;
62      private LookupDao lookupDao;
63      private LookupCriteriaGenerator lookupCriteriaGenerator;
64      private DateTimeService dateTimeService;
65      private DatabasePlatform databasePlatform;
66  
67      private DocumentDao documentDao;
68      private MaintenanceDocumentDao maintenanceDocumentDaoOjb;
69  
70      private PersistenceStructureService persistenceStructureService;
71      private DataObjectMetaDataService dataObjectMetaDataService;
72      private ConfigurationService kualiConfigurationService;
73      private KualiModuleService kualiModuleService;
74      private DataDictionaryService dataDictionaryService;
75      private DataObjectService dataObjectService;
76      private ViewDictionaryService viewDictionaryService;
77  
78      @Override
79      public <T> T save(T dataObject) {
80          if (dataObject instanceof Collection) {
81              Collection<Object> newList = new ArrayList<Object>(((Collection) dataObject).size());
82              for (Object obj : (Collection<?>) dataObject) {
83                  newList.add(save(obj));
84              }
85              return (T) newList;
86          } else {
87              return (T) businessObjectService.save((PersistableBusinessObject) dataObject);
88          }
89      }
90  
91      @Override
92      public <T> T linkAndSave(T dataObject) {
93          return (T) businessObjectService.linkAndSave((PersistableBusinessObject) dataObject);
94      }
95  
96      @Override
97      public <T> T saveDocument(T document) {
98          return (T) documentDao.save((Document) document);
99      }
100 
101     @Override
102     public <T> T findByPrimaryKey(Class<T> clazz, Map<String, ?> primaryKeys) {
103         return (T) businessObjectService.findByPrimaryKey(clazz, primaryKeys);
104     }
105 
106     @Override
107     public <T> T findBySinglePrimaryKey(Class<T> clazz, Object primaryKey) {
108         return (T) businessObjectService.findBySinglePrimaryKey(clazz, primaryKey);
109     }
110 
111     @Override
112     public void delete(Object dataObject) {
113         if (dataObject instanceof Collection) {
114             for (Object dobj : (Collection) dataObject) {
115                 delete(dobj);
116             }
117         } else {
118             businessObjectService.delete(dataObject);
119         }
120     }
121 
122     @Override
123     public void deleteMatching(Class<?> type, Map<String, ?> fieldValues) {
124         businessObjectService.deleteMatching(type, fieldValues);
125     }
126 
127     @Override
128     public <T> T retrieve(T dataObject) {
129         return (T) businessObjectService.retrieve(dataObject);
130     }
131 
132     @Override
133     public <T> Collection<T> findAll(Class<T> clazz) {
134         // just find all objects of given type without any attribute criteria
135         return findMatching(clazz, Collections.<String, Object>emptyMap());
136     }
137 
138     @Override
139     public <T> Collection<T> findMatching(Class<T> clazz, Map<String, ?> fieldValues) {
140         return (Collection<T>) businessObjectService.findMatching(clazz, fieldValues);
141     }
142 
143     @Override
144     public <T> Collection<T> findMatchingOrderBy(Class<T> clazz, Map<String, ?> fieldValues, String sortField,
145                                                  boolean sortAscending) {
146         return (Collection<T>) businessObjectService.findMatchingOrderBy(clazz, fieldValues,
147                 sortField, sortAscending);
148     }
149 
150     @Override
151     public Map<String, ?> getPrimaryKeyFieldValues(Object dataObject) {
152         return persistenceService.getPrimaryKeyFieldValues(dataObject);
153     }
154 
155     @Override
156     public void retrieveNonKeyFields(Object persistableObject) {
157         persistenceService.retrieveNonKeyFields(persistableObject);
158         synchronizeEclipseLinkWeavings(persistableObject);
159     }
160 
161     @Override
162     public void retrieveReferenceObject(Object persistableObject, String referenceObjectName) {
163         persistenceService.retrieveReferenceObject(persistableObject, referenceObjectName);
164         synchronizeEclipseLinkWeavings(persistableObject, referenceObjectName);
165     }
166 
167     /**
168      * @see ValueHolderFieldPair for information on why we need to do field sychronization related to weaving
169      */
170     protected void synchronizeEclipseLinkWeavings(Object persistableObject, String propertyName) {
171         if (LegacyUtils.isKradDataManaged(persistableObject.getClass())) {
172             List<ValueHolderFieldPair> fieldPairs = loadValueHolderFieldPairs(persistableObject.getClass());
173             for (ValueHolderFieldPair fieldPair : fieldPairs) {
174                 if (fieldPair.field.getName().equals(propertyName)) {
175                     fieldPair.synchronizeValueHolder(persistableObject);
176                 }
177             }
178         }
179     }
180 
181     /**
182      * @see ValueHolderFieldPair for information on why we need to do field sychronization related to weaving
183      */
184     protected void synchronizeEclipseLinkWeavings(Object persistableObject) {
185         if (LegacyUtils.isKradDataManaged(persistableObject.getClass())) {
186             List<ValueHolderFieldPair> fieldPairs = loadValueHolderFieldPairs(persistableObject.getClass());
187             for (ValueHolderFieldPair fieldPair : fieldPairs) {
188                 fieldPair.synchronizeValueHolder(persistableObject);
189             }
190         }
191     }
192 
193     /**
194      * @see ValueHolderFieldPair for information on why we need to do field sychronization related to weaving
195      */
196     private List<ValueHolderFieldPair> loadValueHolderFieldPairs(Class<?> type) {
197         if (valueHolderFieldCache.get(type) == null) {
198             List<ValueHolderFieldPair> pairs = new ArrayList<ValueHolderFieldPair>();
199             searchValueHolderFieldPairs(type, pairs);
200             valueHolderFieldCache.putIfAbsent(type, pairs);
201         }
202         return valueHolderFieldCache.get(type);
203     }
204 
205     /**
206      * @see ValueHolderFieldPair for information on why we need to do field sychronization related to weaving
207      */
208     private void searchValueHolderFieldPairs(Class<?> type, List<ValueHolderFieldPair> pairs) {
209         if (type.equals(Object.class)) {
210             return;
211         }
212         for (Field valueHolderField : type.getDeclaredFields()) {
213             Matcher matcher = VALUE_HOLDER_FIELD_PATTERN.matcher(valueHolderField.getName());
214             if (matcher.matches()) {
215                 valueHolderField.setAccessible(true);
216                 String fieldName = matcher.group(1);
217                 Field valueField = FieldUtils.getDeclaredField(type, fieldName, true);
218                 if (valueField != null) {
219                     pairs.add(new ValueHolderFieldPair(valueField, valueHolderField));
220                 }
221             }
222         }
223         searchValueHolderFieldPairs(type.getSuperclass(), pairs);
224     }
225 
226     @Override
227     public void refreshAllNonUpdatingReferences(Object persistableObject) {
228         persistenceService.refreshAllNonUpdatingReferences((PersistableBusinessObject) persistableObject);
229         synchronizeEclipseLinkWeavings(persistableObject);
230     }
231 
232     @Override
233     public boolean isProxied(Object object) {
234         if (object == null || (!(object instanceof BusinessObject) && !(object instanceof PersistableBusinessObjectBaseAdapter))) {
235             return false;
236         }
237         return persistenceService.isProxied(object);
238     }
239 
240     @Override
241     public Object resolveProxy(Object o) {
242         return persistenceService.resolveProxy(o);
243     }
244 
245     // Lookup methods
246 
247     @Override
248     public <T> Collection<T> findCollectionBySearchHelper(Class<T> dataObjectClass, Map<String, String> formProperties,
249                                                           boolean unbounded, boolean allPrimaryKeyValuesPresentAndNotWildcard, Integer searchResultsLimit) {
250         return lookupDao.findCollectionBySearchHelper(dataObjectClass, formProperties, unbounded,
251                 allPrimaryKeyValuesPresentAndNotWildcard, searchResultsLimit);
252     }
253 
254     //TODO: implement. currently is same implementation as above
255     @Override
256     public <T> Collection<T> findCollectionBySearchHelper(Class<T> dataObjectClass, Map<String, String> formProperties,
257                                                           List<String> wildcardAsLiteralPropertyNames, boolean unbounded,
258                                                           boolean allPrimaryKeyValuesPresentAndNotWildcard, Integer searchResultsLimit) {
259         return lookupDao.findCollectionBySearchHelper(dataObjectClass, formProperties, unbounded,
260                 allPrimaryKeyValuesPresentAndNotWildcard, searchResultsLimit);
261     }
262 
263     /**
264      *
265      * @param dataObjectClass the dataobject class
266      * @param formProperties the incoming lookup form properties
267      * @param unbounded whether the search is unbounded
268      * @param searchResultsLimit the searchResultsLimit; null implies use of default KNS value if set for the class
269      * @param <T> the data object type
270      * @return collection of lookup results
271      */
272     protected <T> Collection<T> performDataObjectServiceLookup(Class<T> dataObjectClass,
273                                                                Map<String, String> formProperties, boolean unbounded, boolean allPrimaryKeyValuesPresentAndNotWildcard,
274                                                                Integer searchResultsLimit) {
275         if (!unbounded && searchResultsLimit == null) {
276             // use KRAD LookupUtils.getSearchResultsLimit instead of KNS version. we have no LookupForm, so pass null, only the class will be used
277             //searchResultsLimit = LookupUtils.getSearchResultsLimit(example, null);
278             searchResultsLimit = org.kuali.rice.kns.lookup.LookupUtils.getSearchResultsLimit(dataObjectClass);
279         }
280         QueryByCriteria.Builder query = lookupCriteriaGenerator.generateCriteria(dataObjectClass, formProperties,
281                 allPrimaryKeyValuesPresentAndNotWildcard);
282         if (!unbounded && searchResultsLimit != null) {
283             query.setMaxResults(searchResultsLimit);
284         }
285 
286         Collection<T> results = dataObjectService.findMatching(dataObjectClass, query.build()).getResults();
287         return filterCurrentDataObjects(dataObjectClass, results, formProperties);
288     }
289 
290     protected <T> Collection<T> filterCurrentDataObjects(Class<T> dataObjectClass, Collection<T> unfiltered,
291                                                          Map<String, String> formProps) {
292         if (InactivatableFromTo.class.isAssignableFrom(dataObjectClass)) {
293             Boolean currentSpecifier = lookupCriteriaCurrentSpecifier(formProps);
294             if (currentSpecifier != null) {
295                 List<InactivatableFromTo> onlyCurrent =
296                         KRADServiceLocator.getInactivateableFromToService().filterOutNonCurrent(new ArrayList(
297                                 unfiltered), new Date(LookupUtils.getActiveDateTimestampForCriteria(formProps)
298                                 .getTime()));
299                 if (currentSpecifier) {
300                     return (Collection<T>) onlyCurrent;
301                 } else {
302                     unfiltered.removeAll(onlyCurrent);
303                     return unfiltered;
304                 }
305             }
306         }
307         return unfiltered;
308     }
309 
310     protected Boolean lookupCriteriaCurrentSpecifier(Map<String, String> formProps) {
311         String value = formProps.get(KRADPropertyConstants.CURRENT);
312         if (StringUtils.isNotBlank(value)) {
313             // FIXME: use something more portable than this direct OJB converter
314             String currentBooleanStr = (String) new OjbCharBooleanConversion().javaToSql(value);
315             if (OjbCharBooleanConversion.DATABASE_BOOLEAN_TRUE_STRING_REPRESENTATION.equals(currentBooleanStr)) {
316                 return Boolean.TRUE;
317             } else if (OjbCharBooleanConversion.DATABASE_BOOLEAN_FALSE_STRING_REPRESENTATION.equals(
318                     currentBooleanStr)) {
319                 return Boolean.FALSE;
320             }
321         }
322         return null;
323     }
324 
325     @Override
326     public <T> T findObjectBySearch(Class<T> type, Map<String, String> formProps) {
327         return lookupDao.findObjectByMap(type, formProps);
328     }
329 
330     /**
331      * Returns whether all primary key values are specified in the lookup  map and do not contain any wildcards
332      *
333      * @param boClass the bo class to lookup
334      * @param formProps the incoming form/lookup properties
335      * @return whether all primary key values are specified in the lookup  map and do not contain any wildcards
336      */
337     @Override
338     public boolean allPrimaryKeyValuesPresentAndNotWildcard(Class<?> boClass, Map<String, String> formProps) {
339         List<String> pkFields = listPrimaryKeyFieldNames(boClass);
340         Iterator<String> pkIter = pkFields.iterator();
341         boolean returnVal = true;
342         while (returnVal && pkIter.hasNext()) {
343             String pkName = pkIter.next();
344             String pkValue = formProps.get(pkName);
345 
346             if (StringUtils.isBlank(pkValue)) {
347                 returnVal = false;
348             } else {
349                 for (SearchOperator op : SearchOperator.QUERY_CHARACTERS) {
350                     if (pkValue.contains(op.op())) {
351                         returnVal = false;
352                         break;
353                     }
354                 }
355             }
356         }
357         return returnVal;
358     }
359 
360     @SuppressWarnings("unchecked")
361     @Override
362     public List<String> listPrimaryKeyFieldNames(Class<?> type) {
363         List<String> keys = new ArrayList<String>();
364         if ( type == null ) {
365             return keys;
366         }
367         if (isPersistable(type)) {
368             keys = persistenceStructureService.listPrimaryKeyFieldNames(type);
369         } else {
370             ModuleService responsibleModuleService = kualiModuleService.getResponsibleModuleService(type);
371             if (responsibleModuleService != null && responsibleModuleService.isExternalizable(type)) {
372                 keys = responsibleModuleService.listPrimaryKeyFieldNames(type);
373             } else {
374                 // check the Data Dictionary for PK's of non PBO/EBO
375                 DataObjectEntry dataObjectEntry = dataDictionaryService.getDataDictionary().getDataObjectEntry(type.getName());
376                 if ( dataObjectEntry != null ) {
377                     List<String> pks = dataObjectEntry.getPrimaryKeys();
378                     if (pks != null ) {
379                         keys = pks;
380                     }
381                 } else {
382                     LOG.warn( "Unable to retrieve data object entry for non-persistable KNS-managed class: " + type.getName() );
383                 }
384             }
385         }
386         return keys;
387     }
388 
389     /**
390      * LookupServiceImpl calls BusinessObjectMetaDataService to listPrimaryKeyFieldNames.
391      * The BusinessObjectMetaDataService goes beyond the PersistenceStructureService to consult
392      * the associated ModuleService in determining the primary key field names.
393      * TODO: Do we need both listPrimaryKeyFieldNames/persistenceStructureService and
394      * listPrimaryKeyFieldNamesConsultingAllServices/businesObjectMetaDataService or
395      * can the latter superset be used for the former?
396      *
397      * @param type the data object class
398      * @return list of primary key field names, consulting persistence structure service, module service and
399      *         datadictionary
400      */
401     protected List<String> listPrimaryKeyFieldNamesConsultingAllServices(Class<?> type) {
402         return dataObjectMetaDataService.listPrimaryKeyFieldNames(type);
403     }
404 
405     @Override
406     public Class<?> determineCollectionObjectType(Class<?> containingType, String collectionPropertyName) {
407         final Class<?> collectionObjectType;
408         if (isPersistable(containingType)) {
409             Map<String, Class> collectionClasses = new HashMap<String, Class>();
410             collectionClasses = persistenceStructureService.listCollectionObjectTypes(containingType);
411             collectionObjectType = collectionClasses.get(collectionPropertyName);
412         } else {
413             throw new RuntimeException(
414                     "Can't determine the Class of Collection elements because persistenceStructureService.isPersistable("
415                             + containingType.getName()
416                             + ") returns false.");
417         }
418         return collectionObjectType;
419 
420     }
421 
422     @Override
423     public boolean hasReference(Class<?> boClass, String referenceName) {
424         return persistenceStructureService.hasReference(boClass, referenceName);
425     }
426 
427     @Override
428     public boolean hasCollection(Class<?> boClass, String collectionName) {
429         return persistenceStructureService.hasCollection(boClass, collectionName);
430     }
431 
432     @Override
433     public boolean isExtensionAttribute(Class<?> boClass, String attributePropertyName, Class<?> propertyType) {
434         return propertyType.equals(PersistableBusinessObjectExtension.class);
435     }
436 
437     @Override
438     public Class<?> getExtensionAttributeClass(Class<?> boClass, String attributePropertyName) {
439         return persistenceStructureService.getBusinessObjectAttributeClass((Class<? extends PersistableBusinessObject>)boClass, attributePropertyName);
440     }
441 
442 
443     @Override
444     public Map<String, ?> getPrimaryKeyFieldValuesDOMDS(Object dataObject) {
445         return dataObjectMetaDataService.getPrimaryKeyFieldValues(dataObject);
446     }
447 
448     @Override
449     public boolean equalsByPrimaryKeys(Object do1, Object do2) {
450         return dataObjectMetaDataService.equalsByPrimaryKeys(do1, do2);
451     }
452 
453     @Override
454     public void materializeAllSubObjects(Object object) {
455         ObjectUtils.materializeAllSubObjects((PersistableBusinessObject) object);
456     }
457 
458     @Override
459     public Class<?> getPropertyType(Object object, String propertyName) {
460         return ObjectUtils.getPropertyType(object, propertyName, persistenceStructureService);
461     }
462 
463     @Override
464     public Object getExtension(
465             Class<?> businessObjectClass) throws InstantiationException, IllegalAccessException {
466         Class<? extends PersistableBusinessObjectExtension> extensionClass =
467                 persistenceStructureService.getBusinessObjectAttributeClass((Class<? extends PersistableBusinessObject>) businessObjectClass, "extension");
468         if (extensionClass != null) {
469             return extensionClass.newInstance();
470         }
471         return null;
472     }
473 
474     @Override
475     public void refreshReferenceObject(Object businessObject, String referenceObjectName) {
476         if (StringUtils.isNotBlank(referenceObjectName) && !StringUtils.equals(referenceObjectName, "extension")) {
477             if (persistenceStructureService.hasReference(businessObject.getClass(), referenceObjectName)
478                     || persistenceStructureService.hasCollection(businessObject.getClass(), referenceObjectName)) {
479                 retrieveReferenceObject(businessObject, referenceObjectName);
480             }
481         }
482     }
483 
484     @Override
485     public boolean isLockable(Object object) {
486         return isPersistable(object.getClass());
487     }
488 
489     @Override
490     public void verifyVersionNumber(Object dataObject) {
491         if (isPersistable(dataObject.getClass())) {
492             Object pbObject = businessObjectService.retrieve(dataObject);
493             if ( dataObject instanceof Versioned) {
494                 Long pbObjectVerNbr = KRADUtils.isNull(pbObject) ? null : ((Versioned) pbObject).getVersionNumber();
495                 Long newObjectVerNbr = ((Versioned) dataObject).getVersionNumber();
496                 if (pbObjectVerNbr != null && !(pbObjectVerNbr.equals(newObjectVerNbr))) {
497                     GlobalVariables.getMessageMap().putError(KRADConstants.GLOBAL_ERRORS,
498                             RiceKeyConstants.ERROR_VERSION_MISMATCH);
499                     throw new ValidationException(
500                             "Version mismatch between the local business object and the database business object");
501                 }
502             }
503         }
504     }
505 
506     @Override
507     public RemotableQuickFinder.Builder createQuickFinder(Class<?> containingClass, String attributeName) {
508         return createQuickFinderLegacy(containingClass, attributeName);
509     }
510 
511     /**
512      * Legacy implementation of createQuickFinder. Uses the legacy DataObjectMetadataService.
513      */
514     protected RemotableQuickFinder.Builder createQuickFinderLegacy(Class<?> containingClass, String attributeName) {
515         Object sampleComponent;
516         try {
517             sampleComponent = containingClass.newInstance();
518         } catch (InstantiationException e) {
519             throw new RiceRuntimeException(e);
520         } catch (IllegalAccessException e) {
521             throw new RiceRuntimeException(e);
522         }
523 
524         String lookupClassName = null;
525         Map<String, String> fieldConversions = new HashMap<String, String>();
526         Map<String, String> lookupParameters = new HashMap<String, String>();
527 
528         org.kuali.rice.krad.bo.DataObjectRelationship relationship =
529                 getDataObjectRelationship(sampleComponent, containingClass, attributeName, "",
530                         true, true, false);
531         if (relationship != null) {
532             lookupClassName = relationship.getRelatedClass().getName();
533 
534             for (Map.Entry<String, String> entry : relationship.getParentToChildReferences().entrySet()) {
535                 String fromField = entry.getValue();
536                 String toField = entry.getKey();
537                 fieldConversions.put(fromField, toField);
538             }
539 
540             for (Map.Entry<String, String> entry : relationship.getParentToChildReferences().entrySet()) {
541                 String fromField = entry.getKey();
542                 String toField = entry.getValue();
543 
544                 if (relationship.getUserVisibleIdentifierKey() == null || relationship.getUserVisibleIdentifierKey()
545                         .equals(fromField)) {
546                     lookupParameters.put(fromField, toField);
547                 }
548             }
549         } else {
550             // check for title attribute and if match build lookup to component class using pk fields
551             String titleAttribute = dataObjectMetaDataService.getTitleAttribute(containingClass);
552             if (StringUtils.equals(titleAttribute, attributeName)) {
553                 lookupClassName = containingClass.getName();
554 
555                 List<String> pkAttributes = dataObjectMetaDataService.listPrimaryKeyFieldNames(containingClass);
556                 for (String pkAttribute : pkAttributes) {
557                     fieldConversions.put(pkAttribute, pkAttribute);
558                     if (!StringUtils.equals(pkAttribute, attributeName)) {
559                         lookupParameters.put(pkAttribute, pkAttribute);
560                     }
561                 }
562             }
563         }
564 
565         if (StringUtils.isNotBlank(lookupClassName)) {
566             String baseUrl = kualiConfigurationService.getPropertyValueAsString(KRADConstants.KRAD_LOOKUP_URL_KEY);
567             RemotableQuickFinder.Builder builder = RemotableQuickFinder.Builder.create(baseUrl, lookupClassName);
568             builder.setLookupParameters(lookupParameters);
569             builder.setFieldConversions(fieldConversions);
570 
571             return builder;
572         }
573 
574         return null;
575     }
576 
577 
578     @Override
579     public boolean isReferenceUpdatable(Class<?> type, String referenceName) {
580         return persistenceStructureService.isReferenceUpdatable(type, referenceName);
581     }
582 
583     @SuppressWarnings("rawtypes")
584     @Override
585     public Map<String, Class> listReferenceObjectFields(Class<?> type) {
586         return persistenceStructureService.listReferenceObjectFields(type);
587     }
588 
589     @Override
590     public boolean isCollectionUpdatable(Class<?> type, String collectionName) {
591         return persistenceStructureService.isCollectionUpdatable(type, collectionName);
592     }
593 
594     @Override
595     public Map<String, Class> listCollectionObjectTypes(Class<?> type) {
596         return persistenceStructureService.listCollectionObjectTypes(type);
597     }
598 
599     @Override
600     public BusinessObject getReferenceIfExists(Object bo, String referenceName) {
601         if (!(bo instanceof BusinessObject)) {
602             throw new UnsupportedOperationException("getReferenceIfExists only supports BusinessObject in KNS");
603         }
604 
605         return businessObjectService.getReferenceIfExists((BusinessObject) bo, referenceName);
606     }
607 
608     @Override
609     public boolean allForeignKeyValuesPopulatedForReference(Object bo, String referenceName) {
610         if (!(bo instanceof PersistableBusinessObject)) {
611             throw new UnsupportedOperationException(
612                     "getReferenceIfExists only supports PersistableBusinessObject in KNS");
613         }
614 
615         return persistenceService.allForeignKeyValuesPopulatedForReference((PersistableBusinessObject) bo,
616                 referenceName);
617     }
618 
619     /**
620      * gets the relationship that the attribute represents on the class
621      *
622      * @param c - the class to which the attribute belongs
623      * @param attributeName - property name for the attribute
624      * @return a relationship definition for the attribute
625      */
626     @Override
627     public RelationshipDefinition getDictionaryRelationship(Class<?> c, String attributeName) {
628         DataDictionaryEntry entryBase =
629                 KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getDictionaryObjectEntry(
630                         c.getName());
631         if (entryBase == null) {
632             return null;
633         }
634 
635         RelationshipDefinition relationship = null;
636 
637         List<RelationshipDefinition> ddRelationships = entryBase.getRelationships();
638 
639         int minKeys = Integer.MAX_VALUE;
640         for (RelationshipDefinition def : ddRelationships) {
641             // favor key sizes of 1 first
642             if (def.getPrimitiveAttributes().size() == 1) {
643                 for (PrimitiveAttributeDefinition primitive : def.getPrimitiveAttributes()) {
644                     if (primitive.getSourceName().equals(attributeName) || def.getObjectAttributeName().equals(
645                             attributeName)) {
646                         relationship = def;
647                         minKeys = 1;
648                         break;
649                     }
650                 }
651             } else if (def.getPrimitiveAttributes().size() < minKeys) {
652                 for (PrimitiveAttributeDefinition primitive : def.getPrimitiveAttributes()) {
653                     if (primitive.getSourceName().equals(attributeName) || def.getObjectAttributeName().equals(
654                             attributeName)) {
655                         relationship = def;
656                         minKeys = def.getPrimitiveAttributes().size();
657                         break;
658                     }
659                 }
660             }
661         }
662 
663         // check the support attributes
664         if (relationship == null) {
665             for (RelationshipDefinition def : ddRelationships) {
666                 if (def.hasIdentifier()) {
667                     if (def.getIdentifier().getSourceName().equals(attributeName)) {
668                         relationship = def;
669                     }
670                 }
671             }
672         }
673 
674         return relationship;
675     }
676 
677     /**
678      * @see org.kuali.rice.krad.service.LegacyDataAdapter
679      */
680     @Override
681     public String getTitleAttribute(Class<?> dataObjectClass) {
682         String titleAttribute = null;
683         DataObjectEntry entry = getDataObjectEntry(dataObjectClass);
684         if (entry != null) {
685             titleAttribute = entry.getTitleAttribute();
686         }
687         return titleAttribute;
688     }
689 
690     /**
691      * @param dataObjectClass
692      * @return DataObjectEntry for the given dataObjectClass, or null if
693      *         there is none
694      * @throws IllegalArgumentException if the given Class is null
695      */
696     protected DataObjectEntry getDataObjectEntry(Class<?> dataObjectClass) {
697         if (dataObjectClass == null) {
698             throw new IllegalArgumentException("invalid (null) dataObjectClass");
699         }
700 
701         DataObjectEntry entry = dataDictionaryService.getDataDictionary().getDataObjectEntry(dataObjectClass.getName());
702 
703         return entry;
704     }
705 
706     /**
707      * @see org.kuali.rice.krad.service.LegacyDataAdapter#areNotesSupported(Class)
708      */
709     @Override
710     public boolean areNotesSupported(Class<?> dataObjectClass) {
711         boolean hasNotesSupport = false;
712 
713         DataObjectEntry entry = getDataObjectEntry(dataObjectClass);
714         if (entry != null) {
715             hasNotesSupport = entry.isBoNotesEnabled();
716         }
717 
718         return hasNotesSupport;
719     }
720 
721     /**
722      * Grabs primary key fields and sorts them if sort field names is true
723      *
724      * @param dataObject
725      * @param sortFieldNames
726      * @return Map of sorted primary key field values
727      */
728     public Map<String, ?> getPrimaryKeyFieldValues(Object dataObject, boolean sortFieldNames) {
729         Map<String, Object> keyFieldValues = (Map<String, Object>) getPrimaryKeyFieldValues(dataObject);
730         if (sortFieldNames) {
731             Map<String, Object> sortedKeyFieldValues = new TreeMap<String, Object>();
732             sortedKeyFieldValues.putAll(keyFieldValues);
733             return sortedKeyFieldValues;
734         }
735         return keyFieldValues;
736     }
737 
738     /**
739      * @see org.kuali.rice.krad.service.DataObjectMetaDataService#getDataObjectIdentifierString
740      */
741     @Override
742     public String getDataObjectIdentifierString(Object dataObject) {
743         String identifierString = "";
744 
745         if (dataObject == null) {
746             identifierString = "Null";
747             return identifierString;
748         }
749 
750         Class<?> dataObjectClass = dataObject.getClass();
751         // if Legacy and a PersistableBusinessObject or if not Legacy and implement GlobalLyUnique use the object id field
752         if ((PersistableBusinessObject.class.isAssignableFrom(
753                 dataObjectClass)) || (!LegacyUtils.useLegacyForObject(dataObject) && GloballyUnique.class
754                 .isAssignableFrom(dataObjectClass))) {
755             String objectId = ObjectPropertyUtils.getPropertyValue(dataObject, UifPropertyPaths.OBJECT_ID);
756             if (StringUtils.isBlank(objectId)) {
757                 objectId = UUID.randomUUID().toString();
758                 ObjectPropertyUtils.setPropertyValue(dataObject, UifPropertyPaths.OBJECT_ID, objectId);
759             }
760         }
761         return identifierString;
762     }
763 
764     @Override
765     public Class<?> getInquiryObjectClassIfNotTitle(Object dataObject, String propertyName) {
766         Class<?> objectClass = ObjectUtils.materializeClassForProxiedObject(dataObject);
767         org.kuali.rice.krad.bo.DataObjectRelationship relationship =
768                 dataObjectMetaDataService.getDataObjectRelationship(dataObject, objectClass, propertyName, "", true,
769                         false, true);
770         if (relationship != null) {
771             return relationship.getRelatedClass();
772         }
773         return null;
774     }
775 
776     @Override
777     public Map<String, String> getInquiryParameters(Object dataObject, List<String> keys, String propertyName) {
778         Map<String, String> inquiryParameters = new HashMap<String, String>();
779         Class<?> objectClass = ObjectUtils.materializeClassForProxiedObject(dataObject);
780         org.kuali.rice.krad.bo.DataObjectRelationship relationship =
781                 dataObjectMetaDataService.getDataObjectRelationship(dataObject, objectClass, propertyName, "", true,
782                         false, true);
783         for (String keyName : keys) {
784             String keyConversion = keyName;
785             if (relationship != null) {
786                 keyConversion = relationship.getParentAttributeForChildAttribute(keyName);
787             } else if (PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName)) {
788                 String nestedAttributePrefix = KRADUtils.getNestedAttributePrefix(propertyName);
789                 keyConversion = nestedAttributePrefix + "." + keyName;
790             }
791             inquiryParameters.put(keyConversion, keyName);
792         }
793         return inquiryParameters;
794     }
795 
796     @Override
797     public boolean hasLocalLookup(Class<?> dataObjectClass) {
798         return dataObjectMetaDataService.hasLocalLookup(dataObjectClass);
799     }
800 
801     @Override
802     public boolean hasLocalInquiry(Class<?> dataObjectClass) {
803         return dataObjectMetaDataService.hasLocalInquiry(dataObjectClass);
804     }
805 
806     @Override
807     public org.kuali.rice.krad.bo.DataObjectRelationship getDataObjectRelationship(Object dataObject,
808                                                                                    Class<?> dataObjectClass, String attributeName, String attributePrefix, boolean keysOnly,
809                                                                                    boolean supportsLookup, boolean supportsInquiry) {
810         RelationshipDefinition ddReference = getDictionaryRelationship(dataObjectClass, attributeName);
811         return dataObjectMetaDataService.getDataObjectRelationship(dataObject, dataObjectClass, attributeName,
812                 attributePrefix, keysOnly, supportsLookup, supportsInquiry);
813 
814     }
815 
816 
817 
818     protected org.kuali.rice.krad.bo.DataObjectRelationship populateRelationshipFromDictionaryReference(Class<?> dataObjectClass,
819                                                                                                         RelationshipDefinition ddReference, String attributePrefix, boolean keysOnly) {
820         org.kuali.rice.krad.bo.DataObjectRelationship relationship = new org.kuali.rice.krad.bo.DataObjectRelationship(dataObjectClass,
821                 ddReference.getObjectAttributeName(), ddReference.getTargetClass());
822 
823         for (PrimitiveAttributeDefinition def : ddReference.getPrimitiveAttributes()) {
824             if (StringUtils.isNotBlank(attributePrefix)) {
825                 relationship.getParentToChildReferences().put(attributePrefix + "." + def.getSourceName(),
826                         def.getTargetName());
827             } else {
828                 relationship.getParentToChildReferences().put(def.getSourceName(), def.getTargetName());
829             }
830         }
831 
832         if (!keysOnly) {
833             for (SupportAttributeDefinition def : ddReference.getSupportAttributes()) {
834                 if (StringUtils.isNotBlank(attributePrefix)) {
835                     relationship.getParentToChildReferences().put(attributePrefix + "." + def.getSourceName(),
836                             def.getTargetName());
837                     if (def.isIdentifier()) {
838                         relationship.setUserVisibleIdentifierKey(attributePrefix + "." + def.getSourceName());
839                     }
840                 } else {
841                     relationship.getParentToChildReferences().put(def.getSourceName(), def.getTargetName());
842                     if (def.isIdentifier()) {
843                         relationship.setUserVisibleIdentifierKey(def.getSourceName());
844                     }
845                 }
846             }
847         }
848 
849         return relationship;
850     }
851 
852     @Override
853     public boolean isPersistable(Class<?> dataObjectClass) {
854         return persistenceStructureService.isPersistable(dataObjectClass);
855     }
856 
857     @Override
858     public void setObjectPropertyDeep(Object bo, String propertyName, Class type,
859                                       Object propertyValue) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
860         ObjectUtils.setObjectPropertyDeep(bo,propertyName,type,propertyValue);
861     }
862 
863     protected org.kuali.rice.krad.bo.DataObjectRelationship getRelationshipMetadata(Class<?> dataObjectClass,
864                                                                                     String attributeName, String attributePrefix) {
865 
866         RelationshipDefinition relationshipDefinition = getDictionaryRelationship(dataObjectClass, attributeName);
867         if (relationshipDefinition == null) {
868             return null;
869         }
870 
871         org.kuali.rice.krad.bo.DataObjectRelationship dataObjectRelationship =
872                 new org.kuali.rice.krad.bo.DataObjectRelationship(relationshipDefinition.getSourceClass(),
873                         relationshipDefinition.getObjectAttributeName(), relationshipDefinition.getTargetClass());
874 
875         if (!StringUtils.isEmpty(attributePrefix)) {
876             attributePrefix += ".";
877         }
878 
879         List<PrimitiveAttributeDefinition> primitives = relationshipDefinition.getPrimitiveAttributes();
880         for (PrimitiveAttributeDefinition primitiveAttributeDefinition : primitives) {
881             dataObjectRelationship.getParentToChildReferences().put(
882                     attributePrefix + primitiveAttributeDefinition.getSourceName(),
883                     primitiveAttributeDefinition.getTargetName());
884         }
885 
886         return dataObjectRelationship;
887     }
888 
889     protected boolean classHasSupportedFeatures(Class relationshipClass, boolean supportsLookup,
890                                                 boolean supportsInquiry) {
891         boolean hasSupportedFeatures = true;
892         if (supportsLookup && !getViewDictionaryService().isLookupable(relationshipClass)) {
893             hasSupportedFeatures = false;
894         }
895         if (supportsInquiry && !getViewDictionaryService().isInquirable(relationshipClass)) {
896             hasSupportedFeatures = false;
897         }
898 
899         return hasSupportedFeatures;
900     }
901 
902     @Override
903     public boolean isNull(Object object){
904         return ObjectUtils.isNull(object);
905     }
906 
907     @Override
908     public void setObjectProperty(Object bo, String propertyName, Class propertyType,
909                                   Object propertyValue) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException{
910         ObjectUtils.setObjectProperty(bo,propertyName,propertyType,propertyValue);
911     }
912 
913     @Override
914     public Class materializeClassForProxiedObject(Object object){
915         return ObjectUtils.materializeClassForProxiedObject(object);
916     }
917 
918     @Override
919     public Object getNestedValue(Object bo, String fieldName){
920         return ObjectUtils.getNestedValue(bo,fieldName);
921     }
922 
923     @Override
924     public Object createNewObjectFromClass(Class clazz){
925         return ObjectUtils.createNewObjectFromClass(clazz);
926     }
927 
928     @Override
929     public ForeignKeyFieldsPopulationState getForeignKeyFieldsPopulationState(Object dataObject, String referenceName) {
930         return persistenceStructureService.getForeignKeyFieldsPopulationState(
931                 (PersistableBusinessObject) dataObject, referenceName);
932     }
933 
934     @Override
935     public Map<String, String> getForeignKeysForReference(Class<?> clazz, String attributeName) {
936         return persistenceStructureService.getForeignKeysForReference(clazz, attributeName);
937     }
938 
939     @Override
940     public boolean hasPrimaryKeyFieldValues(Object dataObject) {
941         return persistenceStructureService.hasPrimaryKeyFieldValues(dataObject);
942     }
943 
944     @Override
945     public <T extends Document> T findByDocumentHeaderId(Class<T> documentClass, String id) {
946         return documentDao.findByDocumentHeaderId(documentClass, id);
947     }
948 
949     @Override
950     public <T extends Document> List<T> findByDocumentHeaderIds(Class<T> documentClass, List<String> ids) {
951         return documentDao.findByDocumentHeaderIds(documentClass, ids);
952     }
953 
954 
955     @Required
956     public void setBusinessObjectService(BusinessObjectService businessObjectService) {
957         this.businessObjectService = businessObjectService;
958     }
959 
960     @Required
961     public void setPersistenceService(PersistenceService persistenceService) {
962         this.persistenceService = persistenceService;
963     }
964 
965     @Required
966     public void setLookupDao(LookupDao lookupDao) {
967         this.lookupDao = lookupDao;
968     }
969 
970     @Required
971     public void setLookupCriteriaGenerator(LookupCriteriaGenerator lookupCriteriaGenerator) {
972         this.lookupCriteriaGenerator = lookupCriteriaGenerator;
973     }
974 
975     @Required
976     public void setDateTimeService(DateTimeService dts) {
977         this.dateTimeService = dts;
978     }
979 
980     @Required
981     public void setDatabasePlatform(DatabasePlatform databasePlatform) {
982         this.databasePlatform = databasePlatform;
983     }
984 
985     @Required
986     public void setMaintenanceDocumentDaoOjb(MaintenanceDocumentDao maintenanceDocumentDaoOjb) {
987         this.maintenanceDocumentDaoOjb = maintenanceDocumentDaoOjb;
988     }
989 
990     //@Required
991     //public void setNoteDaoOjb(NoteDao noteDaoOjb) {
992     //    this.noteDaoOjb = noteDaoOjb;
993     //}
994 
995     @Required
996     public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) {
997         this.persistenceStructureService = persistenceStructureService;
998     }
999 
1000     @Required
1001     public void setDataObjectMetaDataService(DataObjectMetaDataService dataObjectMetaDataService) {
1002         this.dataObjectMetaDataService = dataObjectMetaDataService;
1003     }
1004 
1005     @Required
1006     public void setKualiConfigurationService(ConfigurationService kualiConfigurationService) {
1007         this.kualiConfigurationService = kualiConfigurationService;
1008     }
1009 
1010     @Required
1011     public void setKualiModuleService(KualiModuleService kualiModuleService) {
1012         this.kualiModuleService = kualiModuleService;
1013     }
1014 
1015     @Required
1016     public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
1017         this.dataDictionaryService = dataDictionaryService;
1018     }
1019 
1020     @Required
1021     public void setDocumentDao(DocumentDao documentDao) {
1022         this.documentDao = documentDao;
1023     }
1024 
1025     public DataObjectService getDataObjectService() {
1026         return dataObjectService;
1027     }
1028 
1029     public void setDataObjectService(DataObjectService dataObjectService) {
1030         this.dataObjectService = dataObjectService;
1031     }
1032 
1033     public ViewDictionaryService getViewDictionaryService() {
1034         return viewDictionaryService;
1035     }
1036 
1037     public void setViewDictionaryService(ViewDictionaryService viewDictionaryService) {
1038         this.viewDictionaryService = viewDictionaryService;
1039     }
1040 
1041     /**
1042      * Holds a reference to a property Field on a JPA entity and the associated EclipseLink {@link org.eclipse.persistence.indirection.ValueHolder} field.
1043      *
1044      * <p>This exists to support the issue of using a data object with both OJB and JPA. EclipseLink will "instrument"
1045      * the entity class using load-time or static weaving (depending on what's been configured). For fields which are
1046      * FetchType.LAZY, EclipseLink will weave a private internal field with a name like "_persistence_propertyName_vh"
1047      * where "propertyName" is the name of the property which is lazy. This type of this field is {@link org.eclipse.persistence.indirection.ValueHolder}.
1048      * In this case, if you call getPropertyName for the property, the internal code will pull the value from the
1049      * ValueHolder instead of the actual property field. Oftentimes this will be ok because the two fields will be in
1050      * sync, but when using methods like OJB's retrieveReference and retrieveAllReferences, after the "retrieve" these
1051      * values will be out of sync. So the {@link #synchronizeValueHolder(Object)} method on this class can be used to
1052      * synchronize these values and ensure that the local field has a value which matches the ValueHolder.</p>
1053      */
1054     private static final class ValueHolderFieldPair {
1055 
1056         final Field field;
1057         final Field valueHolderField;
1058 
1059         ValueHolderFieldPair(Field field, Field valueHolderField) {
1060             this.field = field;
1061             this.valueHolderField = valueHolderField;
1062         }
1063 
1064         void synchronizeValueHolder(Object object) {
1065             try {
1066                 ValueHolder valueHolder = (ValueHolder)valueHolderField.get(object);
1067                 if(valueHolder != null){
1068                     Object value = field.get(object);
1069                     valueHolder.setValue(value);
1070                 }
1071             } catch (IllegalAccessException e) {
1072                 throw new RuntimeException(e);
1073             }
1074         }
1075 
1076     }
1077 
1078 }
1079