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.lang.ArrayUtils;
20  import org.apache.commons.lang.StringUtils;
21  import org.apache.commons.logging.Log;
22  import org.apache.commons.logging.LogFactory;
23  import org.kuali.rice.core.api.util.ClassLoaderUtils;
24  import org.kuali.rice.krad.bo.PersistableBusinessObjectExtension;
25  import org.kuali.rice.krad.datadictionary.exception.AttributeValidationException;
26  import org.kuali.rice.krad.datadictionary.exception.CompletionException;
27  import org.kuali.rice.krad.datadictionary.parse.StringListConverter;
28  import org.kuali.rice.krad.datadictionary.parse.StringMapConverter;
29  import org.kuali.rice.krad.service.KRADServiceLocator;
30  import org.kuali.rice.krad.service.PersistenceStructureService;
31  import org.kuali.rice.krad.uif.UifConstants.ViewType;
32  import org.kuali.rice.krad.uif.util.ComponentBeanPostProcessor;
33  import org.kuali.rice.krad.uif.util.UifBeanFactoryPostProcessor;
34  import org.kuali.rice.krad.uif.view.View;
35  import org.kuali.rice.krad.util.ObjectUtils;
36  import org.springframework.beans.PropertyValues;
37  import org.springframework.beans.factory.config.BeanPostProcessor;
38  import org.springframework.beans.factory.support.DefaultListableBeanFactory;
39  import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
40  import org.springframework.context.expression.StandardBeanExpressionResolver;
41  import org.springframework.core.convert.support.GenericConversionService;
42  import org.springframework.core.io.DefaultResourceLoader;
43  import org.springframework.core.io.Resource;
44  
45  import java.beans.PropertyDescriptor;
46  import java.io.File;
47  import java.io.IOException;
48  import java.util.ArrayList;
49  import java.util.Collection;
50  import java.util.HashMap;
51  import java.util.List;
52  import java.util.Map;
53  import java.util.Set;
54  import java.util.TreeMap;
55  
56  /**
57   * Collection of named BusinessObjectEntry objects, each of which contains
58   * information relating to the display, validation, and general maintenance of a
59   * BusinessObject.
60   */
61  public class DataDictionary  {
62  
63  	protected DefaultListableBeanFactory ddBeans = new DefaultListableBeanFactory();
64      protected XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(ddBeans);
65  
66  	// logger
67  	private static final Log LOG = LogFactory.getLog(DataDictionary.class);
68  
69  	/**
70  	 * The encapsulation of DataDictionary indices
71  	 */
72  	protected DataDictionaryIndex ddIndex = new DataDictionaryIndex(ddBeans);
73  	
74  	// View indices
75  	protected UifDictionaryIndex uifIndex = new UifDictionaryIndex(ddBeans);
76  
77  	/**
78  	 * The DataDictionaryMapper
79  	 * The default mapper simply consults the initialized indices
80  	 * on workflow document type
81  	 */
82  	protected DataDictionaryMapper ddMapper = new DataDictionaryIndexMapper();
83  
84  	protected List<String> configFileLocations = new ArrayList<String>();
85  	
86  
87  	public List<String> getConfigFileLocations() {
88          return this.configFileLocations;
89      }
90  
91      public void setConfigFileLocations(List<String> configFileLocations) {
92          this.configFileLocations = configFileLocations;
93      }
94      
95      public void addConfigFileLocation( String location ) throws IOException {
96          indexSource( location );
97      }
98  
99      /**
100      * Sets the DataDictionaryMapper
101      * @param mapper the datadictionary mapper
102      */
103     public void setDataDictionaryMapper(DataDictionaryMapper mapper) {
104     	this.ddMapper = mapper;
105     }
106     
107     private void indexSource(String sourceName) throws IOException {        
108         if (sourceName == null) {
109             throw new DataDictionaryException("Source Name given is null");
110         }
111 
112         if (!sourceName.endsWith(".xml") ) {
113             Resource resource = getFileResource(sourceName);
114             if (resource.exists()) {
115                 indexSource(resource.getFile());
116             } else {
117                 LOG.warn("Could not find " + sourceName);
118                 throw new DataDictionaryException("DD Resource " + sourceName + " not found");
119             }
120         } else {
121             if ( LOG.isDebugEnabled() ) {
122                 LOG.debug("adding sourceName " + sourceName + " ");
123             }
124             Resource resource = getFileResource(sourceName);
125             if (! resource.exists()) {
126                 throw new DataDictionaryException("DD Resource " + sourceName + " not found");  
127             }
128             
129             String indexName = sourceName.substring(sourceName.lastIndexOf("/") + 1, sourceName.indexOf(".xml"));
130             configFileLocations.add( sourceName );
131         }
132     }    
133 
134     protected Resource getFileResource(String sourceName) {
135         DefaultResourceLoader resourceLoader = new DefaultResourceLoader(ClassLoaderUtils.getDefaultClassLoader());
136         return resourceLoader.getResource(sourceName);
137     }
138 
139     private void indexSource(File dir) {
140         for (File file : dir.listFiles()) {
141             if (file.isDirectory()) {
142                 indexSource(file);
143             } else if (file.getName().endsWith(".xml") ) {
144                 configFileLocations.add( "file:" + file.getAbsolutePath());
145             } else {
146                 if ( LOG.isDebugEnabled() ) {
147                     LOG.debug("Skipping non xml file " + file.getAbsolutePath() + " in DD load");
148                 }
149             }
150         }
151     }
152     
153     public void parseDataDictionaryConfigurationFiles( boolean allowConcurrentValidation ) {
154 		// configure the bean factory, setup component decorator post processor
155 		// and allow Spring EL
156         try {
157             BeanPostProcessor idPostProcessor = ComponentBeanPostProcessor.class.newInstance();
158             ddBeans.addBeanPostProcessor(idPostProcessor);
159             ddBeans.setBeanExpressionResolver(new StandardBeanExpressionResolver());
160 
161             GenericConversionService conversionService = new GenericConversionService();
162             conversionService.addConverter(new StringMapConverter());
163             conversionService.addConverter(new StringListConverter());
164             ddBeans.setConversionService(conversionService);
165         } catch (Exception e1) {
166             LOG.error("Cannot create component decorator post processor: " + e1.getMessage(), e1);
167             throw new RuntimeException("Cannot create component decorator post processor: " + e1.getMessage(), e1);
168         }
169 
170         // expand configuration locations into files
171         LOG.info("Starting DD XML File Load");
172 
173         String[] configFileLocationsArray = new String[configFileLocations.size()];
174         configFileLocationsArray = configFileLocations.toArray(configFileLocationsArray);
175         configFileLocations.clear(); // empty the list out so other items can be added
176         try {
177             xmlReader.loadBeanDefinitions(configFileLocationsArray);
178         } catch (Exception e) {
179             LOG.error("Error loading bean definitions", e);
180             throw new DataDictionaryException("Error loading bean definitions: " + e.getLocalizedMessage());
181         }
182         LOG.info("Completed DD XML File Load");
183 
184         UifBeanFactoryPostProcessor factoryPostProcessor = new UifBeanFactoryPostProcessor();
185         factoryPostProcessor.postProcessBeanFactory(ddBeans);
186 
187         // indexing
188         if (allowConcurrentValidation) {
189             Thread t = new Thread(ddIndex);
190             t.start();
191 
192             Thread t2 = new Thread(uifIndex);
193             t2.start();
194         } else {
195             ddIndex.run();
196             uifIndex.run();
197         }
198     }
199 
200 	static boolean validateEBOs = true;
201     
202     public void validateDD( boolean validateEbos ) {
203     	DataDictionary.validateEBOs = validateEbos;
204     	Map<String,DataObjectEntry> doBeans = ddBeans.getBeansOfType(DataObjectEntry.class);
205         for ( DataObjectEntry entry : doBeans.values() ) {
206             entry.completeValidation();
207         }
208         Map<String,DocumentEntry> docBeans = ddBeans.getBeansOfType(DocumentEntry.class);
209         for ( DocumentEntry entry : docBeans.values() ) {
210             entry.completeValidation();
211         }
212     }
213     
214     public void validateDD() {
215     	validateDD(true);
216     }
217 
218 	/**
219 	 * @param className
220 	 * @return BusinessObjectEntry for the named class, or null if none exists
221 	 */
222     @Deprecated
223 	public BusinessObjectEntry getBusinessObjectEntry(String className ) {
224 		return ddMapper.getBusinessObjectEntry(ddIndex, className);
225 	}
226 
227 	/**
228      * @param className
229      * @return BusinessObjectEntry for the named class, or null if none exists
230      */
231     public DataObjectEntry getDataObjectEntry(String className ) {
232         return ddMapper.getDataObjectEntry(ddIndex, className);
233     }
234 
235 	/**
236 	 * This method gets the business object entry for a concrete class
237 	 * 
238 	 * @param className
239 	 * @return
240 	 */
241 	public BusinessObjectEntry getBusinessObjectEntryForConcreteClass(String className){
242 		return ddMapper.getBusinessObjectEntryForConcreteClass(ddIndex, className);
243 	}
244 	
245 	/**
246 	 * @return List of businessObject classnames
247 	 */
248 	public List<String> getBusinessObjectClassNames() {
249 		return ddMapper.getBusinessObjectClassNames(ddIndex);
250 	}
251 
252 	/**
253 	 * @return Map of (classname, BusinessObjectEntry) pairs
254 	 */
255 	public Map<String, BusinessObjectEntry> getBusinessObjectEntries() {
256 		return ddMapper.getBusinessObjectEntries(ddIndex);
257 	}
258 
259 	/**
260 	 * @param className
261 	 * @return DataDictionaryEntryBase for the named class, or null if none
262 	 *         exists
263 	 */
264 	public DataDictionaryEntry getDictionaryObjectEntry(String className) {
265 		return ddMapper.getDictionaryObjectEntry(ddIndex, className);
266 	}
267 
268 	/**
269 	 * Returns the KNS document entry for the given lookup key.  The documentTypeDDKey is interpreted
270 	 * successively in the following ways until a mapping is found (or none if found):
271 	 * <ol>
272 	 * <li>KEW/workflow document type</li>
273 	 * <li>business object class name</li>
274 	 * <li>maintainable class name</li>
275 	 * </ol>
276 	 * This mapping is compiled when DataDictionary files are parsed on startup (or demand).  Currently this
277 	 * means the mapping is static, and one-to-one (one KNS document maps directly to one and only
278 	 * one key).
279 	 * 
280 	 * @param documentTypeDDKey the KEW/workflow document type name
281 	 * @return the KNS DocumentEntry if it exists
282 	 */
283 	public DocumentEntry getDocumentEntry(String documentTypeDDKey ) {
284 		return ddMapper.getDocumentEntry(ddIndex, documentTypeDDKey);
285 	}
286 
287 	/**
288 	 * Note: only MaintenanceDocuments are indexed by businessObject Class
289 	 * 
290 	 * This is a special case that is referenced in one location. Do we need
291 	 * another map for this stuff??
292 	 * 
293 	 * @param businessObjectClass
294 	 * @return DocumentEntry associated with the given Class, or null if there
295 	 *         is none
296 	 */
297 	public MaintenanceDocumentEntry getMaintenanceDocumentEntryForBusinessObjectClass(Class<?> businessObjectClass) {
298 		return ddMapper.getMaintenanceDocumentEntryForBusinessObjectClass(ddIndex, businessObjectClass);
299 	}
300 
301 	public Map<String, DocumentEntry> getDocumentEntries() {
302 		return ddMapper.getDocumentEntries(ddIndex);
303 	}
304 	
305 	/**
306 	 * Returns the View entry identified by the given id
307 	 * 
308 	 * @param viewId - unique id for view
309 	 * @return View instance associated with the id
310 	 */
311 	public View getViewById(String viewId) {
312 		return ddMapper.getViewById(uifIndex, viewId);
313 	}
314 	
315 	/**
316 	 * Returns View instance identified by the view type name and index
317 	 * 
318 	 * @param viewTypeName
319 	 *            - type name for the view
320 	 * @param indexKey
321 	 *            - Map of index key parameters, these are the parameters the
322 	 *            indexer used to index the view initially and needs to identify
323 	 *            an unique view instance
324 	 * @return View instance that matches the given index
325 	 */
326 	public View getViewByTypeIndex(ViewType viewTypeName, Map<String, String> indexKey) {
327 		return ddMapper.getViewByTypeIndex(uifIndex, viewTypeName, indexKey);
328 	}
329 
330     /**
331      * Indicates whether a <code>View</code> exists for the given view type and index information
332      *
333      * @param viewTypeName - type name for the view
334      * @param indexKey - Map of index key parameters, these are the parameters the
335      * indexer used to index the view initially and needs to identify
336      * an unique view instance
337      * @return boolean true if view exists, false if not
338      */
339     public boolean viewByTypeExist(ViewType viewTypeName, Map<String, String> indexKey) {
340         return ddMapper.viewByTypeExist(uifIndex, viewTypeName, indexKey);
341     }
342 	
343 	/**
344 	 * Gets all <code>View</code> prototypes configured for the given view type
345 	 * name
346 	 * 
347 	 * @param viewTypeName
348 	 *            - view type name to retrieve
349 	 * @return List<View> view prototypes with the given type name, or empty
350 	 *         list
351 	 */
352 	public List<View> getViewsForType(ViewType viewTypeName) {
353 		return ddMapper.getViewsForType(uifIndex, viewTypeName);
354 	}
355 
356     /**
357      * Returns an object from the dictionary by its spring bean name
358      *
359      * @param beanName - id or name for the bean definition
360      * @return Object object instance created or the singleton being maintained
361      */
362     public Object getDictionaryObject(String beanName) {
363         return ddBeans.getBean(beanName);
364     }
365 
366     /**
367      * Indicates whether the data dictionary contains a bean with the given id
368      *
369      * @param id - id of the bean to check for
370      * @return boolean true if dictionary contains bean, false otherwise
371      */
372     public boolean containsDictionaryObject(String id) {
373         return ddBeans.containsBean(id);
374     }
375 
376     /**
377      * Retrieves the configured property values for the view bean definition associated with the given id
378      *
379      * <p>
380      * Since constructing the View object can be expensive, when metadata only is needed this method can be used
381      * to retrieve the configured property values. Note this looks at the merged bean definition
382      * </p>
383      *
384      * @param viewId - id for the view to retrieve
385      * @return PropertyValues configured on the view bean definition, or null if view is not found
386      */
387     public PropertyValues getViewPropertiesById(String viewId) {
388         return ddMapper.getViewPropertiesById(uifIndex, viewId);
389     }
390 
391     /**
392      * Retrieves the configured property values for the view bean definition associated with the given type and
393      * index
394      *
395      * <p>
396      * Since constructing the View object can be expensive, when metadata only is needed this method can be used
397      * to retrieve the configured property values. Note this looks at the merged bean definition
398      * </p>
399      *
400      * @param viewTypeName - type name for the view
401      * @param indexKey - Map of index key parameters, these are the parameters the indexer used to index
402      * the view initially and needs to identify an unique view instance
403      * @return PropertyValues configured on the view bean definition, or null if view is not found
404      */
405     public PropertyValues getViewPropertiesByType(ViewType viewTypeName, Map<String, String> indexKey) {
406         return ddMapper.getViewPropertiesByType(uifIndex, viewTypeName, indexKey);
407     }
408 
409     /**
410      * @param targetClass
411      * @param propertyName
412      * @return true if the given propertyName names a property of the given class
413      * @throws CompletionException if there is a problem accessing the named property on the given class
414      */
415     public static boolean isPropertyOf(Class targetClass, String propertyName) {
416         if (targetClass == null) {
417             throw new IllegalArgumentException("invalid (null) targetClass");
418         }
419         if (StringUtils.isBlank(propertyName)) {
420             throw new IllegalArgumentException("invalid (blank) propertyName");
421         }
422 
423         PropertyDescriptor propertyDescriptor = buildReadDescriptor(targetClass, propertyName);
424 
425         boolean isPropertyOf = (propertyDescriptor != null);
426         return isPropertyOf;
427     }
428 
429     /**
430      * @param targetClass
431      * @param propertyName
432      * @return true if the given propertyName names a Collection property of the given class
433      * @throws CompletionException if there is a problem accessing the named property on the given class
434      */
435     public static boolean isCollectionPropertyOf(Class targetClass, String propertyName) {
436         boolean isCollectionPropertyOf = false;
437 
438         PropertyDescriptor propertyDescriptor = buildReadDescriptor(targetClass, propertyName);
439         if (propertyDescriptor != null) {
440             Class clazz = propertyDescriptor.getPropertyType();
441 
442             if ((clazz != null) && Collection.class.isAssignableFrom(clazz)) {
443                 isCollectionPropertyOf = true;
444             }
445         }
446 
447         return isCollectionPropertyOf;
448     }
449 
450     public static PersistenceStructureService persistenceStructureService;
451     
452     /**
453      * @return the persistenceStructureService
454      */
455     public static PersistenceStructureService getPersistenceStructureService() {
456         if ( persistenceStructureService == null ) {
457             persistenceStructureService = KRADServiceLocator.getPersistenceStructureService();
458         }
459         return persistenceStructureService;
460     }
461     
462     /**
463      * This method determines the Class of the attributeName passed in. Null will be returned if the member is not available, or if
464      * a reflection exception is thrown.
465      * 
466      * @param boClass - Class that the attributeName property exists in.
467      * @param attributeName - Name of the attribute you want a class for.
468      * @return The Class of the attributeName, if the attribute exists on the rootClass. Null otherwise.
469      */
470     public static Class getAttributeClass(Class boClass, String attributeName) {
471 
472         // fail loudly if the attributeName isnt a member of rootClass
473         if (!isPropertyOf(boClass, attributeName)) {
474             throw new AttributeValidationException("unable to find attribute '" + attributeName + "' in rootClass '" + boClass.getName() + "'");
475         }
476 
477     	//Implementing Externalizable Business Object Services...
478         //The boClass can be an interface, hence handling this separately, 
479         //since the original method was throwing exception if the class could not be instantiated.
480         if(boClass.isInterface())
481         	return getAttributeClassWhenBOIsInterface(boClass, attributeName);
482         else
483         	return getAttributeClassWhenBOIsClass(boClass, attributeName);        	
484 
485     }
486 
487     /**
488      * 
489      * This method gets the property type of the given attributeName when the bo class is a concrete class
490      * 
491      * @param boClass
492      * @param attributeName
493      * @return
494      */
495     private static Class getAttributeClassWhenBOIsClass(Class boClass, String attributeName){
496     	Object boInstance;
497         try {
498             boInstance = boClass.newInstance();
499         } catch (Exception e) {
500         	throw new RuntimeException("Unable to instantiate Data Object: " + boClass, e);
501         }
502 
503         // attempt to retrieve the class of the property
504         try {
505             return ObjectUtils.getPropertyType(boInstance, attributeName, getPersistenceStructureService());
506         } catch (Exception e) {
507             throw new RuntimeException("Unable to determine property type for: " + boClass.getName() + "." + attributeName, e);
508         }
509     }
510 
511     /**
512      * 
513      * This method gets the property type of the given attributeName when the bo class is an interface
514      * This method will also work if the bo class is not an interface, 
515      * but that case requires special handling, hence a separate method getAttributeClassWhenBOIsClass 
516      * 
517      * @param boClass
518      * @param attributeName
519      * @return
520      */
521     private static Class getAttributeClassWhenBOIsInterface(Class boClass, String attributeName){
522         if (boClass == null) {
523             throw new IllegalArgumentException("invalid (null) boClass");
524         }
525         if (StringUtils.isBlank(attributeName)) {
526             throw new IllegalArgumentException("invalid (blank) attributeName");
527         }
528 
529         PropertyDescriptor propertyDescriptor = null;
530 
531         String[] intermediateProperties = attributeName.split("\\.");
532         int lastLevel = intermediateProperties.length - 1;
533         Class currentClass = boClass;
534 
535         for (int i = 0; i <= lastLevel; ++i) {
536 
537             String currentPropertyName = intermediateProperties[i];
538             propertyDescriptor = buildSimpleReadDescriptor(currentClass, currentPropertyName);
539 
540             if (propertyDescriptor != null) {
541 
542                 Class propertyType = propertyDescriptor.getPropertyType();
543                 if ( propertyType.equals( PersistableBusinessObjectExtension.class ) ) {
544                     propertyType = getPersistenceStructureService().getBusinessObjectAttributeClass( currentClass, currentPropertyName );                    
545                 }
546                 if (Collection.class.isAssignableFrom(propertyType)) {
547                 	// TODO: determine property type using generics type definition
548                 	throw new AttributeValidationException("Can't determine the Class of Collection elements because when the business object is an (possibly ExternalizableBusinessObject) interface.");
549                 }
550                 else {
551                     currentClass = propertyType;
552                 }
553             }
554             else {
555             	throw new AttributeValidationException("Can't find getter method of " + boClass.getName() + " for property " + attributeName);
556             }
557         }
558         return currentClass;
559     }
560     
561     /**
562      * This method determines the Class of the elements in the collectionName passed in.
563      * 
564      * @param boClass Class that the collectionName collection exists in.
565      * @param collectionName the name of the collection you want the element class for
566      * @return
567      */
568     public static Class getCollectionElementClass(Class boClass, String collectionName) {
569         if (boClass == null) {
570             throw new IllegalArgumentException("invalid (null) boClass");
571         }
572         if (StringUtils.isBlank(collectionName)) {
573             throw new IllegalArgumentException("invalid (blank) collectionName");
574         }
575 
576         PropertyDescriptor propertyDescriptor = null;
577 
578         String[] intermediateProperties = collectionName.split("\\.");
579         Class currentClass = boClass;
580 
581         for (int i = 0; i <intermediateProperties.length; ++i) {
582 
583             String currentPropertyName = intermediateProperties[i];
584             propertyDescriptor = buildSimpleReadDescriptor(currentClass, currentPropertyName);
585 
586 
587                 if (propertyDescriptor != null) {
588 
589                     Class type = propertyDescriptor.getPropertyType();
590                     if (Collection.class.isAssignableFrom(type)) {
591 
592                         if (getPersistenceStructureService().isPersistable(currentClass)) {
593 
594                             Map<String, Class> collectionClasses = new HashMap<String, Class>();
595                             collectionClasses = getPersistenceStructureService().listCollectionObjectTypes(currentClass);
596                             currentClass = collectionClasses.get(currentPropertyName);
597 
598                         }
599                         else {
600                             throw new RuntimeException("Can't determine the Class of Collection elements because persistenceStructureService.isPersistable(" + currentClass.getName() + ") returns false.");
601                         }
602 
603                     }
604                     else {
605 
606                         currentClass = propertyDescriptor.getPropertyType();
607 
608                     }
609                 }
610             }
611 
612         return currentClass;
613     }
614 
615     static private Map<String, Map<String, PropertyDescriptor>> cache = new TreeMap<String, Map<String, PropertyDescriptor>>();
616 
617     /**
618      * @param propertyClass
619      * @param propertyName
620      * @return PropertyDescriptor for the getter for the named property of the given class, if one exists.
621      */
622     public static PropertyDescriptor buildReadDescriptor(Class propertyClass, String propertyName) {
623         if (propertyClass == null) {
624             throw new IllegalArgumentException("invalid (null) propertyClass");
625         }
626         if (StringUtils.isBlank(propertyName)) {
627             throw new IllegalArgumentException("invalid (blank) propertyName");
628         }
629 
630         PropertyDescriptor propertyDescriptor = null;
631 
632         String[] intermediateProperties = propertyName.split("\\.");
633         int lastLevel = intermediateProperties.length - 1;
634         Class currentClass = propertyClass;
635 
636         for (int i = 0; i <= lastLevel; ++i) {
637 
638             String currentPropertyName = intermediateProperties[i];
639             propertyDescriptor = buildSimpleReadDescriptor(currentClass, currentPropertyName);
640 
641             if (i < lastLevel) {
642 
643                 if (propertyDescriptor != null) {
644 
645                     Class propertyType = propertyDescriptor.getPropertyType();
646                     if ( propertyType.equals( PersistableBusinessObjectExtension.class ) ) {
647                         propertyType = getPersistenceStructureService().getBusinessObjectAttributeClass( currentClass, currentPropertyName );                    
648                     }
649                     if (Collection.class.isAssignableFrom(propertyType)) {
650 
651                         if (getPersistenceStructureService().isPersistable(currentClass)) {
652 
653                             Map<String, Class> collectionClasses = new HashMap<String, Class>();
654                             collectionClasses = getPersistenceStructureService().listCollectionObjectTypes(currentClass);
655                             currentClass = collectionClasses.get(currentPropertyName);
656 
657                         }
658                         else {
659 
660                             throw new RuntimeException("Can't determine the Class of Collection elements because persistenceStructureService.isPersistable(" + currentClass.getName() + ") returns false.");
661 
662                         }
663 
664                     }
665                     else {
666 
667                         currentClass = propertyType;
668 
669                     }
670 
671                 }
672 
673             }
674 
675         }
676 
677         return propertyDescriptor;
678     }
679 
680     /**
681      * @param propertyClass
682      * @param propertyName
683      * @return PropertyDescriptor for the getter for the named property of the given class, if one exists.
684      */
685     public static PropertyDescriptor buildSimpleReadDescriptor(Class propertyClass, String propertyName) {
686         if (propertyClass == null) {
687             throw new IllegalArgumentException("invalid (null) propertyClass");
688         }
689         if (StringUtils.isBlank(propertyName)) {
690             throw new IllegalArgumentException("invalid (blank) propertyName");
691         }
692 
693         PropertyDescriptor p = null;
694 
695         // check to see if we've cached this descriptor already. if yes, return true.
696         String propertyClassName = propertyClass.getName();
697         Map<String, PropertyDescriptor> m = cache.get(propertyClassName);
698         if (null != m) {
699             p = m.get(propertyName);
700             if (null != p) {
701                 return p;
702             }
703         }
704 
705         // Use PropertyUtils.getPropertyDescriptors instead of manually constructing PropertyDescriptor because of
706         // issues with introspection and generic/co-variant return types
707         // See https://issues.apache.org/jira/browse/BEANUTILS-340 for more details
708 
709         PropertyDescriptor[] descriptors = PropertyUtils.getPropertyDescriptors(propertyClass);
710         if (ArrayUtils.isNotEmpty(descriptors)) {
711             for (PropertyDescriptor descriptor : descriptors) {
712                 if (descriptor.getName().equals(propertyName)) {
713                     p = descriptor;
714                 }
715             }
716         }
717 
718         // cache the property descriptor if we found it.
719         if (p != null) {
720             if (m == null) {
721                 m = new TreeMap<String, PropertyDescriptor>();
722                 cache.put(propertyClassName, m);
723             }
724             m.put(propertyName, p);
725         }
726 
727         return p;
728     }
729 
730     public Set<InactivationBlockingMetadata> getAllInactivationBlockingMetadatas(Class blockedClass) {
731     	return ddMapper.getAllInactivationBlockingMetadatas(ddIndex, blockedClass);
732     }
733     
734     /**
735      * This method gathers beans of type BeanOverride and invokes each one's performOverride() method.
736      */
737     // KULRICE-4513
738     public void performBeanOverrides()
739     {
740     	Collection<BeanOverride> beanOverrides = ddBeans.getBeansOfType(BeanOverride.class).values();
741     	
742     	if (beanOverrides.isEmpty()){
743     		LOG.info("DataDictionary.performOverrides(): No beans to override");
744     	}
745 		for (BeanOverride beanOverride : beanOverrides) {
746 			
747 			Object bean = ddBeans.getBean(beanOverride.getBeanName());
748 			beanOverride.performOverride(bean);
749 			LOG.info("DataDictionary.performOverrides(): Performing override on bean: " + bean.toString());
750 		}
751     }
752 
753 }