001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krad.service.impl;
017
018import java.util.ArrayList;
019import java.util.HashMap;
020import java.util.Iterator;
021import java.util.List;
022import java.util.Map;
023import java.util.TreeMap;
024import java.util.UUID;
025
026import org.apache.commons.lang.StringUtils;
027import org.kuali.rice.krad.bo.BusinessObject;
028import org.kuali.rice.krad.bo.DataObjectRelationship;
029import org.kuali.rice.krad.bo.PersistableBusinessObject;
030import org.kuali.rice.krad.datadictionary.BusinessObjectEntry;
031import org.kuali.rice.krad.datadictionary.DataDictionaryEntry;
032import org.kuali.rice.krad.datadictionary.DataObjectEntry;
033import org.kuali.rice.krad.datadictionary.PrimitiveAttributeDefinition;
034import org.kuali.rice.krad.datadictionary.RelationshipDefinition;
035import org.kuali.rice.krad.datadictionary.SupportAttributeDefinition;
036import org.kuali.rice.krad.service.DataDictionaryService;
037import org.kuali.rice.krad.service.DataObjectMetaDataService;
038import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
039import org.kuali.rice.krad.service.KualiModuleService;
040import org.kuali.rice.krad.service.ModuleService;
041import org.kuali.rice.krad.service.PersistenceStructureService;
042import org.kuali.rice.krad.uif.UifPropertyPaths;
043import org.kuali.rice.krad.uif.service.ViewDictionaryService;
044import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
045import org.kuali.rice.krad.util.KRADUtils;
046import org.kuali.rice.krad.util.LegacyDataFramework;
047import org.springframework.beans.PropertyAccessorUtils;
048
049/**
050 * @author Kuali Rice Team (rice.collab@kuali.org)
051 *
052 * @deprecated use new KRAD Data framework {@link org.kuali.rice.krad.data.DataObjectService},
053 *             replaced by metadata provider framework
054 */
055@Deprecated
056@LegacyDataFramework
057public class DataObjectMetaDataServiceImpl implements DataObjectMetaDataService {
058    private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(
059            DataObjectMetaDataServiceImpl.class);
060
061    private DataDictionaryService dataDictionaryService;
062    private KualiModuleService kualiModuleService;
063    private PersistenceStructureService persistenceStructureService;
064    private ViewDictionaryService viewDictionaryService;
065
066    @Override
067    public List<String> listPrimaryKeyFieldNames(Class<?> clazz) {
068        if (persistenceStructureService.isPersistable(clazz)) {
069            return persistenceStructureService.listPrimaryKeyFieldNames(clazz);
070        }
071
072        ModuleService responsibleModuleService = getKualiModuleService().getResponsibleModuleService(clazz);
073        if (responsibleModuleService != null && responsibleModuleService.isExternalizable(clazz)) {
074            return responsibleModuleService.listPrimaryKeyFieldNames(clazz);
075        }
076
077        // check the Data Dictionary for PK's of non PBO/EBO
078        List<String> pks = dataDictionaryService.getDataDictionary().getDataObjectEntry(clazz.getName())
079                .getPrimaryKeys();
080        if (pks != null && !pks.isEmpty()) {
081            return pks;
082        }
083
084        return new ArrayList<String>();
085    }
086
087    @Override
088    public Map<String, ?> getPrimaryKeyFieldValues(Object dataObject) {
089        return getPrimaryKeyFieldValues(dataObject, false);
090    }
091
092    @Override
093    public Map<String, ?> getPrimaryKeyFieldValues(Object dataObject, boolean sortFieldNames) {
094        Map<String, Object> keyValueMap;
095
096        if (sortFieldNames) {
097            keyValueMap = new TreeMap<String, Object>();
098        } else {
099            keyValueMap = new HashMap<String, Object>();
100        }
101
102        List<String> fields = listPrimaryKeyFieldNames(dataObject.getClass());
103        for (String fieldName : fields) {
104            keyValueMap.put(fieldName, ObjectPropertyUtils.getPropertyValue(dataObject, fieldName));
105        }
106
107        return keyValueMap;
108    }
109
110    @Override
111    public boolean equalsByPrimaryKeys(Object do1, Object do2) {
112        boolean equal = true;
113
114        if (do1 == null && do2 == null) {
115            equal = true;
116        } else if (do1 == null || do2 == null) {
117            equal = false;
118        } else if (!do1.getClass().getName().equals(do2.getClass().getName())) {
119            equal = false;
120        } else {
121            Map<String, ?> do1Keys = getPrimaryKeyFieldValues(do1);
122            Map<String, ?> do2Keys = getPrimaryKeyFieldValues(do2);
123            for (Iterator<String> iter = do1Keys.keySet().iterator(); iter.hasNext(); ) {
124                String keyName = iter.next();
125                if (do1Keys.get(keyName) != null && do2Keys.get(keyName) != null) {
126                    if (!do1Keys.get(keyName).toString().equals(do2Keys.get(keyName).toString())) {
127                        equal = false;
128                    }
129                } else {
130                    equal = false;
131                }
132            }
133        }
134
135        return equal;
136    }
137
138    @Override
139        public DataObjectRelationship getDataObjectRelationship(Object dataObject, Class<?> dataObjectClass,
140            String attributeName, String attributePrefix, boolean keysOnly, boolean supportsLookup,
141            boolean supportsInquiry) {
142        RelationshipDefinition ddReference = getDictionaryRelationship(dataObjectClass, attributeName);
143
144        return getDataObjectRelationship(ddReference, dataObject, dataObjectClass, attributeName, attributePrefix,
145                keysOnly, supportsLookup, supportsInquiry);
146    }
147
148    protected DataObjectRelationship getDataObjectRelationship(RelationshipDefinition ddReference, Object dataObject,
149            Class<?> dataObjectClass, String attributeName, String attributePrefix, boolean keysOnly,
150            boolean supportsLookup, boolean supportsInquiry) {
151        DataObjectRelationship relationship = null;
152
153        // if it is nested then replace the data object and attributeName with the
154        // sub-refs
155        if (PropertyAccessorUtils.isNestedOrIndexedProperty(attributeName)) {
156            if (ddReference != null) {
157                if (classHasSupportedFeatures(ddReference.getTargetClass(), supportsLookup, supportsInquiry)) {
158                    relationship = populateRelationshipFromDictionaryReference(dataObjectClass, ddReference,
159                            attributePrefix, keysOnly);
160
161                    return relationship;
162                }
163            }
164
165            // recurse down to the next object to find the relationship
166            String localPrefix = StringUtils.substringBefore(attributeName, ".");
167            String localAttributeName = StringUtils.substringAfter(attributeName, ".");
168            if (dataObject == null) {
169                try {
170                    dataObject = KRADUtils.createNewObjectFromClass(dataObjectClass);
171                } catch (RuntimeException e) {
172                    // found interface or abstract class, just swallow exception and return a null relationship
173                    return null;
174                }
175            }
176
177            Object nestedObject = ObjectPropertyUtils.getPropertyValue(dataObject, localPrefix);
178            Class<?> nestedClass = null;
179            if (nestedObject == null) {
180                nestedClass = ObjectPropertyUtils.getPropertyType(dataObject, localPrefix);
181            } else {
182                nestedClass = nestedObject.getClass();
183            }
184
185            String fullPrefix = localPrefix;
186            if (StringUtils.isNotBlank(attributePrefix)) {
187                fullPrefix = attributePrefix + "." + localPrefix;
188            }
189
190            relationship = getDataObjectRelationship(nestedObject, nestedClass, localAttributeName, fullPrefix,
191                    keysOnly, supportsLookup, supportsInquiry);
192
193            return relationship;
194        }
195
196        // non-nested reference, get persistence relationships first
197        int maxSize = Integer.MAX_VALUE;
198
199        // try persistable reference first
200        if (getPersistenceStructureService().isPersistable(dataObjectClass)) {
201            Map<String, DataObjectRelationship> rels = getPersistenceStructureService().getRelationshipMetadata(
202                    dataObjectClass, attributeName, attributePrefix);
203            if (rels.size() > 0) {
204                for (DataObjectRelationship rel : rels.values()) {
205                    if (rel.getParentToChildReferences().size() < maxSize) {
206                        if (classHasSupportedFeatures(rel.getRelatedClass(), supportsLookup, supportsInquiry)) {
207                            maxSize = rel.getParentToChildReferences().size();
208                            relationship = rel;
209                        }
210                    }
211                }
212            }
213        } else {
214            ModuleService moduleService = getKualiModuleService().getResponsibleModuleService(dataObjectClass);
215            if (moduleService != null && moduleService.isExternalizable(dataObjectClass)) {
216                relationship = getRelationshipMetadata(dataObjectClass, attributeName, attributePrefix);
217                if ((relationship != null) && classHasSupportedFeatures(relationship.getRelatedClass(), supportsLookup,
218                        supportsInquiry)) {
219                    return relationship;
220                } else {
221                    return null;
222                }
223            }
224        }
225
226        if (ddReference != null && ddReference.getPrimitiveAttributes().size() < maxSize) {
227            if (classHasSupportedFeatures(ddReference.getTargetClass(), supportsLookup, supportsInquiry)) {
228                relationship = populateRelationshipFromDictionaryReference(dataObjectClass, ddReference, null,
229                        keysOnly);
230            }
231        }
232
233        return relationship;
234    }
235
236    protected boolean classHasSupportedFeatures(Class relationshipClass, boolean supportsLookup,
237            boolean supportsInquiry) {
238        boolean hasSupportedFeatures = true;
239        if (supportsLookup && !getViewDictionaryService().isLookupable(relationshipClass)) {
240            hasSupportedFeatures = false;
241        }
242        if (supportsInquiry && !getViewDictionaryService().isInquirable(relationshipClass)) {
243            hasSupportedFeatures = false;
244        }
245
246        return hasSupportedFeatures;
247    }
248
249    /**
250     * gets the relationship that the attribute represents on the class
251     *
252     * @param c - the class to which the attribute belongs
253     * @param attributeName - property name for the attribute
254     *
255     * @return a relationship definition for the attribute
256     */
257    @Override
258        public RelationshipDefinition getDictionaryRelationship(Class<?> c, String attributeName) {
259        DataDictionaryEntry entryBase = getDataDictionaryService().getDataDictionary().getDictionaryObjectEntry(
260                c.getName());
261        if (entryBase == null) {
262            return null;
263        }
264
265        RelationshipDefinition relationship = null;
266
267        List<RelationshipDefinition> ddRelationships = entryBase.getRelationships();
268
269        int minKeys = Integer.MAX_VALUE;
270        for (RelationshipDefinition def : ddRelationships) {
271            // favor key sizes of 1 first
272            if (def.getPrimitiveAttributes().size() == 1) {
273                for (PrimitiveAttributeDefinition primitive : def.getPrimitiveAttributes()) {
274                    if (primitive.getSourceName().equals(attributeName) || def.getObjectAttributeName().equals(
275                            attributeName)) {
276                        relationship = def;
277                        minKeys = 1;
278                        break;
279                    }
280                }
281            } else if (def.getPrimitiveAttributes().size() < minKeys) {
282                for (PrimitiveAttributeDefinition primitive : def.getPrimitiveAttributes()) {
283                    if (primitive.getSourceName().equals(attributeName) || def.getObjectAttributeName().equals(
284                            attributeName)) {
285                        relationship = def;
286                        minKeys = def.getPrimitiveAttributes().size();
287                        break;
288                    }
289                }
290            }
291        }
292
293        // check the support attributes
294        if (relationship == null) {
295            for (RelationshipDefinition def : ddRelationships) {
296                if (def.hasIdentifier()) {
297                    if (def.getIdentifier().getSourceName().equals(attributeName)) {
298                        relationship = def;
299                    }
300                }
301            }
302        }
303
304        return relationship;
305    }
306
307    protected DataObjectRelationship populateRelationshipFromDictionaryReference(Class<?> dataObjectClass,
308            RelationshipDefinition ddReference, String attributePrefix, boolean keysOnly) {
309        DataObjectRelationship relationship = new DataObjectRelationship(dataObjectClass,
310                ddReference.getObjectAttributeName(), ddReference.getTargetClass());
311
312        for (PrimitiveAttributeDefinition def : ddReference.getPrimitiveAttributes()) {
313            if (StringUtils.isNotBlank(attributePrefix)) {
314                relationship.getParentToChildReferences().put(attributePrefix + "." + def.getSourceName(),
315                        def.getTargetName());
316            } else {
317                relationship.getParentToChildReferences().put(def.getSourceName(), def.getTargetName());
318            }
319        }
320
321        if (!keysOnly) {
322            for (SupportAttributeDefinition def : ddReference.getSupportAttributes()) {
323                if (StringUtils.isNotBlank(attributePrefix)) {
324                    relationship.getParentToChildReferences().put(attributePrefix + "." + def.getSourceName(),
325                            def.getTargetName());
326                    if (def.isIdentifier()) {
327                        relationship.setUserVisibleIdentifierKey(attributePrefix + "." + def.getSourceName());
328                    }
329                } else {
330                    relationship.getParentToChildReferences().put(def.getSourceName(), def.getTargetName());
331                    if (def.isIdentifier()) {
332                        relationship.setUserVisibleIdentifierKey(def.getSourceName());
333                    }
334                }
335            }
336        }
337
338        return relationship;
339    }
340
341    protected DataObjectRelationship getRelationshipMetadata(Class<?> dataObjectClass, String attributeName,
342            String attributePrefix) {
343
344        RelationshipDefinition relationshipDefinition = getDictionaryRelationship(dataObjectClass, attributeName);
345        if (relationshipDefinition == null) {
346            return null;
347        }
348
349        DataObjectRelationship dataObjectRelationship = new DataObjectRelationship(
350                relationshipDefinition.getSourceClass(), relationshipDefinition.getObjectAttributeName(),
351                relationshipDefinition.getTargetClass());
352
353        if (!StringUtils.isEmpty(attributePrefix)) {
354            attributePrefix += ".";
355        }
356
357        List<PrimitiveAttributeDefinition> primitives = relationshipDefinition.getPrimitiveAttributes();
358        for (PrimitiveAttributeDefinition primitiveAttributeDefinition : primitives) {
359            dataObjectRelationship.getParentToChildReferences().put(
360                    attributePrefix + primitiveAttributeDefinition.getSourceName(),
361                    primitiveAttributeDefinition.getTargetName());
362        }
363
364        return dataObjectRelationship;
365    }
366
367    /**
368     * @see org.kuali.rice.krad.service.DataObjectMetaDataService#getTitleAttribute(java.lang.Class)
369     */
370    @Override
371        public String getTitleAttribute(Class<?> dataObjectClass) {
372        String titleAttribute = null;
373
374        DataObjectEntry entry = getDataObjectEntry(dataObjectClass);
375        if (entry != null) {
376            titleAttribute = entry.getTitleAttribute();
377        }
378
379        return titleAttribute;
380    }
381
382    /**
383     * @see org.kuali.rice.krad.service.DataObjectMetaDataService#areNotesSupported(java.lang.Class)
384     */
385    @Override
386    public boolean areNotesSupported(Class<?> dataObjectClass) {
387        boolean hasNotesSupport = false;
388
389        DataObjectEntry entry = getDataObjectEntry(dataObjectClass);
390        if (entry != null) {
391            hasNotesSupport = entry.isBoNotesEnabled();
392        }
393
394        return hasNotesSupport;
395    }
396
397    /**
398     * @see org.kuali.rice.krad.service.DataObjectMetaDataService#getDataObjectIdentifierString
399     */
400    @Override
401        public String getDataObjectIdentifierString(Object dataObject) {
402        String identifierString = "";
403
404        if (dataObject == null) {
405            identifierString = "Null";
406            return identifierString;
407        }
408
409        Class<?> dataObjectClass = dataObject.getClass();
410
411        // if PBO, use object id property
412        if (PersistableBusinessObject.class.isAssignableFrom(dataObjectClass)) {
413            String objectId = ObjectPropertyUtils.getPropertyValue(dataObject, UifPropertyPaths.OBJECT_ID);
414            if (StringUtils.isBlank(objectId)) {
415                objectId = UUID.randomUUID().toString();
416                ObjectPropertyUtils.setPropertyValue(dataObject, UifPropertyPaths.OBJECT_ID, objectId);
417            }
418
419            identifierString = objectId;
420        } else {
421            // build identifier string from primary key values
422            Map<String, ?> primaryKeyFieldValues = getPrimaryKeyFieldValues(dataObject, true);
423            for (Map.Entry<String, ?> primaryKeyValue : primaryKeyFieldValues.entrySet()) {
424                if (primaryKeyValue.getValue() == null) {
425                    identifierString += "Null";
426                } else {
427                    identifierString += primaryKeyValue.getValue();
428                }
429                identifierString += ":";
430            }
431            identifierString = StringUtils.removeEnd(identifierString, ":");
432        }
433
434        return identifierString;
435    }
436
437    /**
438     * @param dataObjectClass
439     * @return DataObjectEntry for the given dataObjectClass, or null if
440     *         there is none
441     * @throws IllegalArgumentException if the given Class is null
442     */
443    protected DataObjectEntry getDataObjectEntry(Class<?> dataObjectClass) {
444        if (dataObjectClass == null) {
445            throw new IllegalArgumentException("invalid (null) dataObjectClass");
446        }
447
448        DataObjectEntry entry = getDataDictionaryService().getDataDictionary().getDataObjectEntry(
449                dataObjectClass.getName());
450
451        return entry;
452    }
453
454    @Override
455        public List<DataObjectRelationship> getDataObjectRelationships(Class<?> dataObjectClass) {
456                if (dataObjectClass == null) {
457                        return null;
458                }
459
460                Map<String, Class> referenceClasses = null;
461                if (PersistableBusinessObject.class.isAssignableFrom(dataObjectClass)
462                                && getPersistenceStructureService().isPersistable(dataObjectClass)) {
463                        referenceClasses = getPersistenceStructureService().listReferenceObjectFields(dataObjectClass);
464                }
465                DataDictionaryEntry ddEntry = getDataDictionaryService().getDataDictionary().getDictionaryObjectEntry(
466                                dataObjectClass.getName());
467                List<RelationshipDefinition> ddRelationships = (ddEntry == null ? new ArrayList<RelationshipDefinition>()
468                                : ddEntry.getRelationships());
469                List<DataObjectRelationship> relationships = new ArrayList<DataObjectRelationship>();
470
471                // loop over all relationships
472                if (referenceClasses != null) {
473                        for (Map.Entry<String, Class> entry : referenceClasses.entrySet()) {
474                if (classHasSupportedFeatures(entry.getValue(), true, false)) {
475                                        Map<String, String> fkToPkRefs = getPersistenceStructureService().getForeignKeysForReference(dataObjectClass,
476                                                        entry.getKey());
477                                        DataObjectRelationship rel = new DataObjectRelationship(dataObjectClass, entry.getKey(),
478                                                        entry.getValue());
479                                        for (Map.Entry<String, String> ref : fkToPkRefs.entrySet()) {
480                                                rel.getParentToChildReferences().put(ref.getKey(), ref.getValue());
481                                        }
482                                        relationships.add(rel);
483                                }
484                        }
485                }
486
487                for (RelationshipDefinition rd : ddRelationships) {
488                        if (classHasSupportedFeatures(rd.getTargetClass(), true, false)) {
489                                DataObjectRelationship rel = new DataObjectRelationship(dataObjectClass, rd.getObjectAttributeName(),
490                                                rd.getTargetClass());
491                                for (PrimitiveAttributeDefinition def : rd.getPrimitiveAttributes()) {
492                                        rel.getParentToChildReferences().put(def.getSourceName(), def.getTargetName());
493                                }
494                                relationships.add(rel);
495                        }
496                }
497
498                return relationships;
499        }
500
501    /**
502     * @see org.kuali.rice.krad.service.DataObjectMetaDataService#hasLocalLookup
503     */
504    @Override
505        public boolean hasLocalLookup(Class<?> dataObjectClass) {
506        return viewDictionaryService.isLookupable(dataObjectClass);
507    }
508
509    /**
510     * @see org.kuali.rice.krad.service.DataObjectMetaDataService#hasLocalInquiry
511     */
512    @Override
513        public boolean hasLocalInquiry(Class<?> dataObjectClass) {
514        return viewDictionaryService.isInquirable(dataObjectClass);
515    }
516
517    /**
518     * @param businessObjectClass - class of business object to return entry for
519     * @return BusinessObjectEntry for the given dataObjectClass, or null if
520     *         there is none
521     */
522    protected BusinessObjectEntry getBusinessObjectEntry(Class businessObjectClass) {
523        validateBusinessObjectClass(businessObjectClass);
524
525        BusinessObjectEntry entry = getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(
526                businessObjectClass.getName());
527        return entry;
528    }
529
530    /**
531     * @param businessObjectClass
532     * @throws IllegalArgumentException if the given Class is null or is not a BusinessObject class
533     */
534    protected void validateBusinessObjectClass(Class businessObjectClass) {
535        if (businessObjectClass == null) {
536            throw new IllegalArgumentException("invalid (null) dataObjectClass");
537        }
538        if (!BusinessObject.class.isAssignableFrom(businessObjectClass)) {
539            throw new IllegalArgumentException(
540                    "class '" + businessObjectClass.getName() + "' is not a descendant of BusinessObject");
541        }
542    }
543
544    protected DataDictionaryService getDataDictionaryService() {
545        return this.dataDictionaryService;
546    }
547
548    public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
549        this.dataDictionaryService = dataDictionaryService;
550    }
551
552    protected KualiModuleService getKualiModuleService() {
553        return this.kualiModuleService;
554    }
555
556    public void setKualiModuleService(KualiModuleService kualiModuleService) {
557        this.kualiModuleService = kualiModuleService;
558    }
559
560    protected PersistenceStructureService getPersistenceStructureService() {
561        return this.persistenceStructureService;
562    }
563
564    public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) {
565        this.persistenceStructureService = persistenceStructureService;
566    }
567
568    protected ViewDictionaryService getViewDictionaryService() {
569        if (this.viewDictionaryService == null) {
570            this.viewDictionaryService = KRADServiceLocatorWeb.getViewDictionaryService();
571        }
572        return this.viewDictionaryService;
573    }
574
575    public void setViewDictionaryService(ViewDictionaryService viewDictionaryService) {
576        this.viewDictionaryService = viewDictionaryService;
577    }
578
579}