/** * Copyright 2005-2014 The Kuali Foundation * * Licensed under the Educational Community License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.opensource.org/licenses/ecl2.php * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.kuali.rice.krad.datadictionary; import org.apache.commons.beanutils.PropertyUtils; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.kuali.rice.core.api.util.ClassLoaderUtils; import org.kuali.rice.krad.bo.PersistableBusinessObjectExtension; import org.kuali.rice.krad.datadictionary.exception.AttributeValidationException; import org.kuali.rice.krad.datadictionary.exception.CompletionException; import org.kuali.rice.krad.datadictionary.parse.StringListConverter; import org.kuali.rice.krad.datadictionary.parse.StringMapConverter; import org.kuali.rice.krad.service.KRADServiceLocator; import org.kuali.rice.krad.service.PersistenceStructureService; import org.kuali.rice.krad.uif.UifConstants.ViewType; import org.kuali.rice.krad.uif.util.ComponentBeanPostProcessor; import org.kuali.rice.krad.uif.util.UifBeanFactoryPostProcessor; import org.kuali.rice.krad.uif.view.View; import org.kuali.rice.krad.util.ObjectUtils; import org.springframework.beans.PropertyValues; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.support.KualiDefaultListableBeanFactory; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.context.ApplicationContext; import org.springframework.context.expression.StandardBeanExpressionResolver; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.support.ResourcePatternUtils; import org.springframework.util.ResourceUtils; import java.beans.PropertyDescriptor; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Collection of named BusinessObjectEntry objects, each of which contains * information relating to the display, validation, and general maintenance of a * BusinessObject. * * THIS OVERRIDE OF THE RICE DATA DICTIONARY IS A TOTAL BAND-AID. * It allows us to pass in the Spring ApplicationContext to retrieve resources, which means we can use file globs * to pull in those resources. Hopefully, as KFS starts pulling in Rice client functionality, the Rice DataDictionary * will be improved to pull multiple files in. */ public class DataDictionary { protected KualiDefaultListableBeanFactory ddBeans = new KualiDefaultListableBeanFactory(); protected XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(ddBeans); // logger private static final Log LOG = LogFactory.getLog(DataDictionary.class); /** * The encapsulation of DataDictionary indices */ protected DataDictionaryIndex ddIndex = new DataDictionaryIndex(ddBeans); // View indices protected UifDictionaryIndex uifIndex = new UifDictionaryIndex(ddBeans); /** * The DataDictionaryMapper * The default mapper simply consults the initialized indices * on workflow document type */ protected DataDictionaryMapper ddMapper = new DataDictionaryIndexMapper(); protected List<String> configFileLocations = new ArrayList<String>(); protected static Pattern resourceJarUrlPattern = Pattern.compile("^.*?\\.jar!(.+)$"); public List<String> getConfigFileLocations() { return this.configFileLocations; } public void setConfigFileLocations(List<String> configFileLocations) { this.configFileLocations = configFileLocations; } public void addConfigFileLocation( String location ) throws IOException { indexSource( location ); } /** * ApplicationContext aware version of method */ public void addConfigFileLocation( String location, ApplicationContext applicationContext ) throws IOException { indexSource( location, applicationContext ); } /** * Sets the DataDictionaryMapper * @param mapper the datadictionary mapper */ public void setDataDictionaryMapper(DataDictionaryMapper mapper) { this.ddMapper = mapper; } private void indexSource(String sourceName) throws IOException { if (sourceName == null) { throw new DataDictionaryException("Source Name given is null"); } if (!sourceName.endsWith(".xml") ) { Resource resource = getFileResource(sourceName); if (resource.exists()) { indexSource(resource.getFile()); } else { LOG.warn("Could not find " + sourceName); throw new DataDictionaryException("DD Resource " + sourceName + " not found"); } } else { if ( LOG.isDebugEnabled() ) { LOG.debug("adding sourceName " + sourceName + " "); } Resource resource = getFileResource(sourceName); if (! resource.exists()) { throw new DataDictionaryException("DD Resource " + sourceName + " not found"); } String indexName = sourceName.substring(sourceName.lastIndexOf("/") + 1, sourceName.indexOf(".xml")); configFileLocations.add( sourceName ); } } /** * ApplicationContext aware version of method */ private void indexSource(String sourceName, ApplicationContext applicationContext) throws IOException { if (sourceName == null) { throw new DataDictionaryException("Source Name given is null"); } if (sourceName.endsWith(".xml")) { final Resource[] resources = ResourcePatternUtils.getResourcePatternResolver(applicationContext).getResources(sourceName); for (Resource resource: resources) { if (resource.exists()) { final String resourcePath = parseResourcePathFromUrl(resource); if (!StringUtils.isBlank(resourcePath)) { configFileLocations.add(resourcePath); } } else { LOG.warn("Could not find " + sourceName); throw new DataDictionaryException("DD Resource " + sourceName + " not found"); } } } else { if ( LOG.isDebugEnabled() ) { LOG.debug("adding sourceName " + sourceName + " "); } Resource resource = getFileResource(sourceName, applicationContext); if (! resource.exists()) { throw new DataDictionaryException("DD Resource " + sourceName + " not found"); } String indexName = sourceName.substring(sourceName.lastIndexOf("/") + 1, sourceName.indexOf(".xml")); configFileLocations.add( sourceName ); } } protected Resource getFileResource(String sourceName) { DefaultResourceLoader resourceLoader = new DefaultResourceLoader(ClassLoaderUtils.getDefaultClassLoader()); return resourceLoader.getResource(sourceName); } /** * Parses the path name from a resource's description * @param resource a resource which hides a path from us * @return the path name if we could parse it out */ protected String parseResourcePathFromUrl(Resource resource) throws IOException { final URL resourceUrl = resource.getURL(); if (ResourceUtils.isJarURL(resourceUrl)) { final Matcher resourceUrlPathMatcher = resourceJarUrlPattern.matcher(resourceUrl.getPath()); if (resourceUrlPathMatcher.matches() && !StringUtils.isBlank(resourceUrlPathMatcher.group(1))) { return "classpath:" + resourceUrlPathMatcher.group(1); } } else if (ResourceUtils.URL_PROTOCOL_FILE.equals(resourceUrl.getProtocol()) && resource.exists()) { return "file:" + resourceUrl.getFile(); } return null; } /** * ApplicationContext aware version of method */ protected Resource getFileResource(String sourceName, ApplicationContext applicationContext) { return applicationContext.getResource(sourceName); } private void indexSource(File dir) { for (File file : dir.listFiles()) { if (file.isDirectory()) { indexSource(file); } else if (file.getName().endsWith(".xml") ) { configFileLocations.add( "file:" + file.getAbsolutePath()); } else { if ( LOG.isDebugEnabled() ) { LOG.debug("Skipping non xml file " + file.getAbsolutePath() + " in DD load"); } } } } public void parseDataDictionaryConfigurationFiles( boolean allowConcurrentValidation ) { // configure the bean factory, setup component decorator post processor // and allow Spring EL try { BeanPostProcessor idPostProcessor = ComponentBeanPostProcessor.class.newInstance(); ddBeans.addBeanPostProcessor(idPostProcessor); ddBeans.setBeanExpressionResolver(new StandardBeanExpressionResolver()); GenericConversionService conversionService = new GenericConversionService(); conversionService.addConverter(new StringMapConverter()); conversionService.addConverter(new StringListConverter()); ddBeans.setConversionService(conversionService); } catch (Exception e1) { LOG.error("Cannot create component decorator post processor: " + e1.getMessage(), e1); throw new RuntimeException("Cannot create component decorator post processor: " + e1.getMessage(), e1); } // expand configuration locations into files LOG.info("Starting DD XML File Load"); String[] configFileLocationsArray = new String[configFileLocations.size()]; configFileLocationsArray = configFileLocations.toArray(configFileLocationsArray); configFileLocations.clear(); // empty the list out so other items can be added try { xmlReader.loadBeanDefinitions(configFileLocationsArray); } catch (Exception e) { LOG.error("Error loading bean definitions", e); throw new DataDictionaryException("Error loading bean definitions: " + e.getLocalizedMessage()); } LOG.info("Completed DD XML File Load"); UifBeanFactoryPostProcessor factoryPostProcessor = new UifBeanFactoryPostProcessor(); factoryPostProcessor.postProcessBeanFactory(ddBeans); // indexing if (allowConcurrentValidation) { Thread t = new Thread(ddIndex); t.start(); Thread t2 = new Thread(uifIndex); t2.start(); } else { ddIndex.run(); uifIndex.run(); } } static boolean validateEBOs = true; public void validateDD( boolean validateEbos ) { DataDictionary.validateEBOs = validateEbos; Map<String,DataObjectEntry> doBeans = ddBeans.getBeansOfType(DataObjectEntry.class); for ( DataObjectEntry entry : doBeans.values() ) { entry.completeValidation(); } Map<String,DocumentEntry> docBeans = ddBeans.getBeansOfType(DocumentEntry.class); for ( DocumentEntry entry : docBeans.values() ) { entry.completeValidation(); } } public void validateDD() { validateDD(true); } /** * @param className * @return BusinessObjectEntry for the named class, or null if none exists */ @Deprecated public BusinessObjectEntry getBusinessObjectEntry(String className ) { return ddMapper.getBusinessObjectEntry(ddIndex, className); } /** * @param className * @return BusinessObjectEntry for the named class, or null if none exists */ public DataObjectEntry getDataObjectEntry(String className ) { return ddMapper.getDataObjectEntry(ddIndex, className); } /** * This method gets the business object entry for a concrete class * * @param className * @return */ public BusinessObjectEntry getBusinessObjectEntryForConcreteClass(String className){ return ddMapper.getBusinessObjectEntryForConcreteClass(ddIndex, className); } /** * @return List of businessObject classnames */ public List<String> getBusinessObjectClassNames() { return ddMapper.getBusinessObjectClassNames(ddIndex); } /** * @return Map of (classname, BusinessObjectEntry) pairs */ public Map<String, BusinessObjectEntry> getBusinessObjectEntries() { return ddMapper.getBusinessObjectEntries(ddIndex); } /** * @param className * @return DataDictionaryEntryBase for the named class, or null if none * exists */ public DataDictionaryEntry getDictionaryObjectEntry(String className) { return ddMapper.getDictionaryObjectEntry(ddIndex, className); } /** * Returns the KNS document entry for the given lookup key. The documentTypeDDKey is interpreted * successively in the following ways until a mapping is found (or none if found): * <ol> * <li>KEW/workflow document type</li> * <li>business object class name</li> * <li>maintainable class name</li> * </ol> * This mapping is compiled when DataDictionary files are parsed on startup (or demand). Currently this * means the mapping is static, and one-to-one (one KNS document maps directly to one and only * one key). * * @param documentTypeDDKey the KEW/workflow document type name * @return the KNS DocumentEntry if it exists */ public DocumentEntry getDocumentEntry(String documentTypeDDKey ) { return ddMapper.getDocumentEntry(ddIndex, documentTypeDDKey); } /** * Note: only MaintenanceDocuments are indexed by businessObject Class * * This is a special case that is referenced in one location. Do we need * another map for this stuff?? * * @param businessObjectClass * @return DocumentEntry associated with the given Class, or null if there * is none */ public MaintenanceDocumentEntry getMaintenanceDocumentEntryForBusinessObjectClass(Class<?> businessObjectClass) { return ddMapper.getMaintenanceDocumentEntryForBusinessObjectClass(ddIndex, businessObjectClass); } public Map<String, DocumentEntry> getDocumentEntries() { return ddMapper.getDocumentEntries(ddIndex); } /** * Returns the View entry identified by the given id * * @param viewId - unique id for view * @return View instance associated with the id */ public View getViewById(String viewId) { return ddMapper.getViewById(uifIndex, viewId); } /** * Returns View instance identified by the view type name and index * * @param viewTypeName * - type name for the view * @param indexKey * - Map of index key parameters, these are the parameters the * indexer used to index the view initially and needs to identify * an unique view instance * @return View instance that matches the given index */ public View getViewByTypeIndex(ViewType viewTypeName, Map<String, String> indexKey) { return ddMapper.getViewByTypeIndex(uifIndex, viewTypeName, indexKey); } /** * Indicates whether a <code>View</code> exists for the given view type and index information * * @param viewTypeName - type name for the view * @param indexKey - Map of index key parameters, these are the parameters the * indexer used to index the view initially and needs to identify * an unique view instance * @return boolean true if view exists, false if not */ public boolean viewByTypeExist(ViewType viewTypeName, Map<String, String> indexKey) { return ddMapper.viewByTypeExist(uifIndex, viewTypeName, indexKey); } /** * Gets all <code>View</code> prototypes configured for the given view type * name * * @param viewTypeName * - view type name to retrieve * @return List<View> view prototypes with the given type name, or empty * list */ public List<View> getViewsForType(ViewType viewTypeName) { return ddMapper.getViewsForType(uifIndex, viewTypeName); } /** * Returns an object from the dictionary by its spring bean name * * @param beanName - id or name for the bean definition * @return Object object instance created or the singleton being maintained */ public Object getDictionaryObject(String beanName) { return ddBeans.getBean(beanName); } /** * Indicates whether the data dictionary contains a bean with the given id * * @param id - id of the bean to check for * @return boolean true if dictionary contains bean, false otherwise */ public boolean containsDictionaryObject(String id) { return ddBeans.containsBean(id); } /** * Retrieves the configured property values for the view bean definition associated with the given id * * <p> * Since constructing the View object can be expensive, when metadata only is needed this method can be used * to retrieve the configured property values. Note this looks at the merged bean definition * </p> * * @param viewId - id for the view to retrieve * @return PropertyValues configured on the view bean definition, or null if view is not found */ public PropertyValues getViewPropertiesById(String viewId) { return ddMapper.getViewPropertiesById(uifIndex, viewId); } /** * Retrieves the configured property values for the view bean definition associated with the given type and * index * * <p> * Since constructing the View object can be expensive, when metadata only is needed this method can be used * to retrieve the configured property values. Note this looks at the merged bean definition * </p> * * @param viewTypeName - type name for the view * @param indexKey - Map of index key parameters, these are the parameters the indexer used to index * the view initially and needs to identify an unique view instance * @return PropertyValues configured on the view bean definition, or null if view is not found */ public PropertyValues getViewPropertiesByType(ViewType viewTypeName, Map<String, String> indexKey) { return ddMapper.getViewPropertiesByType(uifIndex, viewTypeName, indexKey); } /** * @param targetClass * @param propertyName * @return true if the given propertyName names a property of the given class * @throws CompletionException if there is a problem accessing the named property on the given class */ public static boolean isPropertyOf(Class targetClass, String propertyName) { if (targetClass == null) { throw new IllegalArgumentException("invalid (null) targetClass"); } if (StringUtils.isBlank(propertyName)) { throw new IllegalArgumentException("invalid (blank) propertyName"); } PropertyDescriptor propertyDescriptor = buildReadDescriptor(targetClass, propertyName); boolean isPropertyOf = (propertyDescriptor != null); return isPropertyOf; } /** * @param targetClass * @param propertyName * @return true if the given propertyName names a Collection property of the given class * @throws CompletionException if there is a problem accessing the named property on the given class */ public static boolean isCollectionPropertyOf(Class targetClass, String propertyName) { boolean isCollectionPropertyOf = false; PropertyDescriptor propertyDescriptor = buildReadDescriptor(targetClass, propertyName); if (propertyDescriptor != null) { Class clazz = propertyDescriptor.getPropertyType(); if ((clazz != null) && Collection.class.isAssignableFrom(clazz)) { isCollectionPropertyOf = true; } } return isCollectionPropertyOf; } public static PersistenceStructureService persistenceStructureService; /** * @return the persistenceStructureService */ public static PersistenceStructureService getPersistenceStructureService() { if ( persistenceStructureService == null ) { persistenceStructureService = KRADServiceLocator.getPersistenceStructureService(); } return persistenceStructureService; } /** * This method determines the Class of the attributeName passed in. Null will be returned if the member is not available, or if * a reflection exception is thrown. * * @param boClass - Class that the attributeName property exists in. * @param attributeName - Name of the attribute you want a class for. * @return The Class of the attributeName, if the attribute exists on the rootClass. Null otherwise. */ public static Class getAttributeClass(Class boClass, String attributeName) { // fail loudly if the attributeName isnt a member of rootClass if (!isPropertyOf(boClass, attributeName)) { throw new AttributeValidationException("unable to find attribute '" + attributeName + "' in rootClass '" + boClass.getName() + "'"); } //Implementing Externalizable Business Object Services... //The boClass can be an interface, hence handling this separately, //since the original method was throwing exception if the class could not be instantiated. if(boClass.isInterface()) return getAttributeClassWhenBOIsInterface(boClass, attributeName); else return getAttributeClassWhenBOIsClass(boClass, attributeName); } /** * * This method gets the property type of the given attributeName when the bo class is a concrete class * * @param boClass * @param attributeName * @return */ private static Class getAttributeClassWhenBOIsClass(Class boClass, String attributeName){ Object boInstance; try { boInstance = boClass.newInstance(); } catch (Exception e) { throw new RuntimeException("Unable to instantiate Data Object: " + boClass, e); } // attempt to retrieve the class of the property try { return ObjectUtils.getPropertyType(boInstance, attributeName, getPersistenceStructureService()); } catch (Exception e) { throw new RuntimeException("Unable to determine property type for: " + boClass.getName() + "." + attributeName, e); } } /** * * This method gets the property type of the given attributeName when the bo class is an interface * This method will also work if the bo class is not an interface, * but that case requires special handling, hence a separate method getAttributeClassWhenBOIsClass * * @param boClass * @param attributeName * @return */ private static Class getAttributeClassWhenBOIsInterface(Class boClass, String attributeName){ if (boClass == null) { throw new IllegalArgumentException("invalid (null) boClass"); } if (StringUtils.isBlank(attributeName)) { throw new IllegalArgumentException("invalid (blank) attributeName"); } PropertyDescriptor propertyDescriptor = null; String[] intermediateProperties = attributeName.split("\\."); int lastLevel = intermediateProperties.length - 1; Class currentClass = boClass; for (int i = 0; i <= lastLevel; ++i) { String currentPropertyName = intermediateProperties[i]; propertyDescriptor = buildSimpleReadDescriptor(currentClass, currentPropertyName); if (propertyDescriptor != null) { Class propertyType = propertyDescriptor.getPropertyType(); if ( propertyType.equals( PersistableBusinessObjectExtension.class ) ) { propertyType = getPersistenceStructureService().getBusinessObjectAttributeClass( currentClass, currentPropertyName ); } if (Collection.class.isAssignableFrom(propertyType)) { // TODO: determine property type using generics type definition throw new AttributeValidationException("Can't determine the Class of Collection elements because when the business object is an (possibly ExternalizableBusinessObject) interface."); } else { currentClass = propertyType; } } else { throw new AttributeValidationException("Can't find getter method of " + boClass.getName() + " for property " + attributeName); } } return currentClass; } /** * This method determines the Class of the elements in the collectionName passed in. * * @param boClass Class that the collectionName collection exists in. * @param collectionName the name of the collection you want the element class for * @return */ public static Class getCollectionElementClass(Class boClass, String collectionName) { if (boClass == null) { throw new IllegalArgumentException("invalid (null) boClass"); } if (StringUtils.isBlank(collectionName)) { throw new IllegalArgumentException("invalid (blank) collectionName"); } PropertyDescriptor propertyDescriptor = null; String[] intermediateProperties = collectionName.split("\\."); Class currentClass = boClass; for (int i = 0; i <intermediateProperties.length; ++i) { String currentPropertyName = intermediateProperties[i]; propertyDescriptor = buildSimpleReadDescriptor(currentClass, currentPropertyName); if (propertyDescriptor != null) { Class type = propertyDescriptor.getPropertyType(); if (Collection.class.isAssignableFrom(type)) { if (getPersistenceStructureService().isPersistable(currentClass)) { Map<String, Class> collectionClasses = new HashMap<String, Class>(); collectionClasses = getPersistenceStructureService().listCollectionObjectTypes(currentClass); currentClass = collectionClasses.get(currentPropertyName); } else { throw new RuntimeException("Can't determine the Class of Collection elements because persistenceStructureService.isPersistable(" + currentClass.getName() + ") returns false."); } } else { currentClass = propertyDescriptor.getPropertyType(); } } } return currentClass; } static private Map<String, Map<String, PropertyDescriptor>> cache = new TreeMap<String, Map<String, PropertyDescriptor>>(); /** * @param propertyClass * @param propertyName * @return PropertyDescriptor for the getter for the named property of the given class, if one exists. */ public static PropertyDescriptor buildReadDescriptor(Class propertyClass, String propertyName) { if (propertyClass == null) { throw new IllegalArgumentException("invalid (null) propertyClass"); } if (StringUtils.isBlank(propertyName)) { throw new IllegalArgumentException("invalid (blank) propertyName"); } PropertyDescriptor propertyDescriptor = null; String[] intermediateProperties = propertyName.split("\\."); int lastLevel = intermediateProperties.length - 1; Class currentClass = propertyClass; for (int i = 0; i <= lastLevel; ++i) { String currentPropertyName = intermediateProperties[i]; propertyDescriptor = buildSimpleReadDescriptor(currentClass, currentPropertyName); if (i < lastLevel) { if (propertyDescriptor != null) { Class propertyType = propertyDescriptor.getPropertyType(); if ( propertyType.equals( PersistableBusinessObjectExtension.class ) ) { propertyType = getPersistenceStructureService().getBusinessObjectAttributeClass( currentClass, currentPropertyName ); } if (Collection.class.isAssignableFrom(propertyType)) { if (getPersistenceStructureService().isPersistable(currentClass)) { Map<String, Class> collectionClasses = new HashMap<String, Class>(); collectionClasses = getPersistenceStructureService().listCollectionObjectTypes(currentClass); currentClass = collectionClasses.get(currentPropertyName); } else { throw new RuntimeException("Can't determine the Class of Collection elements because persistenceStructureService.isPersistable(" + currentClass.getName() + ") returns false."); } } else { currentClass = propertyType; } } } } return propertyDescriptor; } /** * @param propertyClass * @param propertyName * @return PropertyDescriptor for the getter for the named property of the given class, if one exists. */ public static PropertyDescriptor buildSimpleReadDescriptor(Class propertyClass, String propertyName) { if (propertyClass == null) { throw new IllegalArgumentException("invalid (null) propertyClass"); } if (StringUtils.isBlank(propertyName)) { throw new IllegalArgumentException("invalid (blank) propertyName"); } PropertyDescriptor p = null; // check to see if we've cached this descriptor already. if yes, return true. String propertyClassName = propertyClass.getName(); Map<String, PropertyDescriptor> m = cache.get(propertyClassName); if (null != m) { p = m.get(propertyName); if (null != p) { return p; } } // Use PropertyUtils.getPropertyDescriptors instead of manually constructing PropertyDescriptor because of // issues with introspection and generic/co-variant return types // See https://issues.apache.org/jira/browse/BEANUTILS-340 for more details PropertyDescriptor[] descriptors = PropertyUtils.getPropertyDescriptors(propertyClass); if (ArrayUtils.isNotEmpty(descriptors)) { for (PropertyDescriptor descriptor : descriptors) { if (descriptor.getName().equals(propertyName)) { p = descriptor; } } } // cache the property descriptor if we found it. if (p != null) { if (m == null) { m = new TreeMap<String, PropertyDescriptor>(); cache.put(propertyClassName, m); } m.put(propertyName, p); } return p; } public Set<InactivationBlockingMetadata> getAllInactivationBlockingMetadatas(Class blockedClass) { return ddMapper.getAllInactivationBlockingMetadatas(ddIndex, blockedClass); } /** * This method gathers beans of type BeanOverride and invokes each one's performOverride() method. */ // KULRICE-4513 public void performBeanOverrides() { Collection<BeanOverride> beanOverrides = ddBeans.getBeansOfType(BeanOverride.class).values(); if (beanOverrides.isEmpty()){ LOG.info("DataDictionary.performOverrides(): No beans to override"); } for (BeanOverride beanOverride : beanOverrides) { Object bean = ddBeans.getBean(beanOverride.getBeanName()); beanOverride.performOverride(bean); LOG.info("DataDictionary.performOverrides(): Performing override on bean: " + bean.toString()); } } }