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