View Javadoc

1   /**
2    * Copyright 2005-2012 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.datadictionary;
17  
18  import org.apache.commons.beanutils.PropertyUtils;
19  import org.apache.commons.collections.ListUtils;
20  import org.apache.commons.lang.ArrayUtils;
21  import org.apache.commons.lang.StringUtils;
22  import org.apache.commons.logging.Log;
23  import org.apache.commons.logging.LogFactory;
24  import org.kuali.rice.core.api.util.ClassLoaderUtils;
25  import org.kuali.rice.krad.bo.PersistableBusinessObjectExtension;
26  import org.kuali.rice.krad.datadictionary.exception.AttributeValidationException;
27  import org.kuali.rice.krad.datadictionary.exception.CompletionException;
28  import org.kuali.rice.krad.datadictionary.parse.StringListConverter;
29  import org.kuali.rice.krad.datadictionary.parse.StringMapConverter;
30  import org.kuali.rice.krad.datadictionary.uif.UifDictionaryIndex;
31  import org.kuali.rice.krad.service.KRADServiceLocator;
32  import org.kuali.rice.krad.service.PersistenceStructureService;
33  import org.kuali.rice.krad.uif.UifConstants.ViewType;
34  import org.kuali.rice.krad.uif.util.ComponentBeanPostProcessor;
35  import org.kuali.rice.krad.uif.util.UifBeanFactoryPostProcessor;
36  import org.kuali.rice.krad.uif.view.View;
37  import org.kuali.rice.krad.util.ObjectUtils;
38  import org.springframework.beans.PropertyValues;
39  import org.springframework.beans.factory.config.BeanPostProcessor;
40  import org.springframework.beans.factory.support.KualiDefaultListableBeanFactory;
41  import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
42  import org.springframework.context.expression.StandardBeanExpressionResolver;
43  import org.springframework.core.convert.support.GenericConversionService;
44  import org.springframework.core.io.DefaultResourceLoader;
45  import org.springframework.core.io.Resource;
46  
47  import java.beans.PropertyDescriptor;
48  import java.io.File;
49  import java.io.IOException;
50  import java.util.ArrayList;
51  import java.util.Arrays;
52  import java.util.Collection;
53  import java.util.HashMap;
54  import java.util.List;
55  import java.util.Map;
56  import java.util.Set;
57  import java.util.TreeMap;
58  
59  /**
60   * Encapsulates a bean factory and indexes to the beans within the factory for providing
61   * framework metadata
62   *
63   * @author Kuali Rice Team (rice.collab@kuali.org)
64   */
65  public class DataDictionary {
66      private static final Log LOG = LogFactory.getLog(DataDictionary.class);
67  
68      protected static boolean validateEBOs = true;
69  
70      protected KualiDefaultListableBeanFactory ddBeans = new KualiDefaultListableBeanFactory();
71      protected XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(ddBeans);
72  
73      protected DataDictionaryIndex ddIndex = new DataDictionaryIndex(ddBeans);
74      protected UifDictionaryIndex uifIndex = new UifDictionaryIndex(ddBeans);
75  
76      protected DataDictionaryMapper ddMapper = new DataDictionaryIndexMapper();
77  
78      protected Map<String, List<String>> moduleDictionaryFiles = new HashMap<String, List<String>>();
79      protected List<String> moduleLoadOrder = new ArrayList<String>();
80  
81      protected ArrayList<String> beanValidationFiles = new ArrayList<String>();
82  
83      /**
84       * Populates and processes the dictionary bean factory based on the configured files and
85       * performs indexing
86       *
87       * @param allowConcurrentValidation - indicates whether the indexing should occur on a different thread
88       * or the same thread
89       */
90      public void parseDataDictionaryConfigurationFiles(boolean allowConcurrentValidation) {
91          setupProcessor(ddBeans);
92  
93          loadDictionaryBeans(ddBeans, moduleDictionaryFiles, ddIndex, beanValidationFiles);
94  
95          performDictionaryPostProcessing(allowConcurrentValidation);
96      }
97  
98      /**
99       * Sets up the bean post processor and conversion service
100      *
101      * @param beans - The bean factory for the the dictionary beans
102      */
103     public static void setupProcessor(KualiDefaultListableBeanFactory beans) {
104         try {
105             // UIF post processor that sets component ids
106             BeanPostProcessor idPostProcessor = ComponentBeanPostProcessor.class.newInstance();
107             beans.addBeanPostProcessor(idPostProcessor);
108             beans.setBeanExpressionResolver(new StandardBeanExpressionResolver());
109 
110             // special converters for shorthand map and list property syntax
111             GenericConversionService conversionService = new GenericConversionService();
112             conversionService.addConverter(new StringMapConverter());
113             conversionService.addConverter(new StringListConverter());
114 
115             beans.setConversionService(conversionService);
116         } catch (Exception e1) {
117             throw new DataDictionaryException("Cannot create component decorator post processor: " + e1.getMessage(),
118                     e1);
119         }
120     }
121 
122     /**
123      * Populates and processes the dictionary bean factory based on the configured files
124      *
125      * @param beans - The bean factory for the dictionary bean
126      * @param moduleDictionaryFiles - List of bean xml files
127      * @param index - Index of the data dictionary beans
128      * @param validationFiles - The List of bean xml files loaded into the bean file
129      */
130     public void loadDictionaryBeans(KualiDefaultListableBeanFactory beans,
131             Map<String, List<String>> moduleDictionaryFiles, DataDictionaryIndex index,
132             ArrayList<String> validationFiles) {
133         // expand configuration locations into files
134         LOG.info("Starting DD XML File Load");
135 
136         List<String> allBeanNames = new ArrayList<String>();
137         for (String namespaceCode : moduleLoadOrder) {
138             List<String> moduleDictionaryLocations = moduleDictionaryFiles.get(namespaceCode);
139 
140             XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(beans);
141 
142             String configFileLocationsArray[] = new String[moduleDictionaryLocations.size()];
143             configFileLocationsArray = moduleDictionaryLocations.toArray(configFileLocationsArray);
144             for (int i = 0; i < configFileLocationsArray.length; i++) {
145                 validationFiles.add(configFileLocationsArray[i]);
146             }
147 
148             try {
149                 xmlReader.loadBeanDefinitions(configFileLocationsArray);
150 
151                 // get updated bean names from factory and compare to our previous list to get those that
152                 // were added by the last namespace
153                 List<String> addedBeanNames = Arrays.asList(beans.getBeanDefinitionNames());
154                 addedBeanNames = ListUtils.removeAll(addedBeanNames, allBeanNames);
155                 index.addBeanNamesToNamespace(namespaceCode, addedBeanNames);
156 
157                 allBeanNames.addAll(addedBeanNames);
158             } catch (Exception e) {
159                 throw new DataDictionaryException("Error loading bean definitions: " + e.getLocalizedMessage());
160             }
161         }
162 
163         LOG.info("Completed DD XML File Load");
164     }
165 
166     /**
167      * Invokes post processors and builds indexes for the beans contained in the dictionary
168      *
169      * @param allowConcurrentValidation - indicates whether the indexing should occur on a different thread
170      * or the same thread
171      */
172     public void performDictionaryPostProcessing(boolean allowConcurrentValidation) {
173         // invoke post processing of the dictionary bean definitions
174         DictionaryBeanFactoryPostProcessor dictionaryBeanPostProcessor = new DictionaryBeanFactoryPostProcessor(this,
175                 ddBeans);
176         dictionaryBeanPostProcessor.postProcessBeanFactory();
177 
178         // post processes UIF beans for pulling out expressions within property values
179         UifBeanFactoryPostProcessor factoryPostProcessor = new UifBeanFactoryPostProcessor();
180         factoryPostProcessor.postProcessBeanFactory(ddBeans);
181 
182         if (allowConcurrentValidation) {
183             Thread t = new Thread(ddIndex);
184             t.start();
185 
186             Thread t2 = new Thread(uifIndex);
187             t2.start();
188         } else {
189             ddIndex.run();
190             uifIndex.run();
191         }
192     }
193 
194     public void validateDD(boolean validateEbos) {
195         DataDictionary.validateEBOs = validateEbos;
196 
197         /*  ValidationController validator = new ValidationController();
198     String files[] = new String[beanValidationFiles.size()];
199     files = beanValidationFiles.toArray(files);
200     validator.validate(files, xmlReader.getResourceLoader(), ddBeans, LOG, false);*/
201 
202         Map<String, DataObjectEntry> doBeans = ddBeans.getBeansOfType(DataObjectEntry.class);
203         for (DataObjectEntry entry : doBeans.values()) {
204             entry.completeValidation();
205         }
206 
207         Map<String, DocumentEntry> docBeans = ddBeans.getBeansOfType(DocumentEntry.class);
208         for (DocumentEntry entry : docBeans.values()) {
209             entry.completeValidation();
210         }
211     }
212 
213     public void validateDD() {
214         validateDD(true);
215     }
216 
217     /**
218      * Adds a location of files or a individual resource to the data dictionary
219      *
220      * <p>
221      * The location can either be an XML file on the classpath or a file or folder location within the
222      * file system. If a folder location is given, the folder and all sub-folders will be traversed and any
223      * XML files will be added to the dictionary
224      * </p>
225      *
226      * @param namespaceCode - namespace the beans loaded from the location should be associated with
227      * @param location - classpath resource or file system location
228      * @throws IOException
229      */
230     public void addConfigFileLocation(String namespaceCode, String location) throws IOException {
231         // add module to load order so we load in the order modules were configured
232         if (!moduleLoadOrder.contains(namespaceCode)) {
233             moduleLoadOrder.add(namespaceCode);
234         }
235 
236         indexSource(namespaceCode, location);
237     }
238 
239     /**
240      * Processes a given source for XML files to populate the dictionary with
241      *
242      * @param namespaceCode - namespace the beans loaded from the location should be associated with
243      * @param sourceName - a file system or classpath resource locator
244      * @throws IOException
245      */
246     protected void indexSource(String namespaceCode, String sourceName) throws IOException {
247         if (sourceName == null) {
248             throw new DataDictionaryException("Source Name given is null");
249         }
250 
251         if (!sourceName.endsWith(".xml")) {
252             Resource resource = getFileResource(sourceName);
253             if (resource.exists()) {
254                 try {
255                     indexSource(namespaceCode, resource.getFile());
256                 } catch (IOException e) {
257                     // ignore resources that exist and cause an error here
258                     // they may be directories resident in jar files
259                     LOG.debug("Skipped existing resource without absolute file path");
260                 }
261             } else {
262                 LOG.warn("Could not find " + sourceName);
263                 throw new DataDictionaryException("DD Resource " + sourceName + " not found");
264             }
265         } else {
266             if (LOG.isDebugEnabled()) {
267                 LOG.debug("adding sourceName " + sourceName + " ");
268             }
269 
270             Resource resource = getFileResource(sourceName);
271             if (!resource.exists()) {
272                 throw new DataDictionaryException("DD Resource " + sourceName + " not found");
273             }
274 
275             addModuleDictionaryFile(namespaceCode, sourceName);
276         }
277     }
278 
279     protected Resource getFileResource(String sourceName) {
280         DefaultResourceLoader resourceLoader = new DefaultResourceLoader(ClassLoaderUtils.getDefaultClassLoader());
281 
282         return resourceLoader.getResource(sourceName);
283     }
284 
285     protected void indexSource(String namespaceCode, File dir) {
286         for (File file : dir.listFiles()) {
287             if (file.isDirectory()) {
288                 indexSource(namespaceCode, file);
289             } else if (file.getName().endsWith(".xml")) {
290                 addModuleDictionaryFile(namespaceCode, "file:" + file.getAbsolutePath());
291             } else {
292                 if (LOG.isDebugEnabled()) {
293                     LOG.debug("Skipping non xml file " + file.getAbsolutePath() + " in DD load");
294                 }
295             }
296         }
297     }
298 
299     /**
300      * Adds a file location to the list of dictionary files for the given namespace code
301      *
302      * @param namespaceCode - namespace to add location for
303      * @param location - file or resource location to add
304      */
305     protected void addModuleDictionaryFile(String namespaceCode, String location) {
306         List<String> moduleFileLocations = new ArrayList<String>();
307         if (moduleDictionaryFiles.containsKey(namespaceCode)) {
308             moduleFileLocations = moduleDictionaryFiles.get(namespaceCode);
309         }
310         moduleFileLocations.add(location);
311 
312         moduleDictionaryFiles.put(namespaceCode, moduleFileLocations);
313     }
314 
315     /**
316      * Mapping of namespace codes to dictionary files that are associated with
317      * that namespace
318      *
319      * @return Map<String, List<String>> where map key is namespace code, and value is list of dictionary
320      *         file locations
321      */
322     public Map<String, List<String>> getModuleDictionaryFiles() {
323         return moduleDictionaryFiles;
324     }
325 
326     /**
327      * Setter for the map of module dictionary files
328      *
329      * @param moduleDictionaryFiles
330      */
331     public void setModuleDictionaryFiles(Map<String, List<String>> moduleDictionaryFiles) {
332         this.moduleDictionaryFiles = moduleDictionaryFiles;
333     }
334 
335     /**
336      * Order modules should be loaded into the dictionary
337      *
338      * <p>
339      * Modules are loaded in the order they are found in this list. If not explicity set, they will be loaded in
340      * the order their dictionary file locations are added
341      * </p>
342      *
343      * @return List<String> list of namespace codes indicating the module load order
344      */
345     public List<String> getModuleLoadOrder() {
346         return moduleLoadOrder;
347     }
348 
349     /**
350      * Setter for the list of namespace codes indicating the module load order
351      *
352      * @param moduleLoadOrder
353      */
354     public void setModuleLoadOrder(List<String> moduleLoadOrder) {
355         this.moduleLoadOrder = moduleLoadOrder;
356     }
357 
358     /**
359      * Sets the DataDictionaryMapper
360      *
361      * @param mapper the datadictionary mapper
362      */
363     public void setDataDictionaryMapper(DataDictionaryMapper mapper) {
364         this.ddMapper = mapper;
365     }
366 
367     /**
368      * @param className
369      * @return BusinessObjectEntry for the named class, or null if none exists
370      */
371     @Deprecated
372     public BusinessObjectEntry getBusinessObjectEntry(String className) {
373         return ddMapper.getBusinessObjectEntry(ddIndex, className);
374     }
375 
376     /**
377      * @param className
378      * @return BusinessObjectEntry for the named class, or null if none exists
379      */
380     public DataObjectEntry getDataObjectEntry(String className) {
381         return ddMapper.getDataObjectEntry(ddIndex, className);
382     }
383 
384     /**
385      * This method gets the business object entry for a concrete class
386      *
387      * @param className
388      * @return
389      */
390     public BusinessObjectEntry getBusinessObjectEntryForConcreteClass(String className) {
391         return ddMapper.getBusinessObjectEntryForConcreteClass(ddIndex, className);
392     }
393 
394     /**
395      * @return List of businessObject classnames
396      */
397     public List<String> getBusinessObjectClassNames() {
398         return ddMapper.getBusinessObjectClassNames(ddIndex);
399     }
400 
401     /**
402      * @return Map of (classname, BusinessObjectEntry) pairs
403      */
404     public Map<String, BusinessObjectEntry> getBusinessObjectEntries() {
405         return ddMapper.getBusinessObjectEntries(ddIndex);
406     }
407 
408     /**
409      * @param className
410      * @return DataDictionaryEntryBase for the named class, or null if none
411      *         exists
412      */
413     public DataDictionaryEntry getDictionaryObjectEntry(String className) {
414         return ddMapper.getDictionaryObjectEntry(ddIndex, className);
415     }
416 
417     /**
418      * Returns the KNS document entry for the given lookup key.  The documentTypeDDKey is interpreted
419      * successively in the following ways until a mapping is found (or none if found):
420      * <ol>
421      * <li>KEW/workflow document type</li>
422      * <li>business object class name</li>
423      * <li>maintainable class name</li>
424      * </ol>
425      * This mapping is compiled when DataDictionary files are parsed on startup (or demand).  Currently this
426      * means the mapping is static, and one-to-one (one KNS document maps directly to one and only
427      * one key).
428      *
429      * @param documentTypeDDKey the KEW/workflow document type name
430      * @return the KNS DocumentEntry if it exists
431      */
432     public DocumentEntry getDocumentEntry(String documentTypeDDKey) {
433         return ddMapper.getDocumentEntry(ddIndex, documentTypeDDKey);
434     }
435 
436     /**
437      * Note: only MaintenanceDocuments are indexed by businessObject Class
438      *
439      * This is a special case that is referenced in one location. Do we need
440      * another map for this stuff??
441      *
442      * @param businessObjectClass
443      * @return DocumentEntry associated with the given Class, or null if there
444      *         is none
445      */
446     public MaintenanceDocumentEntry getMaintenanceDocumentEntryForBusinessObjectClass(Class<?> businessObjectClass) {
447         return ddMapper.getMaintenanceDocumentEntryForBusinessObjectClass(ddIndex, businessObjectClass);
448     }
449 
450     public Map<String, DocumentEntry> getDocumentEntries() {
451         return ddMapper.getDocumentEntries(ddIndex);
452     }
453 
454     /**
455      * Returns the View entry identified by the given id
456      *
457      * @param viewId - unique id for view
458      * @return View instance associated with the id
459      */
460     public View getViewById(String viewId) {
461         return ddMapper.getViewById(uifIndex, viewId);
462     }
463 
464     /**
465      * Returns View instance identified by the view type name and index
466      *
467      * @param viewTypeName - type name for the view
468      * @param indexKey - Map of index key parameters, these are the parameters the
469      * indexer used to index the view initially and needs to identify
470      * an unique view instance
471      * @return View instance that matches the given index
472      */
473     public View getViewByTypeIndex(ViewType viewTypeName, Map<String, String> indexKey) {
474         return ddMapper.getViewByTypeIndex(uifIndex, viewTypeName, indexKey);
475     }
476 
477     /**
478      * Indicates whether a <code>View</code> exists for the given view type and index information
479      *
480      * @param viewTypeName - type name for the view
481      * @param indexKey - Map of index key parameters, these are the parameters the
482      * indexer used to index the view initially and needs to identify
483      * an unique view instance
484      * @return boolean true if view exists, false if not
485      */
486     public boolean viewByTypeExist(ViewType viewTypeName, Map<String, String> indexKey) {
487         return ddMapper.viewByTypeExist(uifIndex, viewTypeName, indexKey);
488     }
489 
490     /**
491      * Gets all <code>View</code> prototypes configured for the given view type
492      * name
493      *
494      * @param viewTypeName - view type name to retrieve
495      * @return List<View> view prototypes with the given type name, or empty
496      *         list
497      */
498     public List<View> getViewsForType(ViewType viewTypeName) {
499         return ddMapper.getViewsForType(uifIndex, viewTypeName);
500     }
501 
502     /**
503      * Returns an object from the dictionary by its spring bean name
504      *
505      * @param beanName - id or name for the bean definition
506      * @return Object object instance created or the singleton being maintained
507      */
508     public Object getDictionaryObject(String beanName) {
509         return ddBeans.getBean(beanName);
510     }
511 
512     /**
513      * Indicates whether the data dictionary contains a bean with the given id
514      *
515      * @param id - id of the bean to check for
516      * @return boolean true if dictionary contains bean, false otherwise
517      */
518     public boolean containsDictionaryObject(String id) {
519         return ddBeans.containsBean(id);
520     }
521 
522     /**
523      * Retrieves the configured property values for the view bean definition associated with the given id
524      *
525      * <p>
526      * Since constructing the View object can be expensive, when metadata only is needed this method can be used
527      * to retrieve the configured property values. Note this looks at the merged bean definition
528      * </p>
529      *
530      * @param viewId - id for the view to retrieve
531      * @return PropertyValues configured on the view bean definition, or null if view is not found
532      */
533     public PropertyValues getViewPropertiesById(String viewId) {
534         return ddMapper.getViewPropertiesById(uifIndex, viewId);
535     }
536 
537     /**
538      * Retrieves the configured property values for the view bean definition associated with the given type and
539      * index
540      *
541      * <p>
542      * Since constructing the View object can be expensive, when metadata only is needed this method can be used
543      * to retrieve the configured property values. Note this looks at the merged bean definition
544      * </p>
545      *
546      * @param viewTypeName - type name for the view
547      * @param indexKey - Map of index key parameters, these are the parameters the indexer used to index
548      * the view initially and needs to identify an unique view instance
549      * @return PropertyValues configured on the view bean definition, or null if view is not found
550      */
551     public PropertyValues getViewPropertiesByType(ViewType viewTypeName, Map<String, String> indexKey) {
552         return ddMapper.getViewPropertiesByType(uifIndex, viewTypeName, indexKey);
553     }
554 
555     /**
556      * Retrieves the list of dictionary bean names that are associated with the given namespace code
557      *
558      * @param namespaceCode - namespace code to retrieve associated bean names for
559      * @return List<String> bean names associated with the namespace
560      */
561     public List<String> getBeanNamesForNamespace(String namespaceCode) {
562         List<String> namespaceBeans = new ArrayList<String>();
563 
564         Map<String, List<String>> dictionaryBeansByNamespace = ddIndex.getDictionaryBeansByNamespace();
565         if (dictionaryBeansByNamespace.containsKey(namespaceCode)) {
566             namespaceBeans = dictionaryBeansByNamespace.get(namespaceCode);
567         }
568 
569         return namespaceBeans;
570     }
571 
572     /**
573      * Retrieves the namespace code the given bean name is associated with
574      *
575      * @param beanName - name of the dictionary bean to find namespace code for
576      * @return String namespace code the bean is associated with, or null if a namespace was not found
577      */
578     public String getNamespaceForBeanDefinition(String beanName) {
579         String beanNamespace = null;
580 
581         Map<String, List<String>> dictionaryBeansByNamespace = ddIndex.getDictionaryBeansByNamespace();
582         for (Map.Entry<String, List<String>> moduleDefinitions : dictionaryBeansByNamespace.entrySet()) {
583             List<String> namespaceBeans = moduleDefinitions.getValue();
584             if (namespaceBeans.contains(beanName)) {
585                 beanNamespace = moduleDefinitions.getKey();
586                 break;
587             }
588         }
589 
590         return beanNamespace;
591     }
592 
593     /**
594      * @param targetClass
595      * @param propertyName
596      * @return true if the given propertyName names a property of the given class
597      * @throws CompletionException if there is a problem accessing the named property on the given class
598      */
599     public static boolean isPropertyOf(Class targetClass, String propertyName) {
600         if (targetClass == null) {
601             throw new IllegalArgumentException("invalid (null) targetClass");
602         }
603         if (StringUtils.isBlank(propertyName)) {
604             throw new IllegalArgumentException("invalid (blank) propertyName");
605         }
606 
607         PropertyDescriptor propertyDescriptor = buildReadDescriptor(targetClass, propertyName);
608 
609         boolean isPropertyOf = (propertyDescriptor != null);
610         return isPropertyOf;
611     }
612 
613     /**
614      * @param targetClass
615      * @param propertyName
616      * @return true if the given propertyName names a Collection property of the given class
617      * @throws CompletionException if there is a problem accessing the named property on the given class
618      */
619     public static boolean isCollectionPropertyOf(Class targetClass, String propertyName) {
620         boolean isCollectionPropertyOf = false;
621 
622         PropertyDescriptor propertyDescriptor = buildReadDescriptor(targetClass, propertyName);
623         if (propertyDescriptor != null) {
624             Class clazz = propertyDescriptor.getPropertyType();
625 
626             if ((clazz != null) && Collection.class.isAssignableFrom(clazz)) {
627                 isCollectionPropertyOf = true;
628             }
629         }
630 
631         return isCollectionPropertyOf;
632     }
633 
634     public static PersistenceStructureService persistenceStructureService;
635 
636     /**
637      * @return the persistenceStructureService
638      */
639     public static PersistenceStructureService getPersistenceStructureService() {
640         if (persistenceStructureService == null) {
641             persistenceStructureService = KRADServiceLocator.getPersistenceStructureService();
642         }
643         return persistenceStructureService;
644     }
645 
646     /**
647      * This method determines the Class of the attributeName passed in. Null will be returned if the member is not
648      * available, or if
649      * a reflection exception is thrown.
650      *
651      * @param boClass - Class that the attributeName property exists in.
652      * @param attributeName - Name of the attribute you want a class for.
653      * @return The Class of the attributeName, if the attribute exists on the rootClass. Null otherwise.
654      */
655     public static Class getAttributeClass(Class boClass, String attributeName) {
656 
657         // fail loudly if the attributeName isnt a member of rootClass
658         if (!isPropertyOf(boClass, attributeName)) {
659             throw new AttributeValidationException(
660                     "unable to find attribute '" + attributeName + "' in rootClass '" + boClass.getName() + "'");
661         }
662 
663         //Implementing Externalizable Business Object Services...
664         //The boClass can be an interface, hence handling this separately, 
665         //since the original method was throwing exception if the class could not be instantiated.
666         if (boClass.isInterface()) {
667             return getAttributeClassWhenBOIsInterface(boClass, attributeName);
668         } else {
669             return getAttributeClassWhenBOIsClass(boClass, attributeName);
670         }
671 
672     }
673 
674     /**
675      * This method gets the property type of the given attributeName when the bo class is a concrete class
676      *
677      * @param boClass
678      * @param attributeName
679      * @return
680      */
681     private static Class getAttributeClassWhenBOIsClass(Class boClass, String attributeName) {
682         Object boInstance;
683         try {
684             boInstance = boClass.newInstance();
685         } catch (Exception e) {
686             throw new RuntimeException("Unable to instantiate Data Object: " + boClass, e);
687         }
688 
689         // attempt to retrieve the class of the property
690         try {
691             return ObjectUtils.getPropertyType(boInstance, attributeName, getPersistenceStructureService());
692         } catch (Exception e) {
693             throw new RuntimeException(
694                     "Unable to determine property type for: " + boClass.getName() + "." + attributeName, e);
695         }
696     }
697 
698     /**
699      * This method gets the property type of the given attributeName when the bo class is an interface
700      * This method will also work if the bo class is not an interface,
701      * but that case requires special handling, hence a separate method getAttributeClassWhenBOIsClass
702      *
703      * @param boClass
704      * @param attributeName
705      * @return
706      */
707     private static Class getAttributeClassWhenBOIsInterface(Class boClass, String attributeName) {
708         if (boClass == null) {
709             throw new IllegalArgumentException("invalid (null) boClass");
710         }
711         if (StringUtils.isBlank(attributeName)) {
712             throw new IllegalArgumentException("invalid (blank) attributeName");
713         }
714 
715         PropertyDescriptor propertyDescriptor = null;
716 
717         String[] intermediateProperties = attributeName.split("\\.");
718         int lastLevel = intermediateProperties.length - 1;
719         Class currentClass = boClass;
720 
721         for (int i = 0; i <= lastLevel; ++i) {
722 
723             String currentPropertyName = intermediateProperties[i];
724             propertyDescriptor = buildSimpleReadDescriptor(currentClass, currentPropertyName);
725 
726             if (propertyDescriptor != null) {
727 
728                 Class propertyType = propertyDescriptor.getPropertyType();
729                 if (propertyType.equals(PersistableBusinessObjectExtension.class)) {
730                     propertyType = getPersistenceStructureService().getBusinessObjectAttributeClass(currentClass,
731                             currentPropertyName);
732                 }
733                 if (Collection.class.isAssignableFrom(propertyType)) {
734                     // TODO: determine property type using generics type definition
735                     throw new AttributeValidationException(
736                             "Can't determine the Class of Collection elements because when the business object is an (possibly ExternalizableBusinessObject) interface.");
737                 } else {
738                     currentClass = propertyType;
739                 }
740             } else {
741                 throw new AttributeValidationException(
742                         "Can't find getter method of " + boClass.getName() + " for property " + attributeName);
743             }
744         }
745         return currentClass;
746     }
747 
748     /**
749      * This method determines the Class of the elements in the collectionName passed in.
750      *
751      * @param boClass Class that the collectionName collection exists in.
752      * @param collectionName the name of the collection you want the element class for
753      * @return
754      */
755     public static Class getCollectionElementClass(Class boClass, String collectionName) {
756         if (boClass == null) {
757             throw new IllegalArgumentException("invalid (null) boClass");
758         }
759         if (StringUtils.isBlank(collectionName)) {
760             throw new IllegalArgumentException("invalid (blank) collectionName");
761         }
762 
763         PropertyDescriptor propertyDescriptor = null;
764 
765         String[] intermediateProperties = collectionName.split("\\.");
766         Class currentClass = boClass;
767 
768         for (int i = 0; i < intermediateProperties.length; ++i) {
769 
770             String currentPropertyName = intermediateProperties[i];
771             propertyDescriptor = buildSimpleReadDescriptor(currentClass, currentPropertyName);
772 
773             if (propertyDescriptor != null) {
774 
775                 Class type = propertyDescriptor.getPropertyType();
776                 if (Collection.class.isAssignableFrom(type)) {
777 
778                     if (getPersistenceStructureService().isPersistable(currentClass)) {
779 
780                         Map<String, Class> collectionClasses = new HashMap<String, Class>();
781                         collectionClasses = getPersistenceStructureService().listCollectionObjectTypes(currentClass);
782                         currentClass = collectionClasses.get(currentPropertyName);
783 
784                     } else {
785                         throw new RuntimeException(
786                                 "Can't determine the Class of Collection elements because persistenceStructureService.isPersistable("
787                                         +
788                                         currentClass.getName()
789                                         +
790                                         ") returns false.");
791                     }
792 
793                 } else {
794 
795                     currentClass = propertyDescriptor.getPropertyType();
796 
797                 }
798             }
799         }
800 
801         return currentClass;
802     }
803 
804     static private Map<String, Map<String, PropertyDescriptor>> cache =
805             new TreeMap<String, Map<String, PropertyDescriptor>>();
806 
807     /**
808      * @param propertyClass
809      * @param propertyName
810      * @return PropertyDescriptor for the getter for the named property of the given class, if one exists.
811      */
812     public static PropertyDescriptor buildReadDescriptor(Class propertyClass, String propertyName) {
813         if (propertyClass == null) {
814             throw new IllegalArgumentException("invalid (null) propertyClass");
815         }
816         if (StringUtils.isBlank(propertyName)) {
817             throw new IllegalArgumentException("invalid (blank) propertyName");
818         }
819 
820         PropertyDescriptor propertyDescriptor = null;
821 
822         String[] intermediateProperties = propertyName.split("\\.");
823         int lastLevel = intermediateProperties.length - 1;
824         Class currentClass = propertyClass;
825 
826         for (int i = 0; i <= lastLevel; ++i) {
827 
828             String currentPropertyName = intermediateProperties[i];
829             propertyDescriptor = buildSimpleReadDescriptor(currentClass, currentPropertyName);
830 
831             if (i < lastLevel) {
832 
833                 if (propertyDescriptor != null) {
834 
835                     Class propertyType = propertyDescriptor.getPropertyType();
836                     if (propertyType.equals(PersistableBusinessObjectExtension.class)) {
837                         propertyType = getPersistenceStructureService().getBusinessObjectAttributeClass(currentClass,
838                                 currentPropertyName);
839                     }
840                     if (Collection.class.isAssignableFrom(propertyType)) {
841 
842                         if (getPersistenceStructureService().isPersistable(currentClass)) {
843 
844                             Map<String, Class> collectionClasses = new HashMap<String, Class>();
845                             collectionClasses = getPersistenceStructureService().listCollectionObjectTypes(
846                                     currentClass);
847                             currentClass = collectionClasses.get(currentPropertyName);
848 
849                         } else {
850 
851                             throw new RuntimeException(
852                                     "Can't determine the Class of Collection elements because persistenceStructureService.isPersistable("
853                                             +
854                                             currentClass.getName()
855                                             +
856                                             ") returns false.");
857 
858                         }
859 
860                     } else {
861 
862                         currentClass = propertyType;
863 
864                     }
865 
866                 }
867 
868             }
869 
870         }
871 
872         return propertyDescriptor;
873     }
874 
875     /**
876      * @param propertyClass
877      * @param propertyName
878      * @return PropertyDescriptor for the getter for the named property of the given class, if one exists.
879      */
880     public static PropertyDescriptor buildSimpleReadDescriptor(Class propertyClass, String propertyName) {
881         if (propertyClass == null) {
882             throw new IllegalArgumentException("invalid (null) propertyClass");
883         }
884         if (StringUtils.isBlank(propertyName)) {
885             throw new IllegalArgumentException("invalid (blank) propertyName");
886         }
887 
888         PropertyDescriptor p = null;
889 
890         // check to see if we've cached this descriptor already. if yes, return true.
891         String propertyClassName = propertyClass.getName();
892         Map<String, PropertyDescriptor> m = cache.get(propertyClassName);
893         if (null != m) {
894             p = m.get(propertyName);
895             if (null != p) {
896                 return p;
897             }
898         }
899 
900         // Use PropertyUtils.getPropertyDescriptors instead of manually constructing PropertyDescriptor because of
901         // issues with introspection and generic/co-variant return types
902         // See https://issues.apache.org/jira/browse/BEANUTILS-340 for more details
903 
904         PropertyDescriptor[] descriptors = PropertyUtils.getPropertyDescriptors(propertyClass);
905         if (ArrayUtils.isNotEmpty(descriptors)) {
906             for (PropertyDescriptor descriptor : descriptors) {
907                 if (descriptor.getName().equals(propertyName)) {
908                     p = descriptor;
909                 }
910             }
911         }
912 
913         // cache the property descriptor if we found it.
914         if (p != null) {
915             if (m == null) {
916                 m = new TreeMap<String, PropertyDescriptor>();
917                 cache.put(propertyClassName, m);
918             }
919             m.put(propertyName, p);
920         }
921 
922         return p;
923     }
924 
925     public Set<InactivationBlockingMetadata> getAllInactivationBlockingMetadatas(Class blockedClass) {
926         return ddMapper.getAllInactivationBlockingMetadatas(ddIndex, blockedClass);
927     }
928 
929     /**
930      * This method gathers beans of type BeanOverride and invokes each one's performOverride() method.
931      */
932     // KULRICE-4513
933     public void performBeanOverrides() {
934         Collection<BeanOverride> beanOverrides = ddBeans.getBeansOfType(BeanOverride.class).values();
935 
936         if (beanOverrides.isEmpty()) {
937             LOG.info("DataDictionary.performOverrides(): No beans to override");
938         }
939         for (BeanOverride beanOverride : beanOverrides) {
940 
941             Object bean = ddBeans.getBean(beanOverride.getBeanName());
942             beanOverride.performOverride(bean);
943             LOG.info("DataDictionary.performOverrides(): Performing override on bean: " + bean.toString());
944         }
945     }
946 
947 }