Created
April 6, 2013 03:45
-
-
Save jncorpron/5324669 to your computer and use it in GitHub Desktop.
SpringBooster is a utility class for launching main method Java classes with support for the spring-test annotations for configuration. SpringBooster will also instantiate and optionally call the calling class's run() method. Also works if the payload already exists. Requires spring-test and commons-lang dependency, and optionally google-guava a…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2010 Justin Corpron | |
*/ | |
package com.javareactor.spring.util; | |
import java.util.Arrays; | |
import java.util.List; | |
import java.util.Set; | |
import org.apache.commons.lang3.Validate; | |
import org.apache.commons.lang3.time.StopWatch; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import org.springframework.beans.factory.config.AutowireCapableBeanFactory; | |
import org.springframework.beans.factory.support.DefaultListableBeanFactory; | |
import org.springframework.context.ConfigurableApplicationContext; | |
import org.springframework.context.annotation.AnnotationConfigApplicationContext; | |
import org.springframework.context.support.ClassPathXmlApplicationContext; | |
import org.springframework.context.support.GenericApplicationContext; | |
import org.springframework.core.io.support.ResourcePatternResolver; | |
import org.springframework.test.context.ActiveProfiles; | |
import org.springframework.test.context.ContextConfiguration; | |
import com.google.common.collect.Lists; | |
import com.google.common.collect.Sets; | |
/** | |
* Launch a spring application using the Spring-test configuration on any annotated class. | |
* <p> | |
* Example Usage: | |
* <code> | |
* @ContextConfiguration( | |
* classes = { | |
* LocalMongoConfiguration.class, | |
* MongoJobListingConfiguration.class | |
* }) | |
* @ActiveProfiles("dev") | |
* public class MongoDataImportApp | |
* extends ImportableJobListingGeneratorApp { | |
* | |
* public static void main(String[] args) { | |
* SpringBooster.launch(); | |
* } | |
* } | |
* </code> | |
*/ | |
public class SpringBooster { | |
private static final Logger logger = | |
LoggerFactory.getLogger(SpringBooster.class); | |
private static final ThreadLocal<SpringBooster> booster = | |
new ThreadLocal<SpringBooster>(); | |
private Class<?> payloadClass; | |
private Object payload; | |
private boolean destroyAfterLaunch; | |
private String[] payloadAliases = new String[0]; | |
private String payloadId; | |
private Runnable runnable; | |
private String[] configurationLocations; | |
private Class<?>[] configurationClasses; | |
private String[] activeProfiles; | |
private ConfigurableApplicationContext context; | |
protected SpringBooster(Class<?> payloadClass) { | |
this.payloadClass = payloadClass; | |
booster.set(this); | |
} | |
protected SpringBooster(Object payload) { | |
this.payload = payload; | |
payloadClass = payload.getClass(); | |
booster.set(this); | |
} | |
public void setConfigurationLocations(String[] configurationLocations) { | |
this.configurationLocations = configurationLocations; | |
} | |
public void setDestroyAfterLaunch(boolean destroyAfterLaunch) { | |
this.destroyAfterLaunch = destroyAfterLaunch; | |
} | |
public void setPayloadAliases(String[] payloadAliases) { | |
this.payloadAliases = payloadAliases; | |
} | |
public void setPayloadId(String payloadId) { | |
this.payloadId = payloadId; | |
} | |
public void ignite() { | |
StopWatch stopWatch = new StopWatch(); | |
stopWatch.start(); | |
if (configurationLocations == null | |
&& configurationClasses == null) { | |
determineConfiguration(); | |
} | |
if (activeProfiles == null) { | |
determineActiveProfiles(); | |
} | |
constructPayload(); | |
if (payload instanceof Runnable) { | |
runnable = (Runnable) payload; | |
} | |
createContext(); | |
autowirePayload(); | |
runPayload(); | |
if (isSpent()) { | |
destroy(); | |
} | |
stopWatch.stop(); | |
logger.info("Spring boosted application launched in {}ms", | |
stopWatch.getTime()); | |
} | |
private void determineConfiguration() { | |
ContextConfiguration currentAnnotation; | |
Class<?> currentClass = payloadClass; | |
Set<String> configurationLocations = Sets.newHashSet(); | |
Set<Class<?>> configurationClasses = Sets.newHashSet(); | |
do { | |
currentAnnotation = | |
currentClass.getAnnotation(ContextConfiguration.class); | |
if (currentAnnotation != null) { | |
// XML configuration locations | |
List<String> currentConfigurationLocations = | |
Lists.newArrayList(currentAnnotation.value()); | |
for (int i = 0; i < currentConfigurationLocations.size(); i++) { | |
String configLocation = currentConfigurationLocations.get(i); | |
if (!configLocation.startsWith( | |
ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX)) { | |
currentConfigurationLocations.set( | |
i, | |
ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + configLocation); | |
} | |
} | |
configurationLocations.addAll( | |
currentConfigurationLocations); | |
// Class configurations | |
List<Class<?>> currentConfigurationClasses = | |
Lists.newArrayList(currentAnnotation.classes()); | |
configurationClasses.addAll(currentConfigurationClasses); | |
} | |
currentClass = currentClass.getSuperclass(); | |
} while ((currentAnnotation == null | |
|| currentAnnotation.inheritLocations()) | |
&& currentClass != null); | |
Validate.isTrue( | |
!configurationLocations.isEmpty() | |
|| !configurationClasses.isEmpty(), | |
"No @ContextConfiguration found on the application class " + | |
"or its super classes."); | |
this.configurationLocations = configurationLocations.toArray(new String[0]); | |
this.configurationClasses = configurationClasses.toArray(new Class[0]); | |
} | |
private void determineActiveProfiles() { | |
ActiveProfiles currentAnnotation; | |
Class<?> currentClass = payloadClass; | |
Set<String> activeProfiles = Sets.newHashSet(); | |
do { | |
currentAnnotation = | |
currentClass.getAnnotation(ActiveProfiles.class); | |
if (currentAnnotation != null) { | |
activeProfiles.addAll(Arrays.asList(currentAnnotation.value())); | |
activeProfiles.addAll(Arrays.asList(currentAnnotation.profiles())); | |
} | |
} while ((currentAnnotation == null | |
|| !currentAnnotation.inheritProfiles()) | |
&& currentClass != null); | |
this.activeProfiles = activeProfiles.toArray(new String[0]); | |
} | |
private void createContext() { | |
/* | |
* We'll create a parent context with the payload to inject it into an | |
* application context with the config files. This is based on this | |
* suggestion: | |
* http://stackoverflow.com/questions/496711/adding-a-pre-constructed-bean-to-a-spring-application-context | |
*/ | |
DefaultListableBeanFactory payloadBeanFactory = | |
new DefaultListableBeanFactory(); | |
// payload bean | |
if (payloadId == null) { | |
payloadId = payload.getClass().getSimpleName(); | |
} | |
payloadBeanFactory.registerSingleton(payloadId, payload); | |
if (payloadAliases != null) { | |
// payload aliases | |
for (String payloadAlias : payloadAliases) { | |
payloadBeanFactory.registerAlias(payloadId, payloadAlias); | |
} | |
} | |
GenericApplicationContext payloadContext = | |
new GenericApplicationContext(payloadBeanFactory); | |
payloadContext.refresh(); | |
if (configurationLocations.length > 0) { | |
ClassPathXmlApplicationContext configContext = | |
new ClassPathXmlApplicationContext(); | |
configContext.getEnvironment().setActiveProfiles(activeProfiles); | |
configContext.setParent(payloadContext); | |
configContext.setConfigLocations(configurationLocations); | |
context = configContext; | |
} else { | |
AnnotationConfigApplicationContext configContext = | |
new AnnotationConfigApplicationContext(); | |
configContext.setParent(payloadContext); | |
configContext.getEnvironment().setActiveProfiles(activeProfiles); | |
configContext.register(configurationClasses); | |
context = configContext; | |
} | |
context.refresh(); | |
} | |
private void autowirePayload() { | |
AutowireCapableBeanFactory beanFactory = context.getAutowireCapableBeanFactory(); | |
beanFactory.autowireBeanProperties(payload, AutowireCapableBeanFactory.AUTOWIRE_BY_NAME, false); | |
beanFactory.initializeBean(payload, payloadClass.getName()); | |
} | |
private void runPayload() { | |
if (runnable != null) { | |
runnable.run(); | |
} | |
} | |
private boolean isSpent() { | |
return runnable != null || destroyAfterLaunch; | |
} | |
private void constructPayload() { | |
if (payload == null) { | |
try { | |
payload = payloadClass.newInstance(); | |
} catch (InstantiationException e) { | |
throw new RuntimeException("Unable to construct payload", e); | |
} catch (IllegalAccessException e) { | |
throw new RuntimeException("Unable to construct payload", e); | |
} | |
} | |
} | |
private void destroy() { | |
context.close(); | |
} | |
public static Builder build() { | |
Class<?> payloadClass = determineLaunchingClass(); | |
return new Builder(payloadClass); | |
} | |
/** | |
* Launch a spring application based on the annotated calling class. | |
* This will instantiate an instance of the calling class which will be the | |
* 'boosted' payload. | |
*/ | |
public static void launch() { | |
Class<?> clazz = determineLaunchingClass(); | |
new SpringBooster(clazz).ignite(); | |
} | |
/** | |
* Launch a spring application based on the annotated calling class. | |
*/ | |
public static void launch(Object payload) { | |
new SpringBooster(payload).ignite(); | |
} | |
public static void abort() { | |
booster.get().destroy(); | |
booster.remove(); | |
} | |
public static Class<?> determineLaunchingClass() { | |
StackTraceElement[] stackTrace = new Exception().getStackTrace(); | |
try { | |
return Class.forName(stackTrace[2].getClassName()); | |
} catch (ClassNotFoundException e) { | |
throw new RuntimeException(e); | |
} | |
} | |
public static class Builder { | |
private Object payload; | |
private Class<?> payloadClass; | |
private String payloadId; | |
private String[] payloadAliases; | |
private boolean destroyAfterLaunch = false; | |
private String[] configLocations; | |
public Builder(Class<?> payloadClass) { | |
this.payloadClass = payloadClass; | |
} | |
public Builder configs(String...locations) { | |
this.configLocations = locations; | |
return this; | |
} | |
public Builder withPayload(Object payload) { | |
this.payload = payload; | |
return this; | |
} | |
public Builder destroyAfterLaunch() { | |
this.destroyAfterLaunch = true; | |
return this; | |
} | |
public Builder payloadId(String payloadId) { | |
this.payloadId = payloadId; | |
return this; | |
} | |
public void launch() { | |
SpringBooster booster; | |
if (payload == null) { | |
booster = new SpringBooster(payloadClass); | |
} else { | |
booster = new SpringBooster(payload); | |
} | |
booster.setConfigurationLocations(configLocations); | |
booster.setDestroyAfterLaunch(destroyAfterLaunch); | |
booster.setPayloadId(payloadId); | |
booster.setPayloadAliases(payloadAliases); | |
booster.ignite(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment