/*
* Copyright (c) 2012-2014, Parallel Universe Software Co. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation
*
* or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 3.0
* as published by the Free Software Foundation.
*/
package co.paralleluniverse.common.spring;
import com.google.common.base.Throwables;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.InstantiationStrategy;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.jmx.export.MBeanExporter;
import org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource;
import org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler;
/**
* Spring helper functions.
*
* @author pron
*/
public final class SpringContainerHelper {
private static final Logger LOG = LoggerFactory.getLogger(SpringContainerHelper.class);
public static ConfigurableApplicationContext createContext(String defaultDomain, Resource xmlResource, Object properties, BeanFactoryPostProcessor beanFactoryPostProcessor) {
LOG.info("JAVA: {} {}, {}", new Object[]{System.getProperty("java.runtime.name"), System.getProperty("java.runtime.version"), System.getProperty("java.vendor")});
LOG.info("OS: {} {}, {}", new Object[]{System.getProperty("os.name"), System.getProperty("os.version"), System.getProperty("os.arch")});
LOG.info("DIR: {}", System.getProperty("user.dir"));
final DefaultListableBeanFactory beanFactory = createBeanFactory();
final GenericApplicationContext context = new GenericApplicationContext(beanFactory);
context.registerShutdownHook();
final PropertyPlaceholderConfigurer propertyPlaceholderConfigurer = new PropertyPlaceholderConfigurer();
propertyPlaceholderConfigurer.setSystemPropertiesMode(PropertyPlaceholderConfigurer.SYSTEM_PROPERTIES_MODE_OVERRIDE);
if (properties != null) {
if (properties instanceof Resource)
propertyPlaceholderConfigurer.setLocation((Resource) properties);
else if (properties instanceof Properties)
propertyPlaceholderConfigurer.setProperties((Properties) properties);
else
throw new IllegalArgumentException("Properties argument - " + properties + " - is of an unhandled type");
}
context.addBeanFactoryPostProcessor(propertyPlaceholderConfigurer);
// MBean exporter
//final MBeanExporter mbeanExporter = new AnnotationMBeanExporter();
//mbeanExporter.setServer(ManagementFactory.getPlatformMBeanServer());
//beanFactory.registerSingleton("mbeanExporter", mbeanExporter);
context.registerBeanDefinition("mbeanExporter", getMBeanExporterBeanDefinition(defaultDomain));
// inject bean names into components
context.addBeanFactoryPostProcessor(new BeanFactoryPostProcessor() {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
for (String beanName : beanFactory.getBeanDefinitionNames()) {
try {
final BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
if (beanDefinition.getBeanClassName() != null) { // van be null for factory methods
final Class<?> beanClass = Class.forName(beanDefinition.getBeanClassName());
if (Component.class.isAssignableFrom(beanClass))
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, beanName);
}
} catch (Exception ex) {
LOG.error("Error loading bean " + beanName + " definition.", ex);
throw new Error(ex);
}
}
}
});
if (beanFactoryPostProcessor != null)
context.addBeanFactoryPostProcessor(beanFactoryPostProcessor);
beanFactory.registerCustomEditor(org.w3c.dom.Element.class, co.paralleluniverse.common.util.DOMElementProprtyEditor.class);
final Map<String, Object> beans = new HashMap<String, Object>();
beanFactory.addBeanPostProcessor(new BeanPostProcessor() {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
LOG.info("Loading bean {} [{}]", beanName, bean.getClass().getName());
beans.put(beanName, bean);
if (bean instanceof Service) {
final BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
Collection<String> dependencies = getBeanDependencies(beanDefinition);
for (String dependeeName : dependencies) {
Object dependee = beanFactory.getBean(dependeeName);
if (dependee instanceof Service) {
((Service) dependee).addDependedBy((Service) bean);
((Service) bean).addDependsOn((Service) dependee);
}
}
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
LOG.info("Bean {} [{}] loaded", beanName, bean.getClass().getName());
return bean;
}
});
final XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader((BeanDefinitionRegistry) beanFactory);
xmlReader.loadBeanDefinitions(xmlResource);
// start container
context.refresh();
// Call .postInit() on all Components
// There's probably a better way to do this
try {
for (Map.Entry<String, Object> entry : beans.entrySet()) {
final String beanName = entry.getKey();
final Object bean = entry.getValue();
if (bean instanceof Component) {
LOG.info("Performing post-initialization on bean {} [{}]", beanName, bean.getClass().getName());
((Component) bean).postInit();
}
}
} catch (Exception e) {
throw Throwables.propagate(e);
}
return context;
}
public static Collection<String> getBeanDependencies(BeanDefinition beanDefinition) {
Set<String> dependencies = new HashSet<String>();
if (beanDefinition.getDependsOn() != null)
dependencies.addAll(Arrays.asList(beanDefinition.getDependsOn()));
for (ValueHolder value : beanDefinition.getConstructorArgumentValues().getGenericArgumentValues()) {
if (value.getValue() instanceof BeanReference)
dependencies.add(((BeanReference) value.getValue()).getBeanName());
}
for (ValueHolder value : beanDefinition.getConstructorArgumentValues().getIndexedArgumentValues().values()) {
if (value.getValue() instanceof BeanReference)
dependencies.add(((BeanReference) value.getValue()).getBeanName());
}
for (PropertyValue value : beanDefinition.getPropertyValues().getPropertyValueList()) {
if (value.getValue() instanceof BeanReference)
dependencies.add(((BeanReference) value.getValue()).getBeanName());
}
return dependencies;
}
/**
* adds hooks to capture autowired constructor args and add them as dependencies
*
* @return
*/
private static DefaultListableBeanFactory createBeanFactory() {
return new DefaultListableBeanFactory() {
{
final InstantiationStrategy is = getInstantiationStrategy();
setInstantiationStrategy(new InstantiationStrategy() {
@Override
public Object instantiate(RootBeanDefinition beanDefinition, String beanName, BeanFactory owner) throws BeansException {
return is.instantiate(beanDefinition, beanName, owner);
}
@Override
public Object instantiate(RootBeanDefinition beanDefinition, String beanName, BeanFactory owner, Constructor<?> ctor, Object[] args) throws BeansException {
final Object bean = is.instantiate(beanDefinition, beanName, owner, ctor, args);
addDependencies(bean, args);
return bean;
}
@Override
public Object instantiate(RootBeanDefinition beanDefinition, String beanName, BeanFactory owner, Object factoryBean, Method factoryMethod, Object[] args) throws BeansException {
final Object bean = is.instantiate(beanDefinition, beanName, owner, factoryBean, factoryMethod, args);
addDependencies(bean, args);
return bean;
}
});
}
private void addDependencies(Object bean, Object[] args) {
if (bean instanceof Service) {
for (Object arg : args) {
if (arg instanceof Service) {
((Service) arg).addDependedBy((Service) bean);
((Service) bean).addDependsOn((Service) arg);
}
}
}
}
};
}
public static BeanDefinition defineBean(Class<?> clazz, ConstructorArgumentValues constructorArgs, MutablePropertyValues properties) {
GenericBeanDefinition bean = new GenericBeanDefinition();
bean.setBeanClass(clazz);
bean.setAutowireCandidate(true);
bean.setConstructorArgumentValues(constructorArgs);
bean.setPropertyValues(properties);
return bean;
}
private static BeanDefinition getMBeanExporterBeanDefinition(String defaultDomain) {
final AnnotationJmxAttributeSource annotationSource = new AnnotationJmxAttributeSource();
final GenericBeanDefinition bean = new GenericBeanDefinition();
bean.setBeanClass(MBeanExporter.class);
MutablePropertyValues properties = new MutablePropertyValues();
properties.add("server", ManagementFactory.getPlatformMBeanServer());
properties.add("autodetectMode", MBeanExporter.AUTODETECT_ASSEMBLER);
properties.add("assembler", new MetadataMBeanInfoAssembler(annotationSource));
properties.add("namingStrategy", new MBeanNamingStrategy(annotationSource).setDefaultDomain(defaultDomain));
bean.setPropertyValues(properties);
return bean;
}
public static ConstructorArgumentValues constructorArgs(Object... args) {
ConstructorArgumentValues cav = new ConstructorArgumentValues();
for (int i = 0; i < args.length; i++)
cav.addIndexedArgumentValue(i, args[i]);
return cav;
}
public static MutablePropertyValues properties(Map<String, ? extends Object> properties) {
MutablePropertyValues mpv = new MutablePropertyValues(properties);
return mpv;
}
private SpringContainerHelper() {
}
}