001package org.kuali.rice.krad.service.impl;
002
003import java.beans.PropertyDescriptor;
004import java.lang.reflect.InvocationTargetException;
005import java.util.ArrayList;
006import java.util.Collection;
007import java.util.Collections;
008import java.util.Date;
009import java.util.HashMap;
010import java.util.Iterator;
011import java.util.List;
012import java.util.Map;
013import java.util.TreeMap;
014
015import org.apache.commons.beanutils.PropertyUtils;
016import org.apache.commons.lang.StringUtils;
017import org.kuali.rice.core.api.config.property.ConfigurationService;
018import org.kuali.rice.core.api.criteria.OrderByField;
019import org.kuali.rice.core.api.criteria.OrderDirection;
020import org.kuali.rice.core.api.criteria.QueryByCriteria;
021import org.kuali.rice.core.api.criteria.QueryResults;
022import org.kuali.rice.core.api.mo.common.Versioned;
023import org.kuali.rice.core.api.search.SearchOperator;
024import org.kuali.rice.core.api.uif.RemotableQuickFinder;
025import org.kuali.rice.core.api.util.RiceKeyConstants;
026import org.kuali.rice.core.framework.persistence.ojb.conversion.OjbCharBooleanConversion;
027import org.kuali.rice.krad.bo.BusinessObject;
028import org.kuali.rice.krad.bo.InactivatableFromTo;
029import org.kuali.rice.krad.bo.PersistableBusinessObjectExtension;
030import org.kuali.rice.krad.data.CompoundKey;
031import org.kuali.rice.krad.data.DataObjectService;
032import org.kuali.rice.krad.data.DataObjectWrapper;
033import org.kuali.rice.krad.data.KradDataServiceLocator;
034import org.kuali.rice.krad.data.PersistenceOption;
035import org.kuali.rice.krad.data.metadata.DataObjectAttributeRelationship;
036import org.kuali.rice.krad.data.metadata.DataObjectCollection;
037import org.kuali.rice.krad.data.metadata.DataObjectMetadata;
038import org.kuali.rice.krad.data.metadata.DataObjectRelationship;
039import org.kuali.rice.krad.data.provider.annotation.ExtensionFor;
040import org.kuali.rice.krad.datadictionary.DataDictionaryEntry;
041import org.kuali.rice.krad.datadictionary.DataObjectEntry;
042import org.kuali.rice.krad.datadictionary.PrimitiveAttributeDefinition;
043import org.kuali.rice.krad.datadictionary.RelationshipDefinition;
044import org.kuali.rice.krad.datadictionary.SupportAttributeDefinition;
045import org.kuali.rice.krad.document.Document;
046import org.kuali.rice.krad.exception.ValidationException;
047import org.kuali.rice.krad.lookup.LookupUtils;
048import org.kuali.rice.krad.service.DataDictionaryService;
049import org.kuali.rice.krad.service.DocumentAdHocService;
050import org.kuali.rice.krad.service.KRADServiceLocator;
051import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
052import org.kuali.rice.krad.service.KualiModuleService;
053import org.kuali.rice.krad.service.LegacyDataAdapter;
054import org.kuali.rice.krad.service.ModuleService;
055import org.kuali.rice.krad.uif.service.ViewDictionaryService;
056import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
057import org.kuali.rice.krad.util.ForeignKeyFieldsPopulationState;
058import org.kuali.rice.krad.util.GlobalVariables;
059import org.kuali.rice.krad.util.KRADConstants;
060import org.kuali.rice.krad.util.KRADPropertyConstants;
061import org.kuali.rice.krad.util.KRADUtils;
062import org.kuali.rice.krad.util.LegacyUtils;
063import org.springframework.beans.PropertyAccessorUtils;
064import org.springframework.beans.factory.annotation.Required;
065import org.springframework.dao.IncorrectResultSizeDataAccessException;
066
067/**
068 * Created by sheiksalahudeenm on 10/10/14.
069 *
070 * Overridden for fixing the issue in KRAD Transaction document/JPA which is having composite primary key.
071 * Modified method name: findByDocumentHeaderId.
072 * Changes description : Instead of passing single 'id' passing CompoundKey as a parameter.
073 */
074public class KRADLegacyDataAdapterImpl implements LegacyDataAdapter {
075    private DataObjectService dataObjectService;
076    private LookupCriteriaGenerator lookupCriteriaGenerator;
077
078    private ConfigurationService kualiConfigurationService;
079    private KualiModuleService kualiModuleService;
080    private DataDictionaryService dataDictionaryService;
081    private ViewDictionaryService viewDictionaryService;
082
083    @Override
084    public <T> T save(T dataObject) {
085        if (dataObject instanceof Collection) {
086            Collection<Object> newList = new ArrayList<Object>(((Collection) dataObject).size());
087            for (Object obj : (Collection<?>) dataObject) {
088                newList.add(save(obj));
089            }
090            return (T) newList;
091        } else {
092            return dataObjectService.save(dataObject);
093        }
094    }
095
096    @Override
097    public <T> T linkAndSave(T dataObject) {
098        // This method is only used from MaintainableImpl
099        return dataObjectService.save(dataObject, PersistenceOption.LINK_KEYS);
100    }
101
102    @Override
103    public <T> T saveDocument(T document) {
104        return dataObjectService.save(document, PersistenceOption.LINK_KEYS, PersistenceOption.FLUSH);
105    }
106
107    @Override
108    public <T> T findByPrimaryKey(Class<T> clazz, Map<String, ?> primaryKeys) {
109        return dataObjectService.find(clazz, new CompoundKey(primaryKeys));
110    }
111
112    @Override
113    public <T> T findBySinglePrimaryKey(Class<T> clazz, Object primaryKey) {
114        return dataObjectService.find(clazz, primaryKey);
115    }
116
117    @Override
118    public void delete(Object dataObject) {
119        if (dataObject instanceof Collection) {
120            for (Object dobj : (Collection) dataObject) {
121                delete(dobj);
122            }
123        } else {
124            dataObjectService.delete(dataObject);
125        }
126    }
127
128    @Override
129    public void deleteMatching(Class<?> type, Map<String, ?> fieldValues) {
130        dataObjectService.deleteMatching(type, QueryByCriteria.Builder.andAttributes(fieldValues).build());
131    }
132
133    @Override
134    public <T> T retrieve(T dataObject) {
135        Object id = null;
136        Map<String, Object> primaryKeyValues = dataObjectService.wrap(dataObject).getPrimaryKeyValues();
137        if (primaryKeyValues.isEmpty()) {
138            throw new IllegalArgumentException("Given data object has no primary key!");
139        }
140        if (primaryKeyValues.size() == 1) {
141            id = primaryKeyValues.values().iterator().next();
142        } else {
143            id = new CompoundKey(primaryKeyValues);
144        }
145        return dataObjectService.find((Class<T>) dataObject.getClass(), id);
146    }
147
148    @Override
149    public <T> Collection<T> findAll(Class<T> clazz) {
150        // just find all objects of given type without any attribute criteria
151        return findMatching(clazz, Collections.<String, Object>emptyMap());
152    }
153
154    @Override
155    public <T> Collection<T> findMatching(Class<T> clazz, Map<String, ?> fieldValues) {
156        QueryResults<T> result = dataObjectService.findMatching(clazz, QueryByCriteria.Builder.andAttributes(
157                fieldValues).build());
158        return result.getResults();
159    }
160
161    @Override
162    public <T> Collection<T> findMatchingOrderBy(Class<T> clazz, Map<String, ?> fieldValues, String sortField,
163                                                 boolean sortAscending) {
164        OrderDirection direction = sortAscending ? OrderDirection.ASCENDING : OrderDirection.DESCENDING;
165        OrderByField orderBy = OrderByField.Builder.create(sortField, direction).build();
166        QueryResults<T> result = dataObjectService.findMatching(clazz, QueryByCriteria.Builder.andAttributes(
167                fieldValues).setOrderByFields(orderBy).build());
168        return result.getResults();
169    }
170
171    @Override
172    public Map<String, ?> getPrimaryKeyFieldValues(Object dataObject) {
173        return dataObjectService.wrap(dataObject).getPrimaryKeyValues();
174    }
175
176    @Override
177    public void retrieveNonKeyFields(Object persistableObject) {
178        List<DataObjectRelationship> relationships = dataObjectService.getMetadataRepository().getMetadata(
179                persistableObject.getClass()).getRelationships();
180        for (DataObjectRelationship relationship : relationships) {
181            retrieveReferenceObject(persistableObject, relationship.getName());
182        }
183    }
184
185    @Override
186    public void retrieveReferenceObject(Object persistableObject, String referenceObjectName) {
187        dataObjectService.wrap(persistableObject).fetchRelationship(referenceObjectName);
188    }
189
190    @Override
191    public void refreshAllNonUpdatingReferences(Object persistableObject) {
192        List<DataObjectRelationship> nonUpdateableRelationships = findNonUpdateableRelationships(persistableObject);
193        for (DataObjectRelationship relationship : nonUpdateableRelationships) {
194            retrieveReferenceObject(persistableObject, relationship.getName());
195        }
196    }
197
198    protected List<DataObjectRelationship> findNonUpdateableRelationships(Object persistableObject) {
199        List<DataObjectRelationship> nonUpdateableRelationships = new ArrayList<DataObjectRelationship>();
200        DataObjectMetadata dataObjectMetadata = dataObjectService.getMetadataRepository().
201                getMetadata(persistableObject.getClass());
202        if (dataObjectMetadata != null) {
203            List<DataObjectRelationship> relationships = dataObjectMetadata.getRelationships();
204            for (DataObjectRelationship relationship : relationships) {
205                if (!relationship.isSavedWithParent()) {
206                    nonUpdateableRelationships.add(relationship);
207                }
208            }
209        }
210        return nonUpdateableRelationships;
211    }
212
213    @Override
214    public boolean isProxied(Object object) {
215        // KRAD data adapter does nothing
216        return false;
217    }
218
219    @Override
220    public Object resolveProxy(Object o) {
221        // KRAD data adapter does nothing
222        return o;
223    }
224
225    // Lookup methods
226
227    @Override
228    public <T> Collection<T> findCollectionBySearchHelper(Class<T> dataObjectClass, Map<String, String> formProperties,
229                                                          boolean unbounded, boolean allPrimaryKeyValuesPresentAndNotWildcard, Integer searchResultsLimit) {
230        return performDataObjectServiceLookup(dataObjectClass, formProperties, unbounded,
231                allPrimaryKeyValuesPresentAndNotWildcard, searchResultsLimit);
232    }
233
234    @Override
235    public <T> Collection<T> findCollectionBySearchHelper(Class<T> dataObjectClass, Map<String, String> formProperties,
236                                                          List<String> wildcardAsLiteralPropertyNames, boolean unbounded,
237                                                          boolean allPrimaryKeyValuesPresentAndNotWildcard, Integer searchResultsLimit) {
238        return performDataObjectServiceLookup(dataObjectClass, formProperties, wildcardAsLiteralPropertyNames,
239                unbounded, allPrimaryKeyValuesPresentAndNotWildcard, searchResultsLimit);
240    }
241
242    /**
243     * Our new DataObjectService-based lookup implementation
244     *
245     * @param dataObjectClass the dataobject class
246     * @param formProperties the incoming lookup form properties
247     * @param unbounded whether the search is unbounded
248     * @param searchResultsLimit the searchResultsLimit; null implies use of default KNS value if set for the class
249     * @param <T> the data object type
250     * @return collection of lookup results
251     */
252    protected <T> Collection<T> performDataObjectServiceLookup(Class<T> dataObjectClass,
253                                                               Map<String, String> formProperties, boolean unbounded, boolean allPrimaryKeyValuesPresentAndNotWildcard,
254                                                               Integer searchResultsLimit) {
255        if (!unbounded && searchResultsLimit == null) {
256            // use KRAD LookupUtils.getSearchResultsLimit instead of KNS version. we have no LookupForm, so pass null, only the class will be used
257            //searchResultsLimit = LookupUtils.getSearchResultsLimit(example, null);
258            searchResultsLimit = org.kuali.rice.kns.lookup.LookupUtils.getSearchResultsLimit(dataObjectClass);
259        }
260        QueryByCriteria.Builder query = lookupCriteriaGenerator.generateCriteria(dataObjectClass, formProperties,
261                allPrimaryKeyValuesPresentAndNotWildcard);
262        if (!unbounded && searchResultsLimit != null) {
263            query.setMaxResults(searchResultsLimit);
264        }
265
266        Collection<T> results = dataObjectService.findMatching(dataObjectClass, query.build()).getResults();
267        return filterCurrentDataObjects(dataObjectClass, results, formProperties);
268    }
269
270    /**
271     * Our newer DataObjectService-based lookup implementation
272     *
273     * @param dataObjectClass the dataobject class
274     * @param formProperties the incoming lookup form properties
275     * @param wildcardAsLiteralPropertyNames list of the lookup properties with wildcard characters disabled
276     * @param unbounded whether the search is unbounded
277     * @param searchResultsLimit the searchResultsLimit; null implies use of default KNS value if set for the class
278     * @param <T> the data object type
279     * @return collection of lookup results
280     */
281    protected <T> Collection<T> performDataObjectServiceLookup(Class<T> dataObjectClass,
282                                                               Map<String, String> formProperties, List<String> wildcardAsLiteralPropertyNames, boolean unbounded,
283                                                               boolean allPrimaryKeyValuesPresentAndNotWildcard, Integer searchResultsLimit) {
284        if (!unbounded && searchResultsLimit == null) {
285            // use KRAD LookupUtils.getSearchResultsLimit instead of KNS version. we have no LookupForm, so pass null, only the class will be used
286            //searchResultsLimit = LookupUtils.getSearchResultsLimit(example, null);
287            searchResultsLimit = org.kuali.rice.kns.lookup.LookupUtils.getSearchResultsLimit(dataObjectClass);
288        }
289
290        QueryByCriteria.Builder query = lookupCriteriaGenerator.generateCriteria(dataObjectClass, formProperties,
291                wildcardAsLiteralPropertyNames, allPrimaryKeyValuesPresentAndNotWildcard);
292        if (!unbounded && searchResultsLimit != null) {
293            query.setMaxResults(searchResultsLimit);
294        }
295
296        Collection<T> results = dataObjectService.findMatching(dataObjectClass, query.build()).getResults();
297        return filterCurrentDataObjects(dataObjectClass, results, formProperties);
298    }
299
300    protected <T> Collection<T> filterCurrentDataObjects(Class<T> dataObjectClass, Collection<T> unfiltered,
301                                                         Map<String, String> formProps) {
302        if (InactivatableFromTo.class.isAssignableFrom(dataObjectClass)) {
303            Boolean currentSpecifier = lookupCriteriaCurrentSpecifier(formProps);
304            if (currentSpecifier != null) {
305                List<InactivatableFromTo> onlyCurrent =
306                        KRADServiceLocator.getInactivateableFromToService().filterOutNonCurrent(new ArrayList(
307                                unfiltered), new Date(LookupUtils.getActiveDateTimestampForCriteria(formProps)
308                                .getTime()));
309                if (currentSpecifier) {
310                    return (Collection<T>) onlyCurrent;
311                } else {
312                    unfiltered.removeAll(onlyCurrent);
313                    return unfiltered;
314                }
315            }
316        }
317        return unfiltered;
318    }
319
320    protected Boolean lookupCriteriaCurrentSpecifier(Map<String, String> formProps) {
321        String value = formProps.get(KRADPropertyConstants.CURRENT);
322        if (StringUtils.isNotBlank(value)) {
323            // FIXME: use something more portable than this direct OJB converter
324            String currentBooleanStr = (String) new OjbCharBooleanConversion().javaToSql(value);
325            if (OjbCharBooleanConversion.DATABASE_BOOLEAN_TRUE_STRING_REPRESENTATION.equals(currentBooleanStr)) {
326                return Boolean.TRUE;
327            } else if (OjbCharBooleanConversion.DATABASE_BOOLEAN_FALSE_STRING_REPRESENTATION.equals(
328                    currentBooleanStr)) {
329                return Boolean.FALSE;
330            }
331        }
332        return null;
333    }
334
335    @Override
336    public <T> T findObjectBySearch(Class<T> type, Map<String, String> formProps) {
337        // This is the strictly Lookup-compatible way of constructing the criteria
338        // from a map of properties, which performs some minor logic such as only including
339        // non-blank and "writable" properties, as well as minor type coercions of string values
340        QueryByCriteria.Builder queryByCriteria = lookupCriteriaGenerator.createObjectCriteriaFromMap(type, formProps);
341        List<T> results = dataObjectService.findMatching(type, queryByCriteria.build()).getResults();
342        if (results.isEmpty()) {
343            return null;
344        }
345        if (results.size() != 1) {
346            // this behavior is different from the legacy OJB behavior in that it throws an exception if more than
347            // one result from such a single object query
348            throw new IncorrectResultSizeDataAccessException("Incorrect number of results returned when finding object",
349                    1, results.size());
350        }
351        return results.get(0);
352    }
353
354    /**
355     * Returns whether all primary key values are specified in the lookup  map and do not contain any wildcards
356     *
357     * @param boClass the bo class to lookup
358     * @param formProps the incoming form/lookup properties
359     * @return whether all primary key values are specified in the lookup  map and do not contain any wildcards
360     */
361    @Override
362    public boolean allPrimaryKeyValuesPresentAndNotWildcard(Class<?> boClass, Map<String, String> formProps) {
363        List<String> pkFields = listPrimaryKeyFieldNames(boClass);
364        Iterator<String> pkIter = pkFields.iterator();
365        boolean returnVal = true;
366        while (returnVal && pkIter.hasNext()) {
367            String pkName = pkIter.next();
368            String pkValue = formProps.get(pkName);
369
370            if (StringUtils.isBlank(pkValue)) {
371                returnVal = false;
372            } else {
373                for (SearchOperator op : SearchOperator.QUERY_CHARACTERS) {
374                    if (pkValue.contains(op.op())) {
375                        returnVal = false;
376                        break;
377                    }
378                }
379            }
380        }
381        return returnVal;
382    }
383
384    //    @Override
385    //    public Attachment getAttachmentByNoteId(Long noteId) {
386    //        // noteIdentifier is the PK of Attachment, so just look up by PK
387    //        return dataObjectService.find(Attachment.class, noteId);
388    //    }
389
390    @Override
391    public List<String> listPrimaryKeyFieldNames(Class<?> type) {
392        List<String> keys = Collections.emptyList();
393        if (dataObjectService.getMetadataRepository().contains(type)) {
394            keys = dataObjectService.getMetadataRepository().getMetadata(type).getPrimaryKeyAttributeNames();
395        } else {
396            // check the Data Dictionary for PK's of non-persisted objects
397            DataObjectEntry dataObjectEntry = dataDictionaryService.getDataDictionary().getDataObjectEntry(
398                    type.getName());
399            if (dataObjectEntry != null) {
400                List<String> pks = dataObjectEntry.getPrimaryKeys();
401                if (pks != null) {
402                    keys = pks;
403                }
404            } else {
405                ModuleService responsibleModuleService = kualiModuleService.getResponsibleModuleService(type);
406                if (responsibleModuleService != null && responsibleModuleService.isExternalizable(type)) {
407                    keys = responsibleModuleService.listPrimaryKeyFieldNames(type);
408                }
409            }
410        }
411        return keys;
412    }
413
414    /**
415     * LookupServiceImpl calls BusinessObjectMetaDataService to listPrimaryKeyFieldNames.
416     * The BusinessObjectMetaDataService goes beyond the PersistenceStructureService to consult
417     * the associated ModuleService in determining the primary key field names.
418     * TODO: Do we need both listPrimaryKeyFieldNames/persistenceStructureService and
419     * listPrimaryKeyFieldNamesConsultingAllServices/businesObjectMetaDataService or
420     * can the latter superset be used for the former?
421     *
422     * @param type the data object class
423     * @return list of primary key field names, consulting persistence structure service, module service and
424     * datadictionary
425     */
426    protected List<String> listPrimaryKeyFieldNamesConsultingAllServices(Class<?> type) {
427        List<String> keys = new ArrayList<String>();
428        if (dataObjectService.getMetadataRepository().contains(type)) {
429            keys = dataObjectService.getMetadataRepository().getMetadata(type).getPrimaryKeyAttributeNames();
430        }
431        return keys;
432    }
433
434    @Override
435    public Class<?> determineCollectionObjectType(Class<?> containingType, String collectionPropertyName) {
436        final Class<?> collectionObjectType;
437        if (dataObjectService.getMetadataRepository().contains(containingType)) {
438            DataObjectMetadata metadata = dataObjectService.getMetadataRepository().getMetadata(containingType);
439            DataObjectCollection collection = metadata.getCollection(collectionPropertyName);
440            if (collection == null) {
441                throw new IllegalArgumentException(
442                        "Failed to locate a collection property with the given name: " + collectionPropertyName);
443            }
444            collectionObjectType = collection.getRelatedType();
445        } else {
446            throw new IllegalArgumentException(
447                    "Given containing class is not a valid data object, no metadata could be located for "
448                            + containingType.getName());
449        }
450        return collectionObjectType;
451
452    }
453
454    @Override
455    public boolean hasReference(Class<?> boClass, String referenceName) {
456        throw new UnsupportedOperationException("hasReference not valid for KRAD data operation");
457    }
458
459    @Override
460    public boolean hasCollection(Class<?> boClass, String collectionName) {
461        throw new UnsupportedOperationException("hasCollection not valid for KRAD data operation");
462    }
463
464    @Override
465    public boolean isExtensionAttribute(Class<?> boClass, String attributePropertyName, Class<?> propertyType) {
466        DataObjectMetadata metadata = dataObjectService.getMetadataRepository().getMetadata(boClass);
467        if (metadata != null) {
468            DataObjectRelationship relationship = metadata.getRelationship(attributePropertyName);
469            if (relationship != null) {
470                Class<?> relatedType = relationship.getRelatedType();
471                // right now, the only way to tell if an attribute is an extension is to check this annotation, the
472                // metadata repository does not currently store any such info that we can glom onto
473                ExtensionFor annotation = relatedType.getAnnotation(ExtensionFor.class);
474                if (annotation != null) {
475                    return annotation.value().equals(boClass);
476                }
477            }
478        }
479        return false;
480    }
481
482    @Override
483    public Class<?> getExtensionAttributeClass(Class<?> boClass, String attributePropertyName) {
484        DataObjectMetadata metadata = dataObjectService.getMetadataRepository().getMetadata(boClass);
485        if (metadata != null) {
486            DataObjectRelationship relationship = metadata.getRelationship(attributePropertyName);
487            if (relationship != null) {
488                return relationship.getRelatedType();
489            }
490        }
491        return null;
492    }
493
494    @Override
495    public Map<String, ?> getPrimaryKeyFieldValuesDOMDS(Object dataObject) {
496        return dataObjectService.wrap(dataObject).getPrimaryKeyValues();
497    }
498
499    @Override
500    public boolean equalsByPrimaryKeys(Object do1, Object do2) {
501        return dataObjectService.wrap(do1).equalsByPrimaryKey(do2);
502    }
503
504//    @Override
505//    public PersistableBusinessObject toPersistableBusinessObject(Object object) {
506//        throw new UnsupportedOperationException("toPersistableBusinessObject not valid for KRAD data operation");
507//    }
508
509    @Override
510    public void materializeAllSubObjects(Object object) {
511        DataObjectWrapper<?> wrappedObject = dataObjectService.wrap(object);
512
513        // Using 3 as that is what the KNS version of this method did
514        wrappedObject.materializeReferencedObjectsToDepth(3);
515    }
516
517    @Override
518    /**
519     * Recursively calls getPropertyTypeChild if nested property to allow it to properly look it up
520     */
521    public Class<?> getPropertyType(Object object, String propertyName) {
522        DataObjectWrapper wrappedObject = dataObjectService.wrap(object);
523        if (PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName)) {
524            return wrappedObject.getPropertyTypeNullSafe(wrappedObject.getWrappedClass(), propertyName);
525        }
526        return wrappedObject.getPropertyType(propertyName);
527    }
528
529    @Override
530    public Object getExtension(
531            Class<?> businessObjectClass) throws InstantiationException, IllegalAccessException {
532        DataObjectMetadata metadata = dataObjectService.getMetadataRepository().getMetadata(businessObjectClass);
533        DataObjectRelationship extensionRelationship = metadata.getRelationship("extension");
534        if (extensionRelationship != null) {
535            Class<?> extensionType = extensionRelationship.getRelatedType();
536            return extensionType.newInstance();
537        }
538        return null;
539    }
540
541    @Override
542    public void refreshReferenceObject(Object businessObject, String referenceObjectName) {
543        dataObjectService.wrap(businessObject).fetchRelationship(referenceObjectName);
544    }
545
546    @Override
547    public boolean isLockable(Object object) {
548        return isPersistable(object.getClass());
549    }
550
551    @Override
552    public void verifyVersionNumber(Object dataObject) {
553        DataObjectMetadata metadata = dataObjectService.getMetadataRepository().getMetadata(dataObject.getClass());
554        if (metadata == null) {
555            return;
556        }
557
558        if (metadata.isSupportsOptimisticLocking()) {
559            if (dataObject instanceof Versioned) {
560                Map<String, ?> keyPropertyValues = dataObjectService.wrap(dataObject).getPrimaryKeyValues();
561                CompoundKey key = new CompoundKey(keyPropertyValues);
562                Object persistableDataObject = null;
563                if (!key.hasNullKeyValues()) {
564                    persistableDataObject = dataObjectService.find(dataObject.getClass(), key);
565                }
566                // if it's null that means that this is an insert, not an update
567                if (persistableDataObject != null) {
568                    Long databaseVersionNumber = ((Versioned) persistableDataObject).getVersionNumber();
569                    Long documentVersionNumber = ((Versioned) dataObject).getVersionNumber();
570                    if (databaseVersionNumber != null && !(databaseVersionNumber.equals(documentVersionNumber))) {
571                        GlobalVariables.getMessageMap().putError(KRADConstants.GLOBAL_ERRORS,
572                                RiceKeyConstants.ERROR_VERSION_MISMATCH);
573                        throw new ValidationException(
574                                "Version mismatch between the local business object and the database business object");
575                    }
576                }
577            }
578        }
579    }
580
581    @Override
582    public RemotableQuickFinder.Builder createQuickFinder(Class<?> containingClass, String attributeName) {
583        return createQuickFinderNew(containingClass, attributeName);
584    }
585
586    /**
587     * New implementation of createQuickFinder which uses the new dataObjectService.getMetadataRepository().
588     */
589    protected RemotableQuickFinder.Builder createQuickFinderNew(Class<?> containingClass, String attributeName) {
590        if (dataObjectService.getMetadataRepository().contains(containingClass)) {
591
592            String lookupClassName = null;
593            Map<String, String> fieldConversions = new HashMap<String, String>();
594            Map<String, String> lookupParameters = new HashMap<String, String>();
595
596            DataObjectMetadata metadata = dataObjectService.getMetadataRepository().getMetadata(containingClass);
597            DataObjectRelationship relationship = metadata.getRelationshipByLastAttributeInRelationship(attributeName);
598            if (relationship != null) {
599                DataObjectMetadata lookupClassMetadata = dataObjectService.getMetadataRepository().getMetadata(
600                        relationship.getRelatedType());
601                lookupClassName = lookupClassMetadata.getClass().getName();
602                for (DataObjectAttributeRelationship attributeRelationship : relationship.getAttributeRelationships()) {
603
604                    // for field conversions, we map from the child attribute name to the parent attribute name because
605                    // whenever the value is returned from the object being looked up (child in this case) we want to
606                    // map the result back to the corresponding attributes on the "parent" object
607                    fieldConversions.put(attributeRelationship.getChildAttributeName(),
608                            attributeRelationship.getParentAttributeName());
609
610                    // for lookup parameters, we need to map the other direction since we are passing parameters *from* our parent
611                    // object *to* the child object
612                    lookupParameters.put(attributeRelationship.getParentAttributeName(),
613                            attributeRelationship.getChildAttributeName());
614                }
615                // in the legacy implementation of this, if there was a "userVisibleIdentifierKey" defined on
616                // the relationship, it would only add the lookup parameter for that key
617                //
618                // In krad-data, we recognize that related objects have business keys and we use that information
619                // to alter the lookup parameters (only) to pass the business key field(s) to the lookup
620                if (lookupClassMetadata.hasDistinctBusinessKey()) {
621                    lookupParameters.clear();
622                    for (String businessKeyAttributeName : lookupClassMetadata.getBusinessKeyAttributeNames()) {
623                        lookupParameters.put(relationship.getName() + "." + businessKeyAttributeName,
624                                businessKeyAttributeName);
625                    }
626                }
627            } else {
628                // check for primary display attribute attribute and if match build lookup to target class using primary key fields
629                String primaryDisplayAttributeName = metadata.getPrimaryDisplayAttributeName();
630                if (StringUtils.equals(primaryDisplayAttributeName, attributeName)) {
631                    lookupClassName = containingClass.getName();
632                    List<String> primaryKeyAttributes = metadata.getPrimaryKeyAttributeNames();
633                    for (String primaryKeyAttribute : primaryKeyAttributes) {
634                        fieldConversions.put(primaryKeyAttribute, primaryKeyAttribute);
635                        if (!StringUtils.equals(primaryKeyAttribute, attributeName)) {
636                            lookupParameters.put(primaryKeyAttribute, primaryKeyAttribute);
637                        }
638                    }
639                }
640            }
641
642            if (StringUtils.isNotBlank(lookupClassName)) {
643                String baseUrl = kualiConfigurationService.getPropertyValueAsString(KRADConstants.KRAD_LOOKUP_URL_KEY);
644                RemotableQuickFinder.Builder builder = RemotableQuickFinder.Builder.create(baseUrl, lookupClassName);
645                builder.setLookupParameters(lookupParameters);
646                builder.setFieldConversions(fieldConversions);
647                return builder;
648            }
649
650        }
651        return null;
652    }
653
654    @Override
655    public boolean isReferenceUpdatable(Class<?> type, String referenceName) {
656        if (dataObjectService.getMetadataRepository().contains(type)) {
657            DataObjectRelationship relationship = dataObjectService.getMetadataRepository().getMetadata(type)
658                    .getRelationship(referenceName);
659            if (relationship != null) {
660                return relationship.isSavedWithParent();
661            }
662        }
663        return false;
664    }
665
666    @SuppressWarnings("rawtypes")
667    @Override
668    public Map<String, Class> listReferenceObjectFields(Class<?> type) {
669        Map<String, Class> referenceNameToTypeMap = new HashMap<String, Class>();
670        if (dataObjectService.getMetadataRepository().contains(type)) {
671            List<DataObjectRelationship> relationships = dataObjectService.getMetadataRepository().getMetadata(type)
672                    .getRelationships();
673            for (DataObjectRelationship rel : relationships) {
674                referenceNameToTypeMap.put(rel.getName(), rel.getRelatedType());
675            }
676        }
677        return referenceNameToTypeMap;
678    }
679
680    @Override
681    public boolean isCollectionUpdatable(Class<?> type, String collectionName) {
682        if (dataObjectService.getMetadataRepository().contains(type)) {
683            DataObjectCollection collection = dataObjectService.getMetadataRepository().getMetadata(type).getCollection(
684                    collectionName);
685            if (collection != null) {
686                return collection.isSavedWithParent();
687            }
688        }
689        return false;
690    }
691
692    @Override
693    public Map<String, Class> listCollectionObjectTypes(Class<?> type) {
694        Map<String, Class> collectionNameToTypeMap = new HashMap<String, Class>();
695        if (dataObjectService.getMetadataRepository().contains(type)) {
696            List<DataObjectCollection> collections = dataObjectService.getMetadataRepository().getMetadata(type)
697                    .getCollections();
698            for (DataObjectCollection coll : collections) {
699                collectionNameToTypeMap.put(coll.getName(), coll.getRelatedType());
700            }
701        }
702        return collectionNameToTypeMap;
703    }
704
705    @Override
706    public Object getReferenceIfExists(Object bo, String referenceName) {
707        // fetches relationship if key is set and return populated value or null
708        DataObjectWrapper<Object> dataObjectWrapper = dataObjectService.wrap(bo);
709        dataObjectWrapper.fetchRelationship(referenceName);
710        return dataObjectWrapper.getPropertyValueNullSafe(referenceName);
711    }
712
713    @Override
714    public boolean allForeignKeyValuesPopulatedForReference(Object bo, String referenceName) {
715        Map<String, String> fkReferences = getForeignKeysForReference(bo.getClass(), referenceName);
716        if (fkReferences.size() > 0) {
717            DataObjectWrapper<Object> dataObjectWrapper = dataObjectService.wrap(bo);
718
719            for (String fkFieldName : fkReferences.keySet()) {
720                Object fkFieldValue = dataObjectWrapper.getForeignKeyAttributeValue(fkFieldName);
721                if (fkFieldValue == null) {
722                    return false;
723                } else if (fkFieldValue instanceof CompoundKey) {
724                    return !((CompoundKey) fkFieldValue).hasNullKeyValues();
725                } else if (String.class.isAssignableFrom(fkFieldValue.getClass())) {
726                    if (StringUtils.isBlank((String) fkFieldValue)) {
727                        return false;
728                    }
729                }
730            }
731        }
732
733        return true;
734    }
735
736    /**
737     * gets the relationship that the attribute represents on the class
738     *
739     * @param c - the class to which the attribute belongs
740     * @param attributeName - property name for the attribute
741     * @return a relationship definition for the attribute
742     */
743    @Override
744    public RelationshipDefinition getDictionaryRelationship(Class<?> c, String attributeName) {
745        DataDictionaryEntry entryBase =
746                KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getDictionaryObjectEntry(
747                        c.getName());
748        if (entryBase == null) {
749            return null;
750        }
751
752        RelationshipDefinition relationship = null;
753
754        List<RelationshipDefinition> ddRelationships = entryBase.getRelationships();
755
756        int minKeys = Integer.MAX_VALUE;
757        for (RelationshipDefinition def : ddRelationships) {
758            // favor key sizes of 1 first
759            if (def.getPrimitiveAttributes().size() == 1) {
760                for (PrimitiveAttributeDefinition primitive : def.getPrimitiveAttributes()) {
761                    if (primitive.getSourceName().equals(attributeName) || def.getObjectAttributeName().equals(
762                            attributeName)) {
763                        relationship = def;
764                        minKeys = 1;
765                        break;
766                    }
767                }
768            } else if (def.getPrimitiveAttributes().size() < minKeys) {
769                for (PrimitiveAttributeDefinition primitive : def.getPrimitiveAttributes()) {
770                    if (primitive.getSourceName().equals(attributeName) || def.getObjectAttributeName().equals(
771                            attributeName)) {
772                        relationship = def;
773                        minKeys = def.getPrimitiveAttributes().size();
774                        break;
775                    }
776                }
777            }
778        }
779
780        // check the support attributes
781        if (relationship == null) {
782            for (RelationshipDefinition def : ddRelationships) {
783                if (def.hasIdentifier()) {
784                    if (def.getIdentifier().getSourceName().equals(attributeName)) {
785                        relationship = def;
786                    }
787                }
788            }
789        }
790
791        return relationship;
792    }
793
794    /**
795     * @see org.kuali.rice.krad.service.LegacyDataAdapter
796     */
797    @Override
798    public String getTitleAttribute(Class<?> dataObjectClass) {
799        String titleAttribute = null;
800        DataObjectEntry entry = getDataObjectEntry(dataObjectClass);
801        if (entry != null) {
802            titleAttribute = entry.getTitleAttribute();
803        }
804        return titleAttribute;
805    }
806
807    /**
808     * @param dataObjectClass
809     * @return DataObjectEntry for the given dataObjectClass, or null if
810     * there is none
811     * @throws IllegalArgumentException if the given Class is null
812     */
813    protected DataObjectEntry getDataObjectEntry(Class<?> dataObjectClass) {
814        if (dataObjectClass == null) {
815            throw new IllegalArgumentException("invalid (null) dataObjectClass");
816        }
817
818        DataObjectEntry entry = dataDictionaryService.getDataDictionary().getDataObjectEntry(dataObjectClass.getName());
819
820        return entry;
821    }
822
823    /**
824     * @see org.kuali.rice.krad.service.LegacyDataAdapter#areNotesSupported(Class)
825     */
826    @Override
827    public boolean areNotesSupported(Class<?> dataObjectClass) {
828        boolean hasNotesSupport = false;
829
830        DataObjectEntry entry = getDataObjectEntry(dataObjectClass);
831        if (entry != null) {
832            hasNotesSupport = entry.isBoNotesEnabled();
833        }
834
835        return hasNotesSupport;
836    }
837
838    /**
839     * Grabs primary key fields and sorts them if sort field names is true
840     *
841     * @param dataObject
842     * @param sortFieldNames
843     * @return Map of sorted primary key field values
844     */
845    public Map<String, ?> getPrimaryKeyFieldValues(Object dataObject, boolean sortFieldNames) {
846        Map<String, Object> keyFieldValues = (Map<String, Object>) getPrimaryKeyFieldValues(dataObject);
847        if (sortFieldNames) {
848            Map<String, Object> sortedKeyFieldValues = new TreeMap<String, Object>();
849            sortedKeyFieldValues.putAll(keyFieldValues);
850            return sortedKeyFieldValues;
851        }
852        return keyFieldValues;
853    }
854
855    /**
856     * @see org.kuali.rice.krad.service.DataObjectMetaDataService#getDataObjectIdentifierString
857     */
858    @Override
859    public String getDataObjectIdentifierString(Object dataObject) {
860        String identifierString = "";
861
862        if (dataObject == null) {
863            identifierString = "Null";
864            return identifierString;
865        }
866
867        Class<?> dataObjectClass = dataObject.getClass();
868        // build identifier string from primary key values
869        Map<String, ?> primaryKeyFieldValues = getPrimaryKeyFieldValues(dataObject, true);
870        for (Map.Entry<String, ?> primaryKeyValue : primaryKeyFieldValues.entrySet()) {
871            if (primaryKeyValue.getValue() == null) {
872                identifierString += "Null";
873            } else {
874                identifierString += primaryKeyValue.getValue();
875            }
876            identifierString += ":";
877        }
878        return StringUtils.removeEnd(identifierString, ":");
879    }
880
881    @Override
882    public Class<?> getInquiryObjectClassIfNotTitle(Object dataObject, String propertyName) {
883        DataObjectMetadata objectMetadata =
884                KRADServiceLocator.getDataObjectService().getMetadataRepository().getMetadata(dataObject.getClass());
885        if (objectMetadata != null) {
886            DataObjectRelationship dataObjectRelationship =
887                    objectMetadata.getRelationship(propertyName);
888            if (dataObjectRelationship != null) {
889                return dataObjectRelationship.getRelatedType();
890            }
891        }
892        return null;
893    }
894
895    @Override
896    public Map<String, String> getInquiryParameters(Object dataObject, List<String> keys, String propertyName) {
897        Map<String, String> inquiryParameters = new HashMap<String, String>();
898        org.kuali.rice.krad.data.metadata.DataObjectRelationship dataObjectRelationship = null;
899
900        DataObjectMetadata objectMetadata =
901                KRADServiceLocator.getDataObjectService().getMetadataRepository().getMetadata(dataObject.getClass());
902
903        if (objectMetadata != null) {
904            dataObjectRelationship = objectMetadata.getRelationshipByLastAttributeInRelationship(propertyName);
905        }
906
907        for (String keyName : keys) {
908            String keyConversion = keyName;
909            if (dataObjectRelationship != null) {
910                keyConversion = dataObjectRelationship.getParentAttributeNameRelatedToChildAttributeName(keyName);
911            } else if (PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName)) {
912                String nestedAttributePrefix = KRADUtils.getNestedAttributePrefix(propertyName);
913                keyConversion = nestedAttributePrefix + "." + keyName;
914            }
915            inquiryParameters.put(keyConversion, keyName);
916        }
917        return inquiryParameters;
918    }
919
920    @Override
921    public boolean hasLocalLookup(Class<?> dataObjectClass) {
922        return viewDictionaryService.isLookupable(dataObjectClass);
923    }
924
925    @Override
926    public boolean hasLocalInquiry(Class<?> dataObjectClass) {
927        return viewDictionaryService.isInquirable(dataObjectClass);
928    }
929
930    @Override
931    public org.kuali.rice.krad.bo.DataObjectRelationship getDataObjectRelationship(Object dataObject,
932            Class<?> dataObjectClass, String attributeName, String attributePrefix, boolean keysOnly,
933            boolean supportsLookup, boolean supportsInquiry) {
934        RelationshipDefinition ddReference = getDictionaryRelationship(dataObjectClass, attributeName);
935
936        org.kuali.rice.krad.bo.DataObjectRelationship relationship = null;
937        DataObjectAttributeRelationship rel = null;
938        if (PropertyAccessorUtils.isNestedOrIndexedProperty(attributeName)) {
939            if (ddReference != null) {
940                if (classHasSupportedFeatures(ddReference.getTargetClass(), supportsLookup, supportsInquiry)) {
941                    relationship = populateRelationshipFromDictionaryReference(dataObjectClass, ddReference,
942                            attributePrefix, keysOnly);
943
944                    return relationship;
945                }
946            }
947
948            if (dataObject == null) {
949                try {
950                    dataObject = KRADUtils.createNewObjectFromClass(dataObjectClass);
951                } catch (RuntimeException e) {
952                    // found interface or abstract class, just swallow exception and return a null relationship
953                    return null;
954                }
955            }
956
957            // recurse down to the next object to find the relationship
958            int nextObjectIndex = PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(attributeName);
959            if (nextObjectIndex == StringUtils.INDEX_NOT_FOUND) {
960                nextObjectIndex = attributeName.length();
961            }
962            String localPrefix = StringUtils.substring(attributeName, 0, nextObjectIndex);
963            String localAttributeName = StringUtils.substring(attributeName, nextObjectIndex + 1);
964            Object nestedObject = ObjectPropertyUtils.getPropertyValue(dataObject, localPrefix);
965            Class<?> nestedClass = null;
966            if (nestedObject == null) {
967                nestedClass = ObjectPropertyUtils.getPropertyType(dataObject, localPrefix);
968            } else {
969                nestedClass = nestedObject.getClass();
970            }
971
972            String fullPrefix = localPrefix;
973            if (StringUtils.isNotBlank(attributePrefix)) {
974                fullPrefix = attributePrefix + "." + localPrefix;
975            }
976
977            relationship = getDataObjectRelationship(nestedObject, nestedClass, localAttributeName, fullPrefix,
978                    keysOnly, supportsLookup, supportsInquiry);
979
980            return relationship;
981        }
982
983        // non-nested reference, get persistence relationships first
984        int maxSize = Integer.MAX_VALUE;
985
986        if (isPersistable(dataObjectClass)) {
987            DataObjectMetadata metadata = dataObjectService.getMetadataRepository().getMetadata(dataObjectClass);
988            DataObjectRelationship dataObjectRelationship = metadata.getRelationship(attributeName);
989
990            if (dataObjectRelationship != null) {
991                List<DataObjectAttributeRelationship> attributeRelationships =
992                        dataObjectRelationship.getAttributeRelationships();
993                for (DataObjectAttributeRelationship dataObjectAttributeRelationship : attributeRelationships) {
994                    if (classHasSupportedFeatures(dataObjectRelationship.getRelatedType(), supportsLookup,
995                            supportsInquiry)) {
996                        maxSize = attributeRelationships.size();
997                        relationship = transformToDeprecatedDataObjectRelationship(dataObjectClass, attributeName,
998                                attributePrefix, dataObjectRelationship.getRelatedType(),
999                                dataObjectAttributeRelationship);
1000
1001                        break;
1002                    }
1003                }
1004            }
1005
1006        } else {
1007            ModuleService moduleService = kualiModuleService.getResponsibleModuleService(dataObjectClass);
1008            if (moduleService != null && moduleService.isExternalizable(dataObjectClass)) {
1009                relationship = getRelationshipMetadata(dataObjectClass, attributeName, attributePrefix);
1010                if ((relationship != null) && classHasSupportedFeatures(relationship.getRelatedClass(), supportsLookup,
1011                        supportsInquiry)) {
1012                    return relationship;
1013                } else {
1014                    return null;
1015                }
1016            }
1017        }
1018
1019        if (ddReference != null && ddReference.getPrimitiveAttributes().size() < maxSize) {
1020            if (classHasSupportedFeatures(ddReference.getTargetClass(), supportsLookup, supportsInquiry)) {
1021                relationship = populateRelationshipFromDictionaryReference(dataObjectClass, ddReference, null,
1022                        keysOnly);
1023            }
1024        }
1025        return relationship;
1026    }
1027
1028    protected org.kuali.rice.krad.bo.DataObjectRelationship transformToDeprecatedDataObjectRelationship(
1029            Class<?> dataObjectClass, String attributeName, String attributePrefix, Class<?> relatedObjectClass,
1030            DataObjectAttributeRelationship relationship) {
1031        org.kuali.rice.krad.bo.DataObjectRelationship rel = new org.kuali.rice.krad.bo.DataObjectRelationship(
1032                dataObjectClass, attributeName, relatedObjectClass);
1033        if (StringUtils.isBlank(attributePrefix)) {
1034            rel.getParentToChildReferences().put(relationship.getParentAttributeName(),
1035                    relationship.getChildAttributeName());
1036        } else {
1037            rel.getParentToChildReferences().put(attributePrefix + "." + relationship.getParentAttributeName(),
1038                    relationship.getChildAttributeName());
1039        }
1040
1041        return rel;
1042    }
1043
1044    protected org.kuali.rice.krad.bo.DataObjectRelationship populateRelationshipFromDictionaryReference(
1045            Class<?> dataObjectClass, RelationshipDefinition ddReference, String attributePrefix, boolean keysOnly) {
1046        org.kuali.rice.krad.bo.DataObjectRelationship relationship = new org.kuali.rice.krad.bo.DataObjectRelationship(
1047                dataObjectClass, ddReference.getObjectAttributeName(), ddReference.getTargetClass());
1048
1049        for (PrimitiveAttributeDefinition def : ddReference.getPrimitiveAttributes()) {
1050            if (StringUtils.isNotBlank(attributePrefix)) {
1051                relationship.getParentToChildReferences().put(attributePrefix + "." + def.getSourceName(),
1052                        def.getTargetName());
1053            } else {
1054                relationship.getParentToChildReferences().put(def.getSourceName(), def.getTargetName());
1055            }
1056        }
1057
1058        if (!keysOnly) {
1059            for (SupportAttributeDefinition def : ddReference.getSupportAttributes()) {
1060                if (StringUtils.isNotBlank(attributePrefix)) {
1061                    relationship.getParentToChildReferences().put(attributePrefix + "." + def.getSourceName(),
1062                            def.getTargetName());
1063                    if (def.isIdentifier()) {
1064                        relationship.setUserVisibleIdentifierKey(attributePrefix + "." + def.getSourceName());
1065                    }
1066                } else {
1067                    relationship.getParentToChildReferences().put(def.getSourceName(), def.getTargetName());
1068                    if (def.isIdentifier()) {
1069                        relationship.setUserVisibleIdentifierKey(def.getSourceName());
1070                    }
1071                }
1072            }
1073        }
1074
1075        return relationship;
1076    }
1077
1078    @Override
1079    public boolean isPersistable(Class<?> dataObjectClass) {
1080        return dataObjectService.getMetadataRepository().contains(dataObjectClass);
1081    }
1082
1083    protected org.kuali.rice.krad.bo.DataObjectRelationship getRelationshipMetadata(Class<?> dataObjectClass,
1084                                                                                    String attributeName, String attributePrefix) {
1085
1086        RelationshipDefinition relationshipDefinition = getDictionaryRelationship(dataObjectClass, attributeName);
1087        if (relationshipDefinition == null) {
1088            return null;
1089        }
1090
1091        org.kuali.rice.krad.bo.DataObjectRelationship dataObjectRelationship =
1092                new org.kuali.rice.krad.bo.DataObjectRelationship(relationshipDefinition.getSourceClass(),
1093                        relationshipDefinition.getObjectAttributeName(), relationshipDefinition.getTargetClass());
1094
1095        if (!StringUtils.isEmpty(attributePrefix)) {
1096            attributePrefix += ".";
1097        }
1098
1099        List<PrimitiveAttributeDefinition> primitives = relationshipDefinition.getPrimitiveAttributes();
1100        for (PrimitiveAttributeDefinition primitiveAttributeDefinition : primitives) {
1101            dataObjectRelationship.getParentToChildReferences().put(
1102                    attributePrefix + primitiveAttributeDefinition.getSourceName(),
1103                    primitiveAttributeDefinition.getTargetName());
1104        }
1105
1106        return dataObjectRelationship;
1107    }
1108
1109    protected boolean classHasSupportedFeatures(Class relationshipClass, boolean supportsLookup,
1110                                                boolean supportsInquiry) {
1111        boolean hasSupportedFeatures = true;
1112        if (supportsLookup && !getViewDictionaryService().isLookupable(relationshipClass)) {
1113            hasSupportedFeatures = false;
1114        }
1115        if (supportsInquiry && !getViewDictionaryService().isInquirable(relationshipClass)) {
1116            hasSupportedFeatures = false;
1117        }
1118
1119        return hasSupportedFeatures;
1120    }
1121
1122    @Override
1123    public ForeignKeyFieldsPopulationState getForeignKeyFieldsPopulationState(Object dataObject, String referenceName) {
1124        DataObjectWrapper<Object> dataObjectWrapper = dataObjectService.wrap(dataObject);
1125        return new ForeignKeyFieldsPopulationState(dataObjectWrapper.areAllPrimaryKeyAttributesPopulated(),
1126                dataObjectWrapper.areAnyPrimaryKeyAttributesPopulated(),
1127                dataObjectWrapper.getUnpopulatedPrimaryKeyAttributeNames());
1128    }
1129
1130    @Override
1131    public Map<String, String> getForeignKeysForReference(Class<?> clazz, String attributeName) {
1132        if (dataObjectService.getMetadataRepository().contains(clazz)) {
1133            DataObjectRelationship relationship = dataObjectService.getMetadataRepository().getMetadata(clazz)
1134                    .getRelationship(attributeName);
1135            List<DataObjectAttributeRelationship> attributeRelationships = relationship.getAttributeRelationships();
1136            Map<String, String> parentChildKeyRelationships = new HashMap<String, String>(
1137                    attributeRelationships.size());
1138            for (DataObjectAttributeRelationship doar : attributeRelationships) {
1139                parentChildKeyRelationships.put(doar.getParentAttributeName(), doar.getChildAttributeName());
1140            }
1141            return parentChildKeyRelationships;
1142        }
1143        return Collections.emptyMap();
1144    }
1145
1146    @Override
1147    public void setObjectPropertyDeep(Object bo, String propertyName, Class type,
1148                                      Object propertyValue) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
1149        DataObjectWrapper<Object> dataObjectWrapper = dataObjectService.wrap(bo);
1150        // Base return cases to avoid null pointers & infinite loops
1151        if (KRADUtils.isNull(bo) || !PropertyUtils.isReadable(bo, propertyName) || (propertyValue != null
1152                && propertyValue.equals(dataObjectWrapper.getPropertyValueNullSafe(propertyName))) || (type != null
1153                && !type.equals(KRADUtils.easyGetPropertyType(bo, propertyName)))) {
1154            return;
1155        }
1156        // Set the property in the BO
1157        KRADUtils.setObjectProperty(bo, propertyName, type, propertyValue);
1158
1159        // Now drill down and check nested BOs and BO lists
1160        PropertyDescriptor[] propertyDescriptors = PropertyUtils.getPropertyDescriptors(bo.getClass());
1161        for (int i = 0; i < propertyDescriptors.length; i++) {
1162
1163            PropertyDescriptor propertyDescriptor = propertyDescriptors[i];
1164
1165            // Business Objects
1166            if (propertyDescriptor.getPropertyType() != null && (BusinessObject.class).isAssignableFrom(
1167                    propertyDescriptor.getPropertyType()) && PropertyUtils.isReadable(bo,
1168                    propertyDescriptor.getName())) {
1169                Object nestedBo = dataObjectWrapper.getPropertyValueNullSafe(propertyDescriptor.getName());
1170                if (nestedBo instanceof BusinessObject) {
1171                    setObjectPropertyDeep(nestedBo, propertyName, type, propertyValue);
1172                }
1173            }
1174
1175            // Lists
1176            else if (propertyDescriptor.getPropertyType() != null && (List.class).isAssignableFrom(
1177                    propertyDescriptor.getPropertyType()) && dataObjectWrapper.getPropertyValueNullSafe(
1178                    propertyDescriptor.getName()) != null) {
1179
1180                List propertyList = (List) dataObjectWrapper.getPropertyValueNullSafe(propertyDescriptor.getName());
1181                for (Object listedBo : propertyList) {
1182                    if (listedBo != null && listedBo instanceof BusinessObject) {
1183                        setObjectPropertyDeep(listedBo, propertyName, type, propertyValue);
1184                    }
1185                } // end for
1186            }
1187        } // end for
1188    }
1189
1190    @Override
1191    public boolean hasPrimaryKeyFieldValues(Object dataObject) {
1192        DataObjectWrapper<Object> dataObjectWrapper = dataObjectService.wrap(dataObject);
1193        return dataObjectWrapper.areAllPrimaryKeyAttributesPopulated();
1194    }
1195
1196    @Override
1197    public Class materializeClassForProxiedObject(Object object) {
1198        if (object == null) {
1199            return null;
1200        }
1201        if (LegacyUtils.isKradDataManaged(object.getClass())) {
1202            Object o = resolveProxy(object);
1203            if (o != null) {
1204                return o.getClass();
1205            }
1206        }
1207        return object.getClass();
1208    }
1209
1210    @Override
1211    public Object getNestedValue(Object bo, String fieldName) {
1212        return KradDataServiceLocator.getDataObjectService().wrap(bo).getPropertyValueNullSafe(fieldName);
1213    }
1214
1215    @Override
1216    public Object createNewObjectFromClass(Class clazz) {
1217        if (clazz == null) {
1218            throw new IllegalArgumentException("Class was passed in as null");
1219        }
1220
1221        Object object = null;
1222
1223        try {
1224            object = clazz.newInstance();
1225        } catch (InstantiationException e) {
1226            throw new RuntimeException(e);
1227        } catch (IllegalAccessException e) {
1228            throw new RuntimeException(e);
1229        }
1230
1231        return object;
1232    }
1233
1234    @Override
1235    public boolean isNull(Object object) {
1236        return object == null;
1237    }
1238
1239    @Override
1240    public void setObjectProperty(Object bo, String propertyName, Class propertyType,
1241                                  Object propertyValue) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
1242        PropertyUtils.setNestedProperty(bo, propertyName, propertyValue);
1243    }
1244
1245    @Override
1246    public <T extends Document> T findByDocumentHeaderId(Class<T> documentClass, String id) {
1247        /* Modified by 'Sheik Salahudeen' for fixing issue in KRAD Transaction Document/JPA which is having composite primary key
1248        * Passing CompoundKey instead of single id*/
1249        Map<String,Object> parameterMap = new HashMap<>();
1250        parameterMap.put("documentNumber",id);
1251        T document = KRADServiceLocator.getDataObjectService().find(documentClass, new CompoundKey(parameterMap));
1252        // original KNS code always did this addAdHocs nonsense, so we'll do the same to preserve behavior
1253        ((DocumentAdHocService) KRADServiceLocatorWeb.getService("documentAdHocService")).addAdHocs(document);
1254        return document;
1255    }
1256
1257    @Override
1258    public <T extends Document> List<T> findByDocumentHeaderIds(Class<T> documentClass, List<String> ids) {
1259        List<T> documents = new ArrayList<T>();
1260        for (String id : ids) {
1261            documents.add(findByDocumentHeaderId(documentClass, id));
1262        }
1263        return documents;
1264    }
1265
1266    @Required
1267    public void setDataObjectService(DataObjectService dataObjectService) {
1268        this.dataObjectService = dataObjectService;
1269    }
1270
1271    @Required
1272    public void setLookupCriteriaGenerator(LookupCriteriaGenerator lookupCriteriaGenerator) {
1273        this.lookupCriteriaGenerator = lookupCriteriaGenerator;
1274    }
1275
1276    @Required
1277    public void setKualiConfigurationService(ConfigurationService kualiConfigurationService) {
1278        this.kualiConfigurationService = kualiConfigurationService;
1279    }
1280
1281    @Required
1282    public void setKualiModuleService(KualiModuleService kualiModuleService) {
1283        this.kualiModuleService = kualiModuleService;
1284    }
1285
1286    @Required
1287    public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
1288        this.dataDictionaryService = dataDictionaryService;
1289    }
1290
1291    public ViewDictionaryService getViewDictionaryService() {
1292        return viewDictionaryService;
1293    }
1294
1295    public void setViewDictionaryService(ViewDictionaryService viewDictionaryService) {
1296        this.viewDictionaryService = viewDictionaryService;
1297    }
1298
1299}