001    /**
002     * Copyright 2005-2013 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.rice.krad.datadictionary;
017    
018    import org.apache.commons.beanutils.PropertyUtils;
019    import org.apache.commons.lang.ArrayUtils;
020    import org.apache.commons.lang.StringUtils;
021    import org.apache.commons.logging.Log;
022    import org.apache.commons.logging.LogFactory;
023    import org.kuali.rice.core.api.util.ClassLoaderUtils;
024    import org.kuali.rice.krad.bo.PersistableBusinessObjectExtension;
025    import org.kuali.rice.krad.datadictionary.exception.AttributeValidationException;
026    import org.kuali.rice.krad.datadictionary.exception.CompletionException;
027    import org.kuali.rice.krad.datadictionary.parse.StringListConverter;
028    import org.kuali.rice.krad.datadictionary.parse.StringMapConverter;
029    import org.kuali.rice.krad.service.KRADServiceLocator;
030    import org.kuali.rice.krad.service.PersistenceStructureService;
031    import org.kuali.rice.krad.uif.UifConstants.ViewType;
032    import org.kuali.rice.krad.uif.util.ComponentBeanPostProcessor;
033    import org.kuali.rice.krad.uif.util.UifBeanFactoryPostProcessor;
034    import org.kuali.rice.krad.uif.view.View;
035    import org.kuali.rice.krad.util.ObjectUtils;
036    import org.springframework.beans.PropertyValues;
037    import org.springframework.beans.factory.config.BeanPostProcessor;
038    import org.springframework.beans.factory.support.KualiDefaultListableBeanFactory;
039    import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
040    import org.springframework.context.expression.StandardBeanExpressionResolver;
041    import org.springframework.core.convert.support.GenericConversionService;
042    import org.springframework.core.io.DefaultResourceLoader;
043    import org.springframework.core.io.Resource;
044    
045    import java.beans.PropertyDescriptor;
046    import java.io.File;
047    import java.io.IOException;
048    import java.util.ArrayList;
049    import java.util.Collection;
050    import java.util.HashMap;
051    import java.util.List;
052    import java.util.Map;
053    import java.util.Set;
054    import java.util.TreeMap;
055    
056    /**
057     * Collection of named BusinessObjectEntry objects, each of which contains
058     * information relating to the display, validation, and general maintenance of a
059     * BusinessObject.
060     */
061    public class DataDictionary  {
062    
063            protected KualiDefaultListableBeanFactory ddBeans = new KualiDefaultListableBeanFactory();
064        protected XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(ddBeans);
065    
066            // logger
067            private static final Log LOG = LogFactory.getLog(DataDictionary.class);
068    
069            /**
070             * The encapsulation of DataDictionary indices
071             */
072            protected DataDictionaryIndex ddIndex = new DataDictionaryIndex(ddBeans);
073            
074            // View indices
075            protected UifDictionaryIndex uifIndex = new UifDictionaryIndex(ddBeans);
076    
077            /**
078             * The DataDictionaryMapper
079             * The default mapper simply consults the initialized indices
080             * on workflow document type
081             */
082            protected DataDictionaryMapper ddMapper = new DataDictionaryIndexMapper();
083    
084            protected List<String> configFileLocations = new ArrayList<String>();
085            
086    
087            public List<String> getConfigFileLocations() {
088            return this.configFileLocations;
089        }
090    
091        public void setConfigFileLocations(List<String> configFileLocations) {
092            this.configFileLocations = configFileLocations;
093        }
094        
095        public void addConfigFileLocation( String location ) throws IOException {
096            indexSource( location );
097        }
098    
099        /**
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    }