View Javadoc

1   /*
2    * Copyright 2011 The Kuali Foundation Licensed under the Educational Community
3    * License, Version 1.0 (the "License"); you may not use this file except in
4    * compliance with the License. You may obtain a copy of the License at
5    * http://www.opensource.org/licenses/ecl1.php Unless required by applicable law
6    * or agreed to in writing, software distributed under the License is
7    * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
8    * KIND, either express or implied. See the License for the specific language
9    * governing permissions and limitations under the License.
10   */
11  package org.kuali.rice.krad.service.impl;
12  
13  import java.util.ArrayList;
14  import java.util.HashMap;
15  import java.util.Iterator;
16  import java.util.List;
17  import java.util.Map;
18  import java.util.TreeMap;
19  
20  import org.apache.commons.lang.StringUtils;
21  import org.kuali.rice.krad.bo.BusinessObject;
22  import org.kuali.rice.krad.bo.DataObjectRelationship;
23  import org.kuali.rice.krad.datadictionary.BusinessObjectEntry;
24  import org.kuali.rice.krad.datadictionary.DataDictionaryEntry;
25  import org.kuali.rice.krad.datadictionary.DataObjectEntry;
26  import org.kuali.rice.krad.datadictionary.PrimitiveAttributeDefinition;
27  import org.kuali.rice.krad.datadictionary.RelationshipDefinition;
28  import org.kuali.rice.krad.datadictionary.SupportAttributeDefinition;
29  import org.kuali.rice.krad.service.DataDictionaryService;
30  import org.kuali.rice.krad.service.DataObjectMetaDataService;
31  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
32  import org.kuali.rice.krad.service.KualiModuleService;
33  import org.kuali.rice.krad.service.ModuleService;
34  import org.kuali.rice.krad.service.PersistenceStructureService;
35  import org.kuali.rice.krad.uif.service.ViewDictionaryService;
36  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
37  import org.kuali.rice.krad.util.ObjectUtils;
38  import org.kuali.rice.krad.web.controller.UifControllerBase;
39  import org.springframework.beans.BeanWrapper;
40  
41  /**
42   * @author Kuali Rice Team (rice.collab@kuali.org)
43   */
44  public class DataObjectMetaDataServiceImpl implements DataObjectMetaDataService {
45  
46      private DataDictionaryService dataDictionaryService;
47      private KualiModuleService kualiModuleService;
48      private PersistenceStructureService persistenceStructureService;
49      private ViewDictionaryService viewDictionaryService;
50  
51      /**
52       * @see org.kuali.rice.krad.service.DataObjectMetaDataService#listPrimaryKeyFieldNames(java.lang.Class)
53       */
54      @Override
55      public List<String> listPrimaryKeyFieldNames(Class<?> clazz) {
56          if (persistenceStructureService.isPersistable(clazz)) {
57              return persistenceStructureService.listPrimaryKeyFieldNames(clazz);
58          }
59  
60          ModuleService responsibleModuleService = getKualiModuleService().getResponsibleModuleService(clazz);
61          if (responsibleModuleService != null && responsibleModuleService.isExternalizable(clazz)) {
62              return responsibleModuleService.listPrimaryKeyFieldNames(clazz);
63          }
64  
65          // check the Data Dictionary for PK's of non PBO/EBO
66          List<String> pks = dataDictionaryService.getDataDictionary().getDataObjectEntry(clazz.getName())
67                  .getPrimaryKeys();
68          if (pks != null && !pks.isEmpty()) {
69              return pks;
70          }
71  
72          return new ArrayList<String>();
73      }
74  
75      /**
76       * @see org.kuali.rice.krad.service.DataObjectMetaDataService#getPrimaryKeyFieldValues(java.lang.Object)
77       */
78      @Override
79      public Map<String, ?> getPrimaryKeyFieldValues(Object dataObject) {
80          return getPrimaryKeyFieldValues(dataObject, false);
81      }
82  
83      /**
84       * @see org.kuali.rice.krad.service.DataObjectMetaDataService#getPrimaryKeyFieldValues(java.lang.Object,
85       *      boolean)
86       */
87      @Override
88      public Map<String, ?> getPrimaryKeyFieldValues(Object dataObject, boolean sortFieldNames) {
89          Map<String, Object> keyValueMap;
90  
91          if (sortFieldNames) {
92              keyValueMap = new TreeMap<String, Object>();
93          } else {
94              keyValueMap = new HashMap<String, Object>();
95          }
96  
97          BeanWrapper wrapper = ObjectPropertyUtils.wrapObject(dataObject);
98  
99          List<String> fields = listPrimaryKeyFieldNames(dataObject.getClass());
100         for (String fieldName : fields) {
101             keyValueMap.put(fieldName, wrapper.getPropertyValue(fieldName));
102         }
103 
104         return keyValueMap;
105     }
106 
107     /**
108      * @see org.kuali.rice.krad.service.DataObjectMetaDataService#equalsByPrimaryKeys(java.lang.Object,
109      *      java.lang.Object)
110      */
111     @Override
112     public boolean equalsByPrimaryKeys(Object do1, Object do2) {
113         boolean equal = true;
114 
115         if (do1 == null && do2 == null) {
116             equal = true;
117         } else if (do1 == null || do2 == null) {
118             equal = false;
119         } else if (!do1.getClass().getName().equals(do2.getClass().getName())) {
120             equal = false;
121         } else {
122             Map<String, ?> do1Keys = getPrimaryKeyFieldValues(do1);
123             Map<String, ?> do2Keys = getPrimaryKeyFieldValues(do2);
124             for (Iterator<String> iter = do1Keys.keySet().iterator(); iter.hasNext(); ) {
125                 String keyName = iter.next();
126                 if (do1Keys.get(keyName) != null && do2Keys.get(keyName) != null) {
127                     if (!do1Keys.get(keyName).toString().equals(do2Keys.get(keyName).toString())) {
128                         equal = false;
129                     }
130                 } else {
131                     equal = false;
132                 }
133             }
134         }
135 
136         return equal;
137     }
138 
139     /**
140      * @see org.kuali.rice.kns.service.BusinessObjectMetaDataService#getDataObjectRelationship(java.lang.Object,
141      *      java.lang.Class, java.lang.String, java.lang.String, boolean,
142      *      boolean, boolean)
143      */
144     public DataObjectRelationship getDataObjectRelationship(Object dataObject, Class<?> dataObjectClass,
145             String attributeName, String attributePrefix, boolean keysOnly, boolean supportsLookup,
146             boolean supportsInquiry) {
147         RelationshipDefinition ddReference = getDictionaryRelationship(dataObjectClass, attributeName);
148 
149         return getDataObjectRelationship(ddReference, dataObject, dataObjectClass, attributeName, attributePrefix,
150                 keysOnly, supportsLookup, supportsInquiry);
151     }
152 
153     protected DataObjectRelationship getDataObjectRelationship(RelationshipDefinition ddReference, Object dataObject,
154             Class<?> dataObjectClass, String attributeName, String attributePrefix, boolean keysOnly,
155             boolean supportsLookup, boolean supportsInquiry) {
156         DataObjectRelationship relationship = null;
157 
158         // if it is nested then replace the data object and attributeName with the
159         // sub-refs
160         if (ObjectUtils.isNestedAttribute(attributeName)) {
161             if (ddReference != null) {
162                 if (classHasSupportedFeatures(ddReference.getTargetClass(), supportsLookup, supportsInquiry)) {
163                     relationship = populateRelationshipFromDictionaryReference(dataObjectClass, ddReference,
164                             attributePrefix, keysOnly);
165 
166                     return relationship;
167                 }
168             }
169 
170             // recurse down to the next object to find the relationship
171             String localPrefix = StringUtils.substringBefore(attributeName, ".");
172             String localAttributeName = StringUtils.substringAfter(attributeName, ".");
173             if (dataObject == null) {
174                 dataObject = ObjectUtils.createNewObjectFromClass(dataObjectClass);
175             }
176 
177             Object nestedObject = ObjectPropertyUtils.getPropertyValue(dataObject, localPrefix);
178             Class<?> nestedClass = null;
179             if (nestedObject == null) {
180                 nestedClass = ObjectPropertyUtils.getPropertyType(dataObject, localPrefix);
181             } else {
182                 nestedClass = nestedObject.getClass();
183             }
184 
185             String fullPrefix = localPrefix;
186             if (StringUtils.isNotBlank(attributePrefix)) {
187                 fullPrefix = attributePrefix + "." + localPrefix;
188             }
189 
190             relationship = getDataObjectRelationship(nestedObject, nestedClass, localAttributeName, fullPrefix,
191                     keysOnly, supportsLookup, supportsInquiry);
192 
193             return relationship;
194         }
195 
196         // non-nested reference, get persistence relationships first
197         int maxSize = Integer.MAX_VALUE;
198 
199         // try persistable reference first
200         if (getPersistenceStructureService().isPersistable(dataObjectClass)) {
201             Map<String, DataObjectRelationship> rels = getPersistenceStructureService().getRelationshipMetadata(
202                     dataObjectClass, attributeName, attributePrefix);
203             if (rels.size() > 0) {
204                 for (DataObjectRelationship rel : rels.values()) {
205                     if (rel.getParentToChildReferences().size() < maxSize) {
206                         if (classHasSupportedFeatures(rel.getRelatedClass(), supportsLookup, supportsInquiry)) {
207                             maxSize = rel.getParentToChildReferences().size();
208                             relationship = rel;
209                         }
210                     }
211                 }
212             }
213         } else {
214             ModuleService moduleService = getKualiModuleService().getResponsibleModuleService(dataObjectClass);
215             if (moduleService != null && moduleService.isExternalizable(dataObjectClass)) {
216                 relationship = getRelationshipMetadata(dataObjectClass, attributeName, attributePrefix);
217                 if ((relationship != null) && classHasSupportedFeatures(relationship.getRelatedClass(), supportsLookup,
218                         supportsInquiry)) {
219                     return relationship;
220                 } else {
221                     return null;
222                 }
223             }
224         }
225 
226         if (ddReference != null && ddReference.getPrimitiveAttributes().size() < maxSize) {
227             if (classHasSupportedFeatures(ddReference.getTargetClass(), supportsLookup, supportsInquiry)) {
228                 relationship = populateRelationshipFromDictionaryReference(dataObjectClass, ddReference, null,
229                         keysOnly);
230             }
231         }
232 
233         return relationship;
234     }
235 
236     protected boolean classHasSupportedFeatures(Class relationshipClass, boolean supportsLookup,
237             boolean supportsInquiry) {
238         boolean hasSupportedFeatures = true;
239         if (supportsLookup && !getViewDictionaryService().isLookupable(relationshipClass)) {
240             hasSupportedFeatures = false;
241         }
242         if (supportsInquiry && !getViewDictionaryService().isInquirable(relationshipClass)) {
243             hasSupportedFeatures = false;
244         }
245 
246         return hasSupportedFeatures;
247     }
248 
249     public RelationshipDefinition getDictionaryRelationship(Class<?> c, String attributeName) {
250         DataDictionaryEntry entryBase = getDataDictionaryService().getDataDictionary().getDictionaryObjectEntry(
251                 c.getName());
252         if (entryBase == null) {
253             return null;
254         }
255 
256         RelationshipDefinition relationship = null;
257 
258         List<RelationshipDefinition> ddRelationships = entryBase.getRelationships();
259 
260         int minKeys = Integer.MAX_VALUE;
261         for (RelationshipDefinition def : ddRelationships) {
262             // favor key sizes of 1 first
263             if (def.getPrimitiveAttributes().size() == 1) {
264                 for (PrimitiveAttributeDefinition primitive : def.getPrimitiveAttributes()) {
265                     if (primitive.getSourceName().equals(attributeName) || def.getObjectAttributeName().equals(
266                             attributeName)) {
267                         relationship = def;
268                         minKeys = 1;
269                         break;
270                     }
271                 }
272             } else if (def.getPrimitiveAttributes().size() < minKeys) {
273                 for (PrimitiveAttributeDefinition primitive : def.getPrimitiveAttributes()) {
274                     if (primitive.getSourceName().equals(attributeName) || def.getObjectAttributeName().equals(
275                             attributeName)) {
276                         relationship = def;
277                         minKeys = def.getPrimitiveAttributes().size();
278                         break;
279                     }
280                 }
281             }
282         }
283 
284         // check the support attributes
285         if (relationship == null) {
286             for (RelationshipDefinition def : ddRelationships) {
287                 if (def.hasIdentifier()) {
288                     if (def.getIdentifier().getSourceName().equals(attributeName)) {
289                         relationship = def;
290                     }
291                 }
292             }
293         }
294 
295         return relationship;
296     }
297 
298     protected DataObjectRelationship populateRelationshipFromDictionaryReference(Class<?> dataObjectClass,
299             RelationshipDefinition ddReference, String attributePrefix, boolean keysOnly) {
300         DataObjectRelationship relationship = new DataObjectRelationship(dataObjectClass,
301                 ddReference.getObjectAttributeName(), ddReference.getTargetClass());
302 
303         for (PrimitiveAttributeDefinition def : ddReference.getPrimitiveAttributes()) {
304             if (StringUtils.isNotBlank(attributePrefix)) {
305                 relationship.getParentToChildReferences().put(attributePrefix + "." + def.getSourceName(),
306                         def.getTargetName());
307             } else {
308                 relationship.getParentToChildReferences().put(def.getSourceName(), def.getTargetName());
309             }
310         }
311 
312         if (!keysOnly) {
313             for (SupportAttributeDefinition def : ddReference.getSupportAttributes()) {
314                 if (StringUtils.isNotBlank(attributePrefix)) {
315                     relationship.getParentToChildReferences().put(attributePrefix + "." + def.getSourceName(),
316                             def.getTargetName());
317                     if (def.isIdentifier()) {
318                         relationship.setUserVisibleIdentifierKey(attributePrefix + "." + def.getSourceName());
319                     }
320                 } else {
321                     relationship.getParentToChildReferences().put(def.getSourceName(), def.getTargetName());
322                     if (def.isIdentifier()) {
323                         relationship.setUserVisibleIdentifierKey(def.getSourceName());
324                     }
325                 }
326             }
327         }
328 
329         return relationship;
330     }
331 
332     protected DataObjectRelationship getRelationshipMetadata(Class<?> dataObjectClass, String attributeName,
333             String attributePrefix) {
334 
335         RelationshipDefinition relationshipDefinition = getDictionaryRelationship(dataObjectClass, attributeName);
336         if (relationshipDefinition == null) {
337             return null;
338         }
339 
340         DataObjectRelationship dataObjectRelationship = new DataObjectRelationship(
341                 relationshipDefinition.getSourceClass(), relationshipDefinition.getObjectAttributeName(),
342                 relationshipDefinition.getTargetClass());
343 
344         if (!StringUtils.isEmpty(attributePrefix)) {
345             attributePrefix += ".";
346         }
347 
348         List<PrimitiveAttributeDefinition> primitives = relationshipDefinition.getPrimitiveAttributes();
349         for (PrimitiveAttributeDefinition primitiveAttributeDefinition : primitives) {
350             dataObjectRelationship.getParentToChildReferences().put(
351                     attributePrefix + primitiveAttributeDefinition.getSourceName(),
352                     primitiveAttributeDefinition.getTargetName());
353         }
354 
355         return dataObjectRelationship;
356     }
357 
358     /**
359      * @see org.kuali.rice.krad.service.DataObjectMetaDataService#getTitleAttribute(java.lang.Class)
360      */
361     @Override
362     public String getTitleAttribute(Class<?> dataObjectClass) {
363         String titleAttribute = null;
364 
365         DataObjectEntry entry = getDataObjectEntry(dataObjectClass);
366         if (entry != null) {
367             titleAttribute = entry.getTitleAttribute();
368         }
369 
370         return titleAttribute;
371     }
372 
373     /**
374      * @see org.kuali.rice.krad.service.DataObjectMetaDataService#areNotesSupported(java.lang.Class)
375      */
376     @Override
377     public boolean areNotesSupported(Class dataObjectClass) {
378         boolean hasNotesSupport = false;
379 
380         DataObjectEntry entry = getDataObjectEntry(UifControllerBase.class);
381         if (entry != null) {
382             hasNotesSupport = entry.isBoNotesEnabled();
383         }
384 
385         return hasNotesSupport;
386     }
387 
388     /**
389      * @param dataObjectClass
390      * @return DataObjectEntry for the given dataObjectClass, or null if
391      *         there is none
392      * @throws IllegalArgumentException if the given Class is null
393      */
394     protected DataObjectEntry getDataObjectEntry(Class<?> dataObjectClass) {
395         if (dataObjectClass == null) {
396             throw new IllegalArgumentException("invalid (null) dataObjectClass");
397         }
398 
399         DataObjectEntry entry = getDataDictionaryService().getDataDictionary().getDataObjectEntry(
400                 dataObjectClass.getName());
401 
402         return entry;
403     }
404 
405     /**
406      * @param businessObjectClass - class of business object to return entry for
407      * @return BusinessObjectEntry for the given dataObjectClass, or null if
408      *         there is none
409      */
410     protected BusinessObjectEntry getBusinessObjectEntry(Class businessObjectClass) {
411         validateBusinessObjectClass(businessObjectClass);
412 
413         BusinessObjectEntry entry = getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(
414                 businessObjectClass.getName());
415         return entry;
416     }
417 
418     /**
419      * @param businessObjectClass
420      * @throws IllegalArgumentException if the given Class is null or is not a BusinessObject class
421      */
422     protected void validateBusinessObjectClass(Class businessObjectClass) {
423         if (businessObjectClass == null) {
424             throw new IllegalArgumentException("invalid (null) dataObjectClass");
425         }
426         if (!BusinessObject.class.isAssignableFrom(businessObjectClass)) {
427             throw new IllegalArgumentException(
428                     "class '" + businessObjectClass.getName() + "' is not a descendant of BusinessObject");
429         }
430     }
431 
432     /**
433      * Protected method to allow subclasses to access the dataDictionaryService.
434      *
435      * @return Returns the dataDictionaryService.
436      */
437     protected DataDictionaryService getDataDictionaryService() {
438         return this.dataDictionaryService;
439     }
440 
441     public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
442         this.dataDictionaryService = dataDictionaryService;
443     }
444 
445     /**
446      * Protected method to allow subclasses to access the kualiModuleService.
447      *
448      * @return Returns the persistenceStructureService.
449      */
450     protected KualiModuleService getKualiModuleService() {
451         return this.kualiModuleService;
452     }
453 
454     public void setKualiModuleService(KualiModuleService kualiModuleService) {
455         this.kualiModuleService = kualiModuleService;
456     }
457 
458     /**
459      * Protected method to allow subclasses to access the
460      * persistenceStructureService.
461      *
462      * @return Returns the persistenceStructureService.
463      */
464     protected PersistenceStructureService getPersistenceStructureService() {
465         return this.persistenceStructureService;
466     }
467 
468     public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) {
469         this.persistenceStructureService = persistenceStructureService;
470     }
471 
472     protected ViewDictionaryService getViewDictionaryService() {
473         if (this.viewDictionaryService == null) {
474             this.viewDictionaryService = KRADServiceLocatorWeb.getViewDictionaryService();
475         }
476         return this.viewDictionaryService;
477     }
478 
479     public void setViewDictionaryService(ViewDictionaryService viewDictionaryService) {
480         this.viewDictionaryService = viewDictionaryService;
481     }
482 
483 }