View Javadoc
1   /**
2    * Copyright 2005-2016 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.service.impl;
17  
18  import java.util.ArrayList;
19  import java.util.HashMap;
20  import java.util.Iterator;
21  import java.util.List;
22  import java.util.Map;
23  import java.util.TreeMap;
24  import java.util.UUID;
25  
26  import org.apache.commons.lang.StringUtils;
27  import org.kuali.rice.krad.bo.BusinessObject;
28  import org.kuali.rice.krad.bo.DataObjectRelationship;
29  import org.kuali.rice.krad.bo.PersistableBusinessObject;
30  import org.kuali.rice.krad.datadictionary.BusinessObjectEntry;
31  import org.kuali.rice.krad.datadictionary.DataDictionaryEntry;
32  import org.kuali.rice.krad.datadictionary.DataObjectEntry;
33  import org.kuali.rice.krad.datadictionary.PrimitiveAttributeDefinition;
34  import org.kuali.rice.krad.datadictionary.RelationshipDefinition;
35  import org.kuali.rice.krad.datadictionary.SupportAttributeDefinition;
36  import org.kuali.rice.krad.service.DataDictionaryService;
37  import org.kuali.rice.krad.service.DataObjectMetaDataService;
38  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
39  import org.kuali.rice.krad.service.KualiModuleService;
40  import org.kuali.rice.krad.service.ModuleService;
41  import org.kuali.rice.krad.service.PersistenceStructureService;
42  import org.kuali.rice.krad.uif.UifPropertyPaths;
43  import org.kuali.rice.krad.uif.service.ViewDictionaryService;
44  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
45  import org.kuali.rice.krad.util.KRADUtils;
46  import org.kuali.rice.krad.util.LegacyDataFramework;
47  import org.springframework.beans.PropertyAccessorUtils;
48  
49  /**
50   * @author Kuali Rice Team (rice.collab@kuali.org)
51   *
52   * @deprecated use new KRAD Data framework {@link org.kuali.rice.krad.data.DataObjectService},
53   *             replaced by metadata provider framework
54   */
55  @Deprecated
56  @LegacyDataFramework
57  public class DataObjectMetaDataServiceImpl implements DataObjectMetaDataService {
58      private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(
59              DataObjectMetaDataServiceImpl.class);
60  
61      private DataDictionaryService dataDictionaryService;
62      private KualiModuleService kualiModuleService;
63      private PersistenceStructureService persistenceStructureService;
64      private ViewDictionaryService viewDictionaryService;
65  
66      @Override
67      public List<String> listPrimaryKeyFieldNames(Class<?> clazz) {
68          if (persistenceStructureService.isPersistable(clazz)) {
69              return persistenceStructureService.listPrimaryKeyFieldNames(clazz);
70          }
71  
72          ModuleService responsibleModuleService = getKualiModuleService().getResponsibleModuleService(clazz);
73          if (responsibleModuleService != null && responsibleModuleService.isExternalizable(clazz)) {
74              return responsibleModuleService.listPrimaryKeyFieldNames(clazz);
75          }
76  
77          // check the Data Dictionary for PK's of non PBO/EBO
78          List<String> pks = dataDictionaryService.getDataDictionary().getDataObjectEntry(clazz.getName())
79                  .getPrimaryKeys();
80          if (pks != null && !pks.isEmpty()) {
81              return pks;
82          }
83  
84          return new ArrayList<String>();
85      }
86  
87      @Override
88      public Map<String, ?> getPrimaryKeyFieldValues(Object dataObject) {
89          return getPrimaryKeyFieldValues(dataObject, false);
90      }
91  
92      @Override
93      public Map<String, ?> getPrimaryKeyFieldValues(Object dataObject, boolean sortFieldNames) {
94          Map<String, Object> keyValueMap;
95  
96          if (sortFieldNames) {
97              keyValueMap = new TreeMap<String, Object>();
98          } else {
99              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 }