001/**
002 * Copyright 2005-2016 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 */
016package org.kuali.rice.krad.datadictionary;
017
018import org.apache.commons.beanutils.PropertyUtils;
019import org.apache.commons.lang.ArrayUtils;
020import org.apache.commons.lang.StringUtils;
021import org.apache.commons.logging.Log;
022import org.apache.commons.logging.LogFactory;
023import org.kuali.rice.core.api.util.ClassLoaderUtils;
024import org.kuali.rice.krad.bo.PersistableBusinessObjectExtension;
025import org.kuali.rice.krad.datadictionary.exception.AttributeValidationException;
026import org.kuali.rice.krad.datadictionary.exception.CompletionException;
027import org.kuali.rice.krad.datadictionary.parse.StringListConverter;
028import org.kuali.rice.krad.datadictionary.parse.StringMapConverter;
029import org.kuali.rice.krad.service.KRADServiceLocator;
030import org.kuali.rice.krad.service.PersistenceStructureService;
031import org.kuali.rice.krad.uif.UifConstants.ViewType;
032import org.kuali.rice.krad.uif.util.ComponentBeanPostProcessor;
033import org.kuali.rice.krad.uif.util.UifBeanFactoryPostProcessor;
034import org.kuali.rice.krad.uif.view.View;
035import org.kuali.rice.krad.util.ObjectUtils;
036import org.springframework.beans.PropertyValues;
037import org.springframework.beans.factory.config.BeanPostProcessor;
038import org.springframework.beans.factory.support.KualiDefaultListableBeanFactory;
039import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
040import org.springframework.context.expression.StandardBeanExpressionResolver;
041import org.springframework.core.convert.support.GenericConversionService;
042import org.springframework.core.io.DefaultResourceLoader;
043import org.springframework.core.io.Resource;
044
045import java.beans.PropertyDescriptor;
046import java.io.File;
047import java.io.IOException;
048import java.util.ArrayList;
049import java.util.Collection;
050import java.util.HashMap;
051import java.util.List;
052import java.util.Map;
053import java.util.Set;
054import 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 */
061public 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}