View Javadoc

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