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     public RelationshipDefinition getDictionaryRelationship(Class<?> c, String attributeName) {
257         DataDictionaryEntry entryBase = getDataDictionaryService().getDataDictionary().getDictionaryObjectEntry(
258                 c.getName());
259         if (entryBase == null) {
260             return null;
261         }
262 
263         RelationshipDefinition relationship = null;
264 
265         List<RelationshipDefinition> ddRelationships = entryBase.getRelationships();
266 
267         int minKeys = Integer.MAX_VALUE;
268         for (RelationshipDefinition def : ddRelationships) {
269             // favor key sizes of 1 first
270             if (def.getPrimitiveAttributes().size() == 1) {
271                 for (PrimitiveAttributeDefinition primitive : def.getPrimitiveAttributes()) {
272                     if (primitive.getSourceName().equals(attributeName) || def.getObjectAttributeName().equals(
273                             attributeName)) {
274                         relationship = def;
275                         minKeys = 1;
276                         break;
277                     }
278                 }
279             } else if (def.getPrimitiveAttributes().size() < minKeys) {
280                 for (PrimitiveAttributeDefinition primitive : def.getPrimitiveAttributes()) {
281                     if (primitive.getSourceName().equals(attributeName) || def.getObjectAttributeName().equals(
282                             attributeName)) {
283                         relationship = def;
284                         minKeys = def.getPrimitiveAttributes().size();
285                         break;
286                     }
287                 }
288             }
289         }
290 
291         // check the support attributes
292         if (relationship == null) {
293             for (RelationshipDefinition def : ddRelationships) {
294                 if (def.hasIdentifier()) {
295                     if (def.getIdentifier().getSourceName().equals(attributeName)) {
296                         relationship = def;
297                     }
298                 }
299             }
300         }
301 
302         return relationship;
303     }
304 
305     protected DataObjectRelationship populateRelationshipFromDictionaryReference(Class<?> dataObjectClass,
306             RelationshipDefinition ddReference, String attributePrefix, boolean keysOnly) {
307         DataObjectRelationship relationship = new DataObjectRelationship(dataObjectClass,
308                 ddReference.getObjectAttributeName(), ddReference.getTargetClass());
309 
310         for (PrimitiveAttributeDefinition def : ddReference.getPrimitiveAttributes()) {
311             if (StringUtils.isNotBlank(attributePrefix)) {
312                 relationship.getParentToChildReferences().put(attributePrefix + "." + def.getSourceName(),
313                         def.getTargetName());
314             } else {
315                 relationship.getParentToChildReferences().put(def.getSourceName(), def.getTargetName());
316             }
317         }
318 
319         if (!keysOnly) {
320             for (SupportAttributeDefinition def : ddReference.getSupportAttributes()) {
321                 if (StringUtils.isNotBlank(attributePrefix)) {
322                     relationship.getParentToChildReferences().put(attributePrefix + "." + def.getSourceName(),
323                             def.getTargetName());
324                     if (def.isIdentifier()) {
325                         relationship.setUserVisibleIdentifierKey(attributePrefix + "." + def.getSourceName());
326                     }
327                 } else {
328                     relationship.getParentToChildReferences().put(def.getSourceName(), def.getTargetName());
329                     if (def.isIdentifier()) {
330                         relationship.setUserVisibleIdentifierKey(def.getSourceName());
331                     }
332                 }
333             }
334         }
335 
336         return relationship;
337     }
338 
339     protected DataObjectRelationship getRelationshipMetadata(Class<?> dataObjectClass, String attributeName,
340             String attributePrefix) {
341 
342         RelationshipDefinition relationshipDefinition = getDictionaryRelationship(dataObjectClass, attributeName);
343         if (relationshipDefinition == null) {
344             return null;
345         }
346 
347         DataObjectRelationship dataObjectRelationship = new DataObjectRelationship(
348                 relationshipDefinition.getSourceClass(), relationshipDefinition.getObjectAttributeName(),
349                 relationshipDefinition.getTargetClass());
350 
351         if (!StringUtils.isEmpty(attributePrefix)) {
352             attributePrefix += ".";
353         }
354 
355         List<PrimitiveAttributeDefinition> primitives = relationshipDefinition.getPrimitiveAttributes();
356         for (PrimitiveAttributeDefinition primitiveAttributeDefinition : primitives) {
357             dataObjectRelationship.getParentToChildReferences().put(
358                     attributePrefix + primitiveAttributeDefinition.getSourceName(),
359                     primitiveAttributeDefinition.getTargetName());
360         }
361 
362         return dataObjectRelationship;
363     }
364 
365     /**
366      * @see org.kuali.rice.krad.service.DataObjectMetaDataService#getTitleAttribute(java.lang.Class)
367      */
368     public String getTitleAttribute(Class<?> dataObjectClass) {
369         String titleAttribute = null;
370 
371         DataObjectEntry entry = getDataObjectEntry(dataObjectClass);
372         if (entry != null) {
373             titleAttribute = entry.getTitleAttribute();
374         }
375 
376         return titleAttribute;
377     }
378 
379     /**
380      * @see org.kuali.rice.krad.service.DataObjectMetaDataService#areNotesSupported(java.lang.Class)
381      */
382     @Override
383     public boolean areNotesSupported(Class<?> dataObjectClass) {
384         boolean hasNotesSupport = false;
385 
386         DataObjectEntry entry = getDataObjectEntry(dataObjectClass);
387         if (entry != null) {
388             hasNotesSupport = entry.isBoNotesEnabled();
389         }
390 
391         return hasNotesSupport;
392     }
393 
394     /**
395      * @see org.kuali.rice.krad.service.DataObjectMetaDataService#getDataObjectIdentifierString
396      */
397     public String getDataObjectIdentifierString(Object dataObject) {
398         String identifierString = "";
399 
400         if (dataObject == null) {
401             identifierString = "Null";
402             return identifierString;
403         }
404 
405         Class<?> dataObjectClass = dataObject.getClass();
406 
407         // if PBO, use object id property
408         if (PersistableBusinessObject.class.isAssignableFrom(dataObjectClass)) {
409             String objectId = ObjectPropertyUtils.getPropertyValue(dataObject, UifPropertyPaths.OBJECT_ID);
410             if (StringUtils.isBlank(objectId)) {
411                 objectId = UUID.randomUUID().toString();
412                 ObjectPropertyUtils.setPropertyValue(dataObject, UifPropertyPaths.OBJECT_ID, objectId);
413             }
414 
415             identifierString = objectId;
416         } else {
417             // build identifier string from primary key values
418             Map<String, ?> primaryKeyFieldValues = getPrimaryKeyFieldValues(dataObject, true);
419             for (Map.Entry<String, ?> primaryKeyValue : primaryKeyFieldValues.entrySet()) {
420                 if (primaryKeyValue.getValue() == null) {
421                     identifierString += "Null";
422                 } else {
423                     identifierString += primaryKeyValue.getValue();
424                 }
425                 identifierString += ":";
426             }
427             identifierString = StringUtils.removeEnd(identifierString, ":");
428         }
429 
430         return identifierString;
431     }
432 
433     /**
434      * @param dataObjectClass
435      * @return DataObjectEntry for the given dataObjectClass, or null if
436      *         there is none
437      * @throws IllegalArgumentException if the given Class is null
438      */
439     protected DataObjectEntry getDataObjectEntry(Class<?> dataObjectClass) {
440         if (dataObjectClass == null) {
441             throw new IllegalArgumentException("invalid (null) dataObjectClass");
442         }
443 
444         DataObjectEntry entry = getDataDictionaryService().getDataDictionary().getDataObjectEntry(
445                 dataObjectClass.getName());
446 
447         return entry;
448     }
449 
450     public List<DataObjectRelationship> getDataObjectRelationships(Class<?> dataObjectClass) {
451 		if (dataObjectClass == null) {
452 			return null;
453 		}
454 
455 		Map<String, Class> referenceClasses = null;
456 		if (PersistableBusinessObject.class.isAssignableFrom(dataObjectClass)
457 				&& getPersistenceStructureService().isPersistable(dataObjectClass)) {
458 			referenceClasses = getPersistenceStructureService().listReferenceObjectFields(dataObjectClass);
459 		}
460 		DataDictionaryEntry ddEntry = getDataDictionaryService().getDataDictionary().getDictionaryObjectEntry(
461 				dataObjectClass.getName());
462 		List<RelationshipDefinition> ddRelationships = (ddEntry == null ? new ArrayList<RelationshipDefinition>()
463 				: ddEntry.getRelationships());
464 		List<DataObjectRelationship> relationships = new ArrayList<DataObjectRelationship>();
465 
466 		// loop over all relationships
467 		if (referenceClasses != null) {
468 			for (Map.Entry<String, Class> entry : referenceClasses.entrySet()) {
469                 if (classHasSupportedFeatures(entry.getValue(), true, false)) {
470 					Map<String, String> fkToPkRefs = getPersistenceStructureService().getForeignKeysForReference(dataObjectClass,
471 							entry.getKey());
472 					DataObjectRelationship rel = new DataObjectRelationship(dataObjectClass, entry.getKey(),
473 							entry.getValue());
474 					for (Map.Entry<String, String> ref : fkToPkRefs.entrySet()) {
475 						rel.getParentToChildReferences().put(ref.getKey(), ref.getValue());
476 					}
477 					relationships.add(rel);
478 				}
479 			}
480 		}
481 
482 		for (RelationshipDefinition rd : ddRelationships) {
483 			if (classHasSupportedFeatures(rd.getTargetClass(), true, false)) {
484 				DataObjectRelationship rel = new DataObjectRelationship(dataObjectClass, rd.getObjectAttributeName(),
485 						rd.getTargetClass());
486 				for (PrimitiveAttributeDefinition def : rd.getPrimitiveAttributes()) {
487 					rel.getParentToChildReferences().put(def.getSourceName(), def.getTargetName());
488 				}
489 				relationships.add(rel);
490 			}
491 		}
492 
493 		return relationships;
494 	}
495 
496     /**
497      * @param businessObjectClass - class of business object to return entry for
498      * @return BusinessObjectEntry for the given dataObjectClass, or null if
499      *         there is none
500      */
501     protected BusinessObjectEntry getBusinessObjectEntry(Class businessObjectClass) {
502         validateBusinessObjectClass(businessObjectClass);
503 
504         BusinessObjectEntry entry = getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(
505                 businessObjectClass.getName());
506         return entry;
507     }
508 
509     /**
510      * @param businessObjectClass
511      * @throws IllegalArgumentException if the given Class is null or is not a BusinessObject class
512      */
513     protected void validateBusinessObjectClass(Class businessObjectClass) {
514         if (businessObjectClass == null) {
515             throw new IllegalArgumentException("invalid (null) dataObjectClass");
516         }
517         if (!BusinessObject.class.isAssignableFrom(businessObjectClass)) {
518             throw new IllegalArgumentException(
519                     "class '" + businessObjectClass.getName() + "' is not a descendant of BusinessObject");
520         }
521     }
522 
523     /**
524      * Protected method to allow subclasses to access the dataDictionaryService.
525      *
526      * @return Returns the dataDictionaryService.
527      */
528     protected DataDictionaryService getDataDictionaryService() {
529         return this.dataDictionaryService;
530     }
531 
532     public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
533         this.dataDictionaryService = dataDictionaryService;
534     }
535 
536     /**
537      * Protected method to allow subclasses to access the kualiModuleService.
538      *
539      * @return Returns the persistenceStructureService.
540      */
541     protected KualiModuleService getKualiModuleService() {
542         return this.kualiModuleService;
543     }
544 
545     public void setKualiModuleService(KualiModuleService kualiModuleService) {
546         this.kualiModuleService = kualiModuleService;
547     }
548 
549     /**
550      * Protected method to allow subclasses to access the
551      * persistenceStructureService.
552      *
553      * @return Returns the persistenceStructureService.
554      */
555     protected PersistenceStructureService getPersistenceStructureService() {
556         return this.persistenceStructureService;
557     }
558 
559     public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) {
560         this.persistenceStructureService = persistenceStructureService;
561     }
562 
563     protected ViewDictionaryService getViewDictionaryService() {
564         if (this.viewDictionaryService == null) {
565             this.viewDictionaryService = KRADServiceLocatorWeb.getViewDictionaryService();
566         }
567         return this.viewDictionaryService;
568     }
569 
570     public void setViewDictionaryService(ViewDictionaryService viewDictionaryService) {
571         this.viewDictionaryService = viewDictionaryService;
572     }
573 
574 }