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