Skip to content

Instantly share code, notes, and snippets.

@jncorpron
Created April 6, 2013 03:45
Show Gist options
  • Save jncorpron/5324669 to your computer and use it in GitHub Desktop.
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…
/*
* 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