View Javadoc

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