View Javadoc

1   /**
2    * Copyright 2005-2014 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.ObjectUtils;
46  import org.springframework.beans.BeanWrapper;
47  
48  /**
49   * @author Kuali Rice Team (rice.collab@kuali.org)
50   */
51  public class DataObjectMetaDataServiceImpl implements DataObjectMetaDataService {
52  
53      private DataDictionaryService dataDictionaryService;
54      private KualiModuleService kualiModuleService;
55      private PersistenceStructureService persistenceStructureService;
56      private ViewDictionaryService viewDictionaryService;
57  
58      /**
59       * @see org.kuali.rice.krad.service.DataObjectMetaDataService#listPrimaryKeyFieldNames(java.lang.Class)
60       */
61      @Override
62      public List<String> listPrimaryKeyFieldNames(Class<?> clazz) {
63          if (persistenceStructureService.isPersistable(clazz)) {
64              return persistenceStructureService.listPrimaryKeyFieldNames(clazz);
65          }
66  
67          ModuleService responsibleModuleService = getKualiModuleService().getResponsibleModuleService(clazz);
68          if (responsibleModuleService != null && responsibleModuleService.isExternalizable(clazz)) {
69              return responsibleModuleService.listPrimaryKeyFieldNames(clazz);
70          }
71  
72          // check the Data Dictionary for PK's of non PBO/EBO
73          List<String> pks = dataDictionaryService.getDataDictionary().getDataObjectEntry(clazz.getName())
74                  .getPrimaryKeys();
75          if (pks != null && !pks.isEmpty()) {
76              return pks;
77          }
78  
79          return new ArrayList<String>();
80      }
81  
82      /**
83       * @see org.kuali.rice.krad.service.DataObjectMetaDataService#getPrimaryKeyFieldValues(java.lang.Object)
84       */
85      @Override
86      public Map<String, ?> getPrimaryKeyFieldValues(Object dataObject) {
87          return getPrimaryKeyFieldValues(dataObject, false);
88      }
89  
90      /**
91       * @see org.kuali.rice.krad.service.DataObjectMetaDataService#getPrimaryKeyFieldValues(java.lang.Object,
92       *      boolean)
93       */
94      @Override
95      public Map<String, ?> getPrimaryKeyFieldValues(Object dataObject, boolean sortFieldNames) {
96          Map<String, Object> keyValueMap;
97  
98          if (sortFieldNames) {
99              keyValueMap = new TreeMap<String, Object>();
100         } else {
101             keyValueMap = new HashMap<String, Object>();
102         }
103 
104         BeanWrapper wrapper = ObjectPropertyUtils.wrapObject(dataObject);
105 
106         List<String> fields = listPrimaryKeyFieldNames(dataObject.getClass());
107         for (String fieldName : fields) {
108             keyValueMap.put(fieldName, wrapper.getPropertyValue(fieldName));
109         }
110 
111         return keyValueMap;
112     }
113 
114     /**
115      * @see org.kuali.rice.krad.service.DataObjectMetaDataService#equalsByPrimaryKeys(java.lang.Object,
116      *      java.lang.Object)
117      */
118     @Override
119     public boolean equalsByPrimaryKeys(Object do1, Object do2) {
120         boolean equal = true;
121 
122         if (do1 == null && do2 == null) {
123             equal = true;
124         } else if (do1 == null || do2 == null) {
125             equal = false;
126         } else if (!do1.getClass().getName().equals(do2.getClass().getName())) {
127             equal = false;
128         } else {
129             Map<String, ?> do1Keys = getPrimaryKeyFieldValues(do1);
130             Map<String, ?> do2Keys = getPrimaryKeyFieldValues(do2);
131             for (Iterator<String> iter = do1Keys.keySet().iterator(); iter.hasNext(); ) {
132                 String keyName = iter.next();
133                 if (do1Keys.get(keyName) != null && do2Keys.get(keyName) != null) {
134                     if (!do1Keys.get(keyName).toString().equals(do2Keys.get(keyName).toString())) {
135                         equal = false;
136                     }
137                 } else {
138                     equal = false;
139                 }
140             }
141         }
142 
143         return equal;
144     }
145 
146     /**
147      * @see org.kuali.rice.kns.service.BusinessObjectMetaDataService#getDataObjectRelationship(java.lang.Object,
148      *      java.lang.Class, java.lang.String, java.lang.String, boolean,
149      *      boolean, boolean)
150      */
151     public DataObjectRelationship getDataObjectRelationship(Object dataObject, Class<?> dataObjectClass,
152             String attributeName, String attributePrefix, boolean keysOnly, boolean supportsLookup,
153             boolean supportsInquiry) {
154         RelationshipDefinition ddReference = getDictionaryRelationship(dataObjectClass, attributeName);
155 
156         return getDataObjectRelationship(ddReference, dataObject, dataObjectClass, attributeName, attributePrefix,
157                 keysOnly, supportsLookup, supportsInquiry);
158     }
159 
160     protected DataObjectRelationship getDataObjectRelationship(RelationshipDefinition ddReference, Object dataObject,
161             Class<?> dataObjectClass, String attributeName, String attributePrefix, boolean keysOnly,
162             boolean supportsLookup, boolean supportsInquiry) {
163         DataObjectRelationship relationship = null;
164 
165         // if it is nested then replace the data object and attributeName with the
166         // sub-refs
167         if (ObjectUtils.isNestedAttribute(attributeName)) {
168             if (ddReference != null) {
169                 if (classHasSupportedFeatures(ddReference.getTargetClass(), supportsLookup, supportsInquiry)) {
170                     relationship = populateRelationshipFromDictionaryReference(dataObjectClass, ddReference,
171                             attributePrefix, keysOnly);
172 
173                     return relationship;
174                 }
175             }
176 
177             // recurse down to the next object to find the relationship
178             String localPrefix = StringUtils.substringBefore(attributeName, ".");
179             String localAttributeName = StringUtils.substringAfter(attributeName, ".");
180             if (dataObject == null) {
181                 dataObject = ObjectUtils.createNewObjectFromClass(dataObjectClass);
182             }
183 
184             Object nestedObject = ObjectPropertyUtils.getPropertyValue(dataObject, localPrefix);
185             Class<?> nestedClass = null;
186             if (nestedObject == null) {
187                 nestedClass = ObjectPropertyUtils.getPropertyType(dataObject, localPrefix);
188             } else {
189                 nestedClass = nestedObject.getClass();
190             }
191 
192             String fullPrefix = localPrefix;
193             if (StringUtils.isNotBlank(attributePrefix)) {
194                 fullPrefix = attributePrefix + "." + localPrefix;
195             }
196 
197             relationship = getDataObjectRelationship(nestedObject, nestedClass, localAttributeName, fullPrefix,
198                     keysOnly, supportsLookup, supportsInquiry);
199 
200             return relationship;
201         }
202 
203         // non-nested reference, get persistence relationships first
204         int maxSize = Integer.MAX_VALUE;
205 
206         // try persistable reference first
207         if (getPersistenceStructureService().isPersistable(dataObjectClass)) {
208             Map<String, DataObjectRelationship> rels = getPersistenceStructureService().getRelationshipMetadata(
209                     dataObjectClass, attributeName, attributePrefix);
210             if (rels.size() > 0) {
211                 for (DataObjectRelationship rel : rels.values()) {
212                     if (rel.getParentToChildReferences().size() < maxSize) {
213                         if (classHasSupportedFeatures(rel.getRelatedClass(), supportsLookup, supportsInquiry)) {
214                             maxSize = rel.getParentToChildReferences().size();
215                             relationship = rel;
216                         }
217                     }
218                 }
219             }
220         } else {
221             ModuleService moduleService = getKualiModuleService().getResponsibleModuleService(dataObjectClass);
222             if (moduleService != null && moduleService.isExternalizable(dataObjectClass)) {
223                 relationship = getRelationshipMetadata(dataObjectClass, attributeName, attributePrefix);
224                 if ((relationship != null) && classHasSupportedFeatures(relationship.getRelatedClass(), supportsLookup,
225                         supportsInquiry)) {
226                     return relationship;
227                 } else {
228                     return null;
229                 }
230             }
231         }
232 
233         if (ddReference != null && ddReference.getPrimitiveAttributes().size() < maxSize) {
234             if (classHasSupportedFeatures(ddReference.getTargetClass(), supportsLookup, supportsInquiry)) {
235                 relationship = populateRelationshipFromDictionaryReference(dataObjectClass, ddReference, null,
236                         keysOnly);
237             }
238         }
239 
240         return relationship;
241     }
242 
243     protected boolean classHasSupportedFeatures(Class relationshipClass, boolean supportsLookup,
244             boolean supportsInquiry) {
245         boolean hasSupportedFeatures = true;
246         if (supportsLookup && !getViewDictionaryService().isLookupable(relationshipClass)) {
247             hasSupportedFeatures = false;
248         }
249         if (supportsInquiry && !getViewDictionaryService().isInquirable(relationshipClass)) {
250             hasSupportedFeatures = false;
251         }
252 
253         return hasSupportedFeatures;
254     }
255 
256     /**
257      * gets the relationship that the attribute represents on the class
258      *
259      * @param c - the class to which the attribute belongs
260      * @param attributeName - property name for the attribute
261      *
262      * @return a relationship definition for the attribute
263      */
264     public RelationshipDefinition getDictionaryRelationship(Class<?> c, String attributeName) {
265         DataDictionaryEntry entryBase = getDataDictionaryService().getDataDictionary().getDictionaryObjectEntry(
266                 c.getName());
267         if (entryBase == null) {
268             return null;
269         }
270 
271         RelationshipDefinition relationship = null;
272 
273         List<RelationshipDefinition> ddRelationships = entryBase.getRelationships();
274 
275         int minKeys = Integer.MAX_VALUE;
276         for (RelationshipDefinition def : ddRelationships) {
277             // favor key sizes of 1 first
278             if (def.getPrimitiveAttributes().size() == 1) {
279                 for (PrimitiveAttributeDefinition primitive : def.getPrimitiveAttributes()) {
280                     if (primitive.getSourceName().equals(attributeName) || def.getObjectAttributeName().equals(
281                             attributeName)) {
282                         relationship = def;
283                         minKeys = 1;
284                         break;
285                     }
286                 }
287             } else if (def.getPrimitiveAttributes().size() < minKeys) {
288                 for (PrimitiveAttributeDefinition primitive : def.getPrimitiveAttributes()) {
289                     if (primitive.getSourceName().equals(attributeName) || def.getObjectAttributeName().equals(
290                             attributeName)) {
291                         relationship = def;
292                         minKeys = def.getPrimitiveAttributes().size();
293                         break;
294                     }
295                 }
296             }
297         }
298 
299         // check the support attributes
300         if (relationship == null) {
301             for (RelationshipDefinition def : ddRelationships) {
302                 if (def.hasIdentifier()) {
303                     if (def.getIdentifier().getSourceName().equals(attributeName)) {
304                         relationship = def;
305                     }
306                 }
307             }
308         }
309 
310         return relationship;
311     }
312 
313     protected DataObjectRelationship populateRelationshipFromDictionaryReference(Class<?> dataObjectClass,
314             RelationshipDefinition ddReference, String attributePrefix, boolean keysOnly) {
315         DataObjectRelationship relationship = new DataObjectRelationship(dataObjectClass,
316                 ddReference.getObjectAttributeName(), ddReference.getTargetClass());
317 
318         for (PrimitiveAttributeDefinition def : ddReference.getPrimitiveAttributes()) {
319             if (StringUtils.isNotBlank(attributePrefix)) {
320                 relationship.getParentToChildReferences().put(attributePrefix + "." + def.getSourceName(),
321                         def.getTargetName());
322             } else {
323                 relationship.getParentToChildReferences().put(def.getSourceName(), def.getTargetName());
324             }
325         }
326 
327         if (!keysOnly) {
328             for (SupportAttributeDefinition def : ddReference.getSupportAttributes()) {
329                 if (StringUtils.isNotBlank(attributePrefix)) {
330                     relationship.getParentToChildReferences().put(attributePrefix + "." + def.getSourceName(),
331                             def.getTargetName());
332                     if (def.isIdentifier()) {
333                         relationship.setUserVisibleIdentifierKey(attributePrefix + "." + def.getSourceName());
334                     }
335                 } else {
336                     relationship.getParentToChildReferences().put(def.getSourceName(), def.getTargetName());
337                     if (def.isIdentifier()) {
338                         relationship.setUserVisibleIdentifierKey(def.getSourceName());
339                     }
340                 }
341             }
342         }
343 
344         return relationship;
345     }
346 
347     protected DataObjectRelationship getRelationshipMetadata(Class<?> dataObjectClass, String attributeName,
348             String attributePrefix) {
349 
350         RelationshipDefinition relationshipDefinition = getDictionaryRelationship(dataObjectClass, attributeName);
351         if (relationshipDefinition == null) {
352             return null;
353         }
354 
355         DataObjectRelationship dataObjectRelationship = new DataObjectRelationship(
356                 relationshipDefinition.getSourceClass(), relationshipDefinition.getObjectAttributeName(),
357                 relationshipDefinition.getTargetClass());
358 
359         if (!StringUtils.isEmpty(attributePrefix)) {
360             attributePrefix += ".";
361         }
362 
363         List<PrimitiveAttributeDefinition> primitives = relationshipDefinition.getPrimitiveAttributes();
364         for (PrimitiveAttributeDefinition primitiveAttributeDefinition : primitives) {
365             dataObjectRelationship.getParentToChildReferences().put(
366                     attributePrefix + primitiveAttributeDefinition.getSourceName(),
367                     primitiveAttributeDefinition.getTargetName());
368         }
369 
370         return dataObjectRelationship;
371     }
372 
373     /**
374      * @see org.kuali.rice.krad.service.DataObjectMetaDataService#getTitleAttribute(java.lang.Class)
375      */
376     public String getTitleAttribute(Class<?> dataObjectClass) {
377         String titleAttribute = null;
378 
379         DataObjectEntry entry = getDataObjectEntry(dataObjectClass);
380         if (entry != null) {
381             titleAttribute = entry.getTitleAttribute();
382         }
383 
384         return titleAttribute;
385     }
386 
387     /**
388      * @see org.kuali.rice.krad.service.DataObjectMetaDataService#areNotesSupported(java.lang.Class)
389      */
390     @Override
391     public boolean areNotesSupported(Class<?> dataObjectClass) {
392         boolean hasNotesSupport = false;
393 
394         DataObjectEntry entry = getDataObjectEntry(dataObjectClass);
395         if (entry != null) {
396             hasNotesSupport = entry.isBoNotesEnabled();
397         }
398 
399         return hasNotesSupport;
400     }
401 
402     /**
403      * @see org.kuali.rice.krad.service.DataObjectMetaDataService#getDataObjectIdentifierString
404      */
405     public String getDataObjectIdentifierString(Object dataObject) {
406         String identifierString = "";
407 
408         if (dataObject == null) {
409             identifierString = "Null";
410             return identifierString;
411         }
412 
413         Class<?> dataObjectClass = dataObject.getClass();
414 
415         // if PBO, use object id property
416         if (PersistableBusinessObject.class.isAssignableFrom(dataObjectClass)) {
417             String objectId = ObjectPropertyUtils.getPropertyValue(dataObject, UifPropertyPaths.OBJECT_ID);
418             if (StringUtils.isBlank(objectId)) {
419                 objectId = UUID.randomUUID().toString();
420                 ObjectPropertyUtils.setPropertyValue(dataObject, UifPropertyPaths.OBJECT_ID, objectId);
421             }
422 
423             identifierString = objectId;
424         } else {
425             // build identifier string from primary key values
426             Map<String, ?> primaryKeyFieldValues = getPrimaryKeyFieldValues(dataObject, true);
427             for (Map.Entry<String, ?> primaryKeyValue : primaryKeyFieldValues.entrySet()) {
428                 if (primaryKeyValue.getValue() == null) {
429                     identifierString += "Null";
430                 } else {
431                     identifierString += primaryKeyValue.getValue();
432                 }
433                 identifierString += ":";
434             }
435             identifierString = StringUtils.removeEnd(identifierString, ":");
436         }
437 
438         return identifierString;
439     }
440 
441     /**
442      * @param dataObjectClass
443      * @return DataObjectEntry for the given dataObjectClass, or null if
444      *         there is none
445      * @throws IllegalArgumentException if the given Class is null
446      */
447     protected DataObjectEntry getDataObjectEntry(Class<?> dataObjectClass) {
448         if (dataObjectClass == null) {
449             throw new IllegalArgumentException("invalid (null) dataObjectClass");
450         }
451 
452         DataObjectEntry entry = getDataDictionaryService().getDataDictionary().getDataObjectEntry(
453                 dataObjectClass.getName());
454 
455         return entry;
456     }
457 
458     public List<DataObjectRelationship> getDataObjectRelationships(Class<?> dataObjectClass) {
459 		if (dataObjectClass == null) {
460 			return null;
461 		}
462 
463 		Map<String, Class> referenceClasses = null;
464 		if (PersistableBusinessObject.class.isAssignableFrom(dataObjectClass)
465 				&& getPersistenceStructureService().isPersistable(dataObjectClass)) {
466 			referenceClasses = getPersistenceStructureService().listReferenceObjectFields(dataObjectClass);
467 		}
468 		DataDictionaryEntry ddEntry = getDataDictionaryService().getDataDictionary().getDictionaryObjectEntry(
469 				dataObjectClass.getName());
470 		List<RelationshipDefinition> ddRelationships = (ddEntry == null ? new ArrayList<RelationshipDefinition>()
471 				: ddEntry.getRelationships());
472 		List<DataObjectRelationship> relationships = new ArrayList<DataObjectRelationship>();
473 
474 		// loop over all relationships
475 		if (referenceClasses != null) {
476 			for (Map.Entry<String, Class> entry : referenceClasses.entrySet()) {
477                 if (classHasSupportedFeatures(entry.getValue(), true, false)) {
478 					Map<String, String> fkToPkRefs = getPersistenceStructureService().getForeignKeysForReference(dataObjectClass,
479 							entry.getKey());
480 					DataObjectRelationship rel = new DataObjectRelationship(dataObjectClass, entry.getKey(),
481 							entry.getValue());
482 					for (Map.Entry<String, String> ref : fkToPkRefs.entrySet()) {
483 						rel.getParentToChildReferences().put(ref.getKey(), ref.getValue());
484 					}
485 					relationships.add(rel);
486 				}
487 			}
488 		}
489 
490 		for (RelationshipDefinition rd : ddRelationships) {
491 			if (classHasSupportedFeatures(rd.getTargetClass(), true, false)) {
492 				DataObjectRelationship rel = new DataObjectRelationship(dataObjectClass, rd.getObjectAttributeName(),
493 						rd.getTargetClass());
494 				for (PrimitiveAttributeDefinition def : rd.getPrimitiveAttributes()) {
495 					rel.getParentToChildReferences().put(def.getSourceName(), def.getTargetName());
496 				}
497 				relationships.add(rel);
498 			}
499 		}
500 
501 		return relationships;
502 	}
503 
504     /**
505      * @param businessObjectClass - class of business object to return entry for
506      * @return BusinessObjectEntry for the given dataObjectClass, or null if
507      *         there is none
508      */
509     protected BusinessObjectEntry getBusinessObjectEntry(Class businessObjectClass) {
510         validateBusinessObjectClass(businessObjectClass);
511 
512         BusinessObjectEntry entry = getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(
513                 businessObjectClass.getName());
514         return entry;
515     }
516 
517     /**
518      * @param businessObjectClass
519      * @throws IllegalArgumentException if the given Class is null or is not a BusinessObject class
520      */
521     protected void validateBusinessObjectClass(Class businessObjectClass) {
522         if (businessObjectClass == null) {
523             throw new IllegalArgumentException("invalid (null) dataObjectClass");
524         }
525         if (!BusinessObject.class.isAssignableFrom(businessObjectClass)) {
526             throw new IllegalArgumentException(
527                     "class '" + businessObjectClass.getName() + "' is not a descendant of BusinessObject");
528         }
529     }
530 
531     /**
532      * Protected method to allow subclasses to access the dataDictionaryService.
533      *
534      * @return Returns the dataDictionaryService.
535      */
536     protected DataDictionaryService getDataDictionaryService() {
537         return this.dataDictionaryService;
538     }
539 
540     public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
541         this.dataDictionaryService = dataDictionaryService;
542     }
543 
544     /**
545      * Protected method to allow subclasses to access the kualiModuleService.
546      *
547      * @return Returns the persistenceStructureService.
548      */
549     protected KualiModuleService getKualiModuleService() {
550         return this.kualiModuleService;
551     }
552 
553     public void setKualiModuleService(KualiModuleService kualiModuleService) {
554         this.kualiModuleService = kualiModuleService;
555     }
556 
557     /**
558      * Protected method to allow subclasses to access the
559      * persistenceStructureService.
560      *
561      * @return Returns the persistenceStructureService.
562      */
563     protected PersistenceStructureService getPersistenceStructureService() {
564         return this.persistenceStructureService;
565     }
566 
567     public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) {
568         this.persistenceStructureService = persistenceStructureService;
569     }
570 
571     protected ViewDictionaryService getViewDictionaryService() {
572         if (this.viewDictionaryService == null) {
573             this.viewDictionaryService = KRADServiceLocatorWeb.getViewDictionaryService();
574         }
575         return this.viewDictionaryService;
576     }
577 
578     public void setViewDictionaryService(ViewDictionaryService viewDictionaryService) {
579         this.viewDictionaryService = viewDictionaryService;
580     }
581 
582 }