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