001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krad.service.impl;
017
018import java.lang.reflect.Field;
019import java.lang.reflect.InvocationTargetException;
020import java.util.ArrayList;
021import java.util.Collection;
022import java.util.Collections;
023import java.util.Date;
024import java.util.HashMap;
025import java.util.Iterator;
026import java.util.List;
027import java.util.Map;
028import java.util.TreeMap;
029import java.util.UUID;
030import java.util.concurrent.ConcurrentHashMap;
031import java.util.concurrent.ConcurrentMap;
032import java.util.regex.Matcher;
033import java.util.regex.Pattern;
034
035import org.apache.commons.lang.StringUtils;
036import org.apache.commons.lang.reflect.FieldUtils;
037import org.eclipse.persistence.indirection.ValueHolder;
038import org.kuali.rice.core.api.config.property.ConfigurationService;
039import org.kuali.rice.core.api.criteria.QueryByCriteria;
040import org.kuali.rice.core.api.datetime.DateTimeService;
041import org.kuali.rice.core.api.exception.RiceRuntimeException;
042import org.kuali.rice.core.api.mo.common.GloballyUnique;
043import org.kuali.rice.core.api.mo.common.Versioned;
044import org.kuali.rice.core.api.search.SearchOperator;
045import org.kuali.rice.core.api.uif.RemotableQuickFinder;
046import org.kuali.rice.core.api.util.RiceKeyConstants;
047import org.kuali.rice.core.framework.persistence.ojb.conversion.OjbCharBooleanConversion;
048import org.kuali.rice.core.framework.persistence.platform.DatabasePlatform;
049import org.kuali.rice.krad.bo.BusinessObject;
050import org.kuali.rice.krad.bo.InactivatableFromTo;
051import org.kuali.rice.krad.bo.PersistableBusinessObject;
052import org.kuali.rice.krad.bo.PersistableBusinessObjectBaseAdapter;
053import org.kuali.rice.krad.bo.PersistableBusinessObjectExtension;
054import org.kuali.rice.krad.dao.DocumentDao;
055import org.kuali.rice.krad.dao.LookupDao;
056import org.kuali.rice.krad.dao.MaintenanceDocumentDao;
057import org.kuali.rice.krad.data.DataObjectService;
058import org.kuali.rice.krad.datadictionary.DataDictionaryEntry;
059import org.kuali.rice.krad.datadictionary.DataObjectEntry;
060import org.kuali.rice.krad.datadictionary.PrimitiveAttributeDefinition;
061import org.kuali.rice.krad.datadictionary.RelationshipDefinition;
062import org.kuali.rice.krad.datadictionary.SupportAttributeDefinition;
063import org.kuali.rice.krad.document.Document;
064import org.kuali.rice.krad.exception.ValidationException;
065import org.kuali.rice.krad.lookup.LookupUtils;
066import org.kuali.rice.krad.service.BusinessObjectService;
067import org.kuali.rice.krad.service.DataDictionaryService;
068import org.kuali.rice.krad.service.DataObjectMetaDataService;
069import org.kuali.rice.krad.service.KRADServiceLocator;
070import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
071import org.kuali.rice.krad.service.KualiModuleService;
072import org.kuali.rice.krad.service.LegacyDataAdapter;
073import org.kuali.rice.krad.service.ModuleService;
074import org.kuali.rice.krad.service.PersistenceService;
075import org.kuali.rice.krad.service.PersistenceStructureService;
076import org.kuali.rice.krad.uif.UifPropertyPaths;
077import org.kuali.rice.krad.uif.service.ViewDictionaryService;
078import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
079import org.kuali.rice.krad.util.ForeignKeyFieldsPopulationState;
080import org.kuali.rice.krad.util.GlobalVariables;
081import org.kuali.rice.krad.util.KRADConstants;
082import org.kuali.rice.krad.util.KRADPropertyConstants;
083import org.kuali.rice.krad.util.KRADUtils;
084import org.kuali.rice.krad.util.LegacyUtils;
085import org.kuali.rice.krad.util.ObjectUtils;
086import org.springframework.beans.PropertyAccessorUtils;
087import org.springframework.beans.factory.annotation.Required;
088
089/**
090*
091 */
092@Deprecated
093public class KNSLegacyDataAdapterImpl implements LegacyDataAdapter {
094    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(
095            KNSLegacyDataAdapterImpl.class);
096
097    private static final Pattern VALUE_HOLDER_FIELD_PATTERN = Pattern.compile("^_persistence_(.*)_vh$");
098
099    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}