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