/**
* Copyright (C) 2011 Brian Ferris <bdferris@onebusaway.org>
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-2.0
*
* 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.onebusaway.container.spring;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Properties;
import java.util.Set;
import net.sf.ehcache.CacheManager;
import org.hibernate.SessionFactory;
import org.onebusaway.container.spring.ehcache.EhCacheFactoryBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PropertyResourceConfigurer;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
/**
* A Spring {@link BeanFactoryPostProcessor} that can set bean dependencies to
* adjust bean creation order. Typically, you don't need to set bean
* dependencies directly, as Spring figures it out from bean references in your
* application context in bean property setters. Sometimes, you need to set a
* dependency that Spring cannot detect on its own, and you'd do this by
* adjusting the {@code depends-on} attribute of a bean defintion. While that
* works for most cases, we've managed to find a case where that doesn't work.
*
* To give a specific example, we're creating a Hibernate {@link SessionFactory}
* that uses an EhCache {@link CacheManager} to manage the second-level cache,
* as defined in the {@code
* org/onebusaway/container/application-context-caching.xml} and {@code
* org/onebusaway/container/application-context-hibernate.xml} application
* context config files. A number of modules import these configs and add
* additional Hibernate entity classes and mappings. These modules would also
* like to add EhCache second-level caches for these entity classes. These
* caches can be created with {@link EhCacheFactoryBean}, but they need to be
* instantiated before the {@link SessionFactory}, as the session factory will
* query the {@link CacheManager} for cache entries on creation. To ensure that
* these cache factories are instantiated before the session factory, and we
* can't set the {@code depends-on} for the session factory in the config, we
* use the {@link DependencyConfigurer} to manipulate the dependency
* relationship directly.
*
* To use {@link DependencyConfigurer}, set a "properties" property for the bean
* definition where the each property key is a bean name and each property value
* is a list of dependent bean names separated by commas. So for example:
*
* <pre class="code">
* <bean class="org.onebusaway.container.spring.DependencyConfigurer">
* <property name="properties">
* <props>
* <prop key="beanA">beanB,beanC</prop>
* </props>
* </property>
* </bean>
* </pre>
*
* This would make the bean "beanA" depend on beans "beanB" and "beanC".
*
* @author bdferris
*
*/
public class DependencyConfigurer extends PropertyResourceConfigurer {
private static Logger _log = LoggerFactory.getLogger(DependencyConfigurer.class);
private boolean _ignoreInvalidKeys = false;
@Override
protected void processProperties(ConfigurableListableBeanFactory beanFactory,
Properties props) throws BeansException {
for (Enumeration<?> names = props.propertyNames(); names.hasMoreElements();) {
String key = (String) names.nextElement();
try {
processKey(beanFactory, key, props.getProperty(key));
} catch (BeansException ex) {
String msg = "Could not process key '" + key
+ "' in PropertyOverrideConfigurer";
if (!_ignoreInvalidKeys) {
throw new BeanInitializationException(msg, ex);
}
if (logger.isDebugEnabled()) {
logger.debug(msg, ex);
}
}
}
}
protected void processKey(ConfigurableListableBeanFactory beanFactory,
String beanName, String property) {
BeanDefinition bd = beanFactory.getBeanDefinition(beanName);
if (bd instanceof AbstractBeanDefinition) {
AbstractBeanDefinition abd = (AbstractBeanDefinition) bd;
Set<String> dependsOn = new LinkedHashSet<String>();
String[] existingDependencies = abd.getDependsOn();
if (existingDependencies != null) {
for (String name : existingDependencies)
dependsOn.add(name);
}
String[] beanNames = property.split(",");
for (String name : beanNames)
dependsOn.add(name);
abd.setDependsOn(dependsOn.toArray(new String[dependsOn.size()]));
} else {
_log.warn("bean definition for \""
+ beanName
+ "\" does not extend AbstractBeanDefinition, so we can't set depends-on");
}
}
}