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