package org.tradex.spring;
import java.lang.management.ManagementFactory;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.sql.DataSource;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.MethodReplacer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jmx.export.MBeanExporter;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.jmx.export.naming.SelfNaming;
import org.tradex.jmx.JMXHelper;
import org.tradex.util.Banner;
/**
* <p>Title: BeanContextUtil</p>
* <p>Description: Spring app context utilities</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.tradex.spring.BeanContextUtil</code></p>
*/
public class BeanContextUtil {
/** A static reference to the fully initialized BeanContextUtil instance */
protected static final AtomicReference<BeanContextUtil> staticInstance = new AtomicReference<BeanContextUtil>(null);
/** A latch to wait on for threads wishing to wait for this service to be ready */
protected static final CountDownLatch readyLatch = new CountDownLatch(1);
/** The wrapping generic application context */
protected GenericApplicationContext genericAppContext = null;
/** The Spring autowiring factory */
protected AutowireCapableBeanFactory autoWiringFactory = null;
/** Static class logger */
protected static final Logger log = Logger.getLogger(BeanContextUtil.class);
/**
* A static accessor for the BeanContextUtil
* @return the BeanContextUtil singleton
*/
public static BeanContextUtil get() {
BeanContextUtil bcu = staticInstance.get();
if(bcu==null) throw new IllegalStateException("The BeanContextUtil has not been initialized", new Throwable());
return bcu;
}
/**
* A static accessor for the BeanContextUtil
* @param waitFor If true, if the BeanContextUtil static instance has not been set, the thread will wait for it.
* @return the BeanContextUtil singleton
*/
public static BeanContextUtil get(boolean waitFor) {
if(waitFor) {
try {
readyLatch.await();
} catch (InterruptedException ie) {}
}
return get();
}
/** The default data source for settlement runs */
@Autowired(required=true)
@Qualifier("Default")
protected DataSource defaultDataSource = null;
/** The JdbcTemplate for the default data source for settlement runs */
protected JdbcTemplate defaultJdbcTemplate = null;
/** The named parameter JdbcTemplate for the default data source for settlement runs */
protected NamedParameterJdbcTemplate defaultNamedJdbcTemplate = null;
/** A map of all sprng defined data sources keyed by bean name */
protected final Map<String, DataSource> allDataSources = new HashMap<String, DataSource>();
/** A lazilly populated map of JDBC templates accessed by DataSource bean name */
protected final Map<String, JdbcTemplate> allJdbcTemplates = new ConcurrentHashMap<String, JdbcTemplate>();
/** A lazilly populated map of named parameter JDBC templates accessed by DataSource bean name */
protected final Map<String, NamedParameterJdbcTemplate> allNamedJdbcTemplates = new ConcurrentHashMap<String, NamedParameterJdbcTemplate>();
/** The core spring application context */
private ApplicationContext applicationContext;
/**
* Callback from the Spring context when it has refreshed
* @param event The context refreshed event
*/
public void onContextRefreshedEvent(ContextRefreshedEvent event) {
defaultJdbcTemplate = new JdbcTemplate(defaultDataSource);
defaultNamedJdbcTemplate = new NamedParameterJdbcTemplate(defaultDataSource);
allDataSources.putAll(applicationContext.getBeansOfType(DataSource.class));
if(applicationContext instanceof GenericApplicationContext) {
genericAppContext = (GenericApplicationContext)applicationContext;
} else {
//genericAppContext = new GenericApplicationContext((DefaultListableBeanFactory) applicationContext.getBeanFactory(), applicationContext);
genericAppContext = new GenericApplicationContext(applicationContext);
}
autoWiringFactory = applicationContext.getAutowireCapableBeanFactory();
log.info(Banner.banner("*", 3, 10, "BeanContextUtil Started", "JVM Process:" + ManagementFactory.getRuntimeMXBean().getName()).toString());
staticInstance.set(this);
readyLatch.countDown();
}
/**
* Returns a generic application context wrapper
* @return a generic application context
*/
public ApplicationContext getGenericApplicationContext() {
return genericAppContext;
}
/**
* Returns the default JdbcTemplate
* @return the default JdbcTemplate
*/
public JdbcTemplate getDefaultJDBCTemplate() {
return defaultJdbcTemplate;
}
/**
* Returns a JdbcTemplate for the named data source
* @param dsName The bean name of the datasource to get a JDBC template for
* @return a JdbcTemplate
*/
public JdbcTemplate getJDBCTemplate(String dsName) {
if(dsName==null) throw new IllegalArgumentException("The passed JDBCTemplate name was null", new Throwable());
JdbcTemplate template = allJdbcTemplates.get(dsName);
if(template==null) {
synchronized(allJdbcTemplates) {
template = allJdbcTemplates.get(dsName);
if(template==null) {
DataSource ds = allDataSources.get(dsName);
if(ds==null) {
throw new IllegalStateException("No datasource registered with bean name [" + dsName + "]", new Throwable());
}
template = new JdbcTemplate(ds);
allJdbcTemplates.put(dsName, template);
}
}
}
return template;
}
/**
* Returns a NamedParameterJdbcTemplate for the named data source
* @param dsName The bean name of the datasource to get a NamedParameterJdbcTemplate for
* @return a NamedParameterJdbcTemplate
*/
public NamedParameterJdbcTemplate getNamedJDBCTemplate(String dsName) {
if(dsName==null) throw new IllegalArgumentException("The passed NamedParameterJdbcTemplate name was null", new Throwable());
NamedParameterJdbcTemplate template = allNamedJdbcTemplates.get(dsName);
if(template==null) {
synchronized(allNamedJdbcTemplates) {
template = allNamedJdbcTemplates.get(dsName);
if(template==null) {
DataSource ds = allDataSources.get(dsName);
if(ds==null) {
throw new IllegalStateException("No datasource registered with bean name [" + dsName + "]", new Throwable());
}
template = new NamedParameterJdbcTemplate(ds);
allNamedJdbcTemplates.put(dsName, template);
}
}
}
return template;
}
/**
* Returns the default named parameter JdbcTemplate
* @return the default named parameter JdbcTemplate
*/
public NamedParameterJdbcTemplate getDefaultNamedJDBCTemplate() {
return defaultNamedJdbcTemplate;
}
/**
* Returns the default data source for this platform
* @return the default data source
*/
public DataSource getDefaultDataSource() {
return defaultDataSource;
}
/**
* Returns the named data source
* @param name The bean name of the datasource to retrieve
* @return the named data source
*/
public DataSource getDataSource(String name) {
if(name==null) throw new IllegalArgumentException("The passed name was null", new Throwable());
DataSource ds = allDataSources.get(name);
if(ds==null) throw new IllegalArgumentException("The passed name [" + name + "] is not a registered data source", new Throwable());
return ds;
}
/**
* Retrieves a number from the named sequence in the default data source
* @param sequenceName The sequence name
* @return The next value of the sequence
*/
public Number getDefaultSequence(String sequenceName) {
return _getSequence(sequenceName, getDefaultJDBCTemplate());
}
/**
* Retrieves a number from the named sequence in the default data source
* @param sequenceName The sequence name
* @param dsName The bean name of the datasource to get the sequence value from
* @return The next value of the sequence
*/
public Number getDefaultSequence(String sequenceName, String dsName) {
if(dsName==null) throw new IllegalArgumentException("The passed data source bean was null", new Throwable());
return _getSequence(sequenceName, getJDBCTemplate(dsName));
}
/**
* Retrieves a number from the named sequence in the data source the passed template is declared for
* @param sequenceName The sequence name
* @param template The template to use
* @return The next value of the sequence
*/
protected Number _getSequence(String sequenceName, JdbcTemplate template) {
if(sequenceName==null) throw new IllegalArgumentException("The passed sequence name was null", new Throwable());
return template.query("SELECT " + sequenceName + ".NEXTVAL FROM DUAL", new ResultSetExtractor<Number>(){
public Number extractData(ResultSet rs) throws SQLException, DataAccessException {
rs.next();
Number number = (Number)rs.getObject(1);
return number;
}
});
}
/**
* Wires a bean created outside the application context
* @param <T> The type of the bean being passed
* @param clazz The type of the bean to be created
* @param ctorArgs The constructor arguments to use when constructing the bean
* @return A newly created and wired bean
*/
@SuppressWarnings("unchecked")
public <T> T createWiredBeanInstance(Class<T> clazz, Object...ctorArgs) {
String beanName = clazz.getName() + ".BeanDefinition";
if(!genericAppContext.containsBeanDefinition(beanName)) {
synchronized(genericAppContext) {
if(!genericAppContext.containsBeanDefinition(beanName)) {
GenericBeanDefinition beanDef = new GenericBeanDefinition();
beanDef.setBeanClass(clazz);
beanDef.setScope("prototype");
ConstructorArgumentValues ctorValues = new ConstructorArgumentValues();
if(ctorArgs!=null) {
for(int i = 0; i < ctorArgs.length; i++) {
ctorValues.addGenericArgumentValue(new ValueHolder(ctorArgs[i], ctorArgs[i].getClass().getName()));
}
beanDef.setConstructorArgumentValues(ctorValues);
}
genericAppContext.registerBeanDefinition(beanName, beanDef);
}
}
}
T t = (T)genericAppContext.getBean(beanName, ctorArgs);
autoWiringFactory.autowireBean(t);
return t;
}
/**
* Wires an already created bean
* @param bean The bean to wire
*/
public void wireBean(Object bean) {
autoWiringFactory.autowireBean(bean);
autoWiringFactory.applyBeanPostProcessorsAfterInitialization(bean, bean.getClass().getName() + ".BeanDefinition");
}
/**
* Wires a bean created outside the application context
* @param <T> The type of the bean being passed
* @param factoryMethodName The static method name in the target class to use as a factory. Ignored if null.
* @param clazz The type of the bean to be created
* @param ctorArgs The constructor arguments to use when constructing the bean
* @return A newly created and wired bean
*/
public <T> T factorizeWiredBeanInstance(String factoryMethodName, Class<T> clazz, Object...ctorArgs) {
String beanName = clazz.getName() + ".BeanDefinition";//generateKey(ctorArgs);
if(!genericAppContext.containsBeanDefinition(beanName)) {
synchronized(genericAppContext) {
if(!genericAppContext.containsBeanDefinition(beanName)) {
GenericBeanDefinition beanDef = new GenericBeanDefinition();
beanDef.setBeanClass(clazz);
beanDef.setScope("prototype");
if(factoryMethodName!=null) {
beanDef.setFactoryMethodName(factoryMethodName);
}
ConstructorArgumentValues ctorValues = new ConstructorArgumentValues();
if(ctorArgs!=null) {
for(int i = 0; i < ctorArgs.length; i++) {
if(ctorArgs[i]==null) {
ctorValues.addIndexedArgumentValue(i, (Object)null);
} else {
ctorValues.addIndexedArgumentValue(i, new ValueHolder(ctorArgs[i], ctorArgs[i].getClass().getName()));
}
}
beanDef.setConstructorArgumentValues(ctorValues);
// beanDef.setSynthetic(true);
}
genericAppContext.registerBeanDefinition(beanName, beanDef);
}
}
}
T t = (T)genericAppContext.getBean(beanName, ctorArgs);
if(clazz.getAnnotation(ManagedResource.class)!=null) {
MBeanExporter exporter = applicationContext.getBean(MBeanExporter.class);
try {
ObjectName on = getObjectName(t);
if(on!=null) {
exporter.registerManagedResource(t, on);
} else {
exporter.registerManagedResource(t);
}
} catch (Exception e) {
e.printStackTrace(System.err);
}
}
return t;
}
/**
* Determines the intended JMX ObjectName for the passed instance.
* @param instance The object to determine the JMX ObjectName for
* @return an ObjectName or null if one could not be determined.
*/
protected static ObjectName getObjectName(Object instance) {
Class<?> clazz = instance.getClass();
ObjectName on = null;
ManagedResource mr = clazz.getAnnotation(ManagedResource.class);
if(mr!=null && !mr.objectName().equals("")) {
return JMXHelper.objectName(mr.objectName());
}
if(instance instanceof SelfNaming) {
try {
return ((SelfNaming)instance).getObjectName();
} catch (MalformedObjectNameException e) {
}
}
return on;
}
/**
* Generates a unique key from the passed args.
* @param ctorArgs
* @return
*/
protected static String generateKey(Object...ctorArgs) {
StringBuilder b = new StringBuilder();
if(ctorArgs!=null) {
for(Object obj: ctorArgs) {
if(obj!=null) {
b.append(obj.toString());
}
}
}
if(b.length()<1) {
b.append(System.nanoTime());
}
return b.toString();
}
/**
* Sets the default datasource
* @param defaultDataSource
*/
public void setDefaultDataSource(DataSource defaultDataSource) {
this.defaultDataSource = defaultDataSource;
}
}