/*
* Copyright (C) 2013 Intel Corporation
* All rights reserved.
*/
package com.intel.mtwilson.test;
import com.intel.dcsg.cpg.classpath.ClassLoadingStrategy;
import com.intel.dcsg.cpg.classpath.FencedClassLoadingStrategy;
import com.intel.dcsg.cpg.classpath.JarUtil;
import com.intel.dcsg.cpg.classpath.MavenResolver;
import com.intel.dcsg.cpg.classpath.UnitedClassLoadingStrategy;
import com.intel.dcsg.cpg.extensions.AnnotationRegistrar;
import com.intel.dcsg.cpg.extensions.ExtensionUtil;
import com.intel.dcsg.cpg.extensions.ImplementationRegistrar;
import com.intel.dcsg.cpg.module.Container;
import com.intel.dcsg.cpg.classpath.JarClassIterator;
import com.intel.dcsg.cpg.module.Module;
import com.intel.dcsg.cpg.module.ModuleUtil;
import com.intel.mtwilson.launcher.ExtensionLauncher;
import com.intel.mtwilson.launcher.ext.Initialize;
import com.intel.mtwilson.launcher.ext.Configure;
import com.intel.mtwilson.launcher.ext.Validate;
import com.intel.mtwilson.launcher.ext.Start;
import com.intel.mtwilson.launcher.ext.Stop;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.jar.Manifest;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.util.Factory;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.reflections.Reflections;
/**
* @author jbuhacoff
*/
public class JunitMavenLauncher extends ExtensionLauncher {
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(JunitMavenLauncher.class);
private static JunitMavenLauncher launcher;
private final MavenResolver resolver = new MavenResolver();
private final Properties configuration;
private final HashSet<String> moduleNames = new HashSet<String>();
private final Set<Module> modules = new HashSet<Module>();
private File targetDirectory;
private File mavenRepositoryDirectory;
// private Container container = new Container();
// private boolean continueEventLoop = true;
private ClassLoadingStrategy classLoadingStrategy = new FencedClassLoadingStrategy(); // a reasonable default until we get semantic versioning working
public JunitMavenLauncher() {
this(new Properties());
}
public JunitMavenLauncher(Properties configuration) {
this.configuration = configuration;
}
public ClassLoadingStrategy getClassLoadingStrategy() {
return classLoadingStrategy;
}
public void setClassLoadingStrategy(ClassLoadingStrategy classLoadingStrategy) {
this.classLoadingStrategy = classLoadingStrategy;
}
public Set<String> getModuleNames() {
return moduleNames;
}
public Properties getProperties() {
return configuration;
}
/**
*
* @param moduleName in the maven form groupId:artifactId:version
*/
public void addModule(String moduleName) {
log.debug("Adding module: {}", moduleName);
moduleNames.add(moduleName);
}
public void addModule(String groupId, String artifactId, String version) {
addModule(String.format("%s:%s:%s", groupId, artifactId, version));
}
private void initializeEnvironment() {
// maybe this default directory code should be inside MavenModuleRepository ? or inside MavenResolver ?
String defaultLocalRepository = System.getProperty("user.home") + File.separator + ".m2" + File.separator + "repository";
mavenRepositoryDirectory = new File(configuration.getProperty("localRepository", defaultLocalRepository));
if (!mavenRepositoryDirectory.exists()) {
throw new IllegalStateException("Missing maven repository");
}
targetDirectory = new File("." + File.separator + "target" + File.separator + "jmod");
if (!targetDirectory.exists()) {
targetDirectory.mkdirs();
}
}
// XXX TODO similar code here and in DirectoryLauncher and MavenLauncher
public void loadModules() throws IOException {
for (String moduleName : moduleNames) {
log.debug("Loading module: {}", moduleName);
File moduleJarFile = locateModuleJarFile(moduleName);
if (ModuleUtil.isModule(moduleJarFile)) {
Manifest manifest = JarUtil.readManifest(moduleJarFile);
// Set<File> classpath = resolver.resolveClasspath(module.getManifest()); // XXX TODO need to make the class loader strategy aware of the resolver?? should be a DirectoryResolver and a MavenResolver ... each needs to know the jar File and Manifest (for classpath or maven-classpath)
Module module = new Module(moduleJarFile, manifest, classLoadingStrategy.getClassLoader(moduleJarFile, manifest, resolver));
log.debug("Module: {}", module.getImplementationTitle() + "-" + module.getImplementationVersion());
log.debug("Class-Path: {}", (Object[])module.getClasspath());
log.debug("Module-Components: {}", (Object[])module.getComponentNames());
// before we try to activate the module, make sure that all its dependencies are present and if not, XXX TODO try to download them automatically
Collection<String> missingArtifacts = resolver.listMissingArtifacts(manifest);
// if any are missing we quit
if (missingArtifacts.isEmpty()) {
log.debug("Classpath ok, registering module");
// container.register(module);
modules.add(module);
} else {
log.warn("Module {} is missing {} jars from classpath", module.getImplementationTitle(), missingArtifacts.size());
}
}
}
// log.debug("Found {} modules", container.getModules().size());
log.debug("Found {} modules", modules.size());
}
private File locateModuleJarFile(String moduleName) {
String[] parts = moduleName.split(":"); // should be exactly 3 : groupId, artifactId, version
File moduleJarFile = resolver.findJarFile(parts[0], parts[1], parts[2]);
if (!moduleJarFile.exists()) {
log.error("Cannot find module: {}", moduleName);
}
return moduleJarFile;
}
private void registerExtensions() throws IOException {
// XXX TODO this should really be somewhere else... but right now Module is tightly coupled to the Component design in cpg-module , and it has
// a component search function but it uses component-specific interfaces with JarComponentIterator.
ImplementationRegistrar registrar = new ImplementationRegistrar();
for(Module module : modules) {
JarClassIterator it = new JarClassIterator(module.getJarFile(), module.getClassLoader());
while(it.hasNext()) {
Class<?> clazz = it.next();
log.debug("Scanning class for extensions {}", clazz.getName());
ExtensionUtil.scan(registrar, clazz); // the whiteboard will include EVERYTHING that implements any interface
}
}
/*
Reflections reflections = new Reflections(); // not specifying package name "my.project.prefix"
Class<?>[] lifecycle = new Class<?>[] { Initialize.class, Configure.class, Validate.class, Startup.class, Shutdown.class };
for(Class<?> phase : lifecycle) {
Set<Class<?>> annotated = reflections.getTypesAnnotatedWith((Class<? extends Annotation>)phase);
AnnotationRegistrar registrar = new AnnotationRegistrar((Class<? extends Annotation>)phase);
ExtensionUtil.scan(registrar, annotated);
}
*/
/*
Reflections reflections = new Reflections(); // not specifying package name "my.project.prefix"
Set<Class<?>> phaseExtensions = new HashSet<Class<?>>();
phaseExtensions.addAll(reflections.getSubTypesOf(Initialize.class));
phaseExtensions.addAll(reflections.getSubTypesOf(Configure.class));
phaseExtensions.addAll(reflections.getSubTypesOf(Validate.class));
phaseExtensions.addAll(reflections.getSubTypesOf(Start.class));
phaseExtensions.addAll(reflections.getSubTypesOf(Stop.class));
ImplementationRegistrar registrar = new ImplementationRegistrar();
ExtensionUtil.scan(registrar, phaseExtensions);
* */ }
/**
* Default implementation is empty; override to add modules using
* addModule(). No need to call super.modules() because it would do
* nothing.
*/
// protected abstract void modules(); { log.debug("DEFAULT MODULES FUNCTION CALLED"); }
/*
@BeforeClass
public static void startup() throws IOException {
// first we need to initialize our extensions registry by scanning the classpath for all the launcher extensions...
launcher.initializeEnvironment();
launcher.modules(); // add any modules that should be included in the test
launcher.loadModules();
launcher.registerExtensions(); // throws IOException
launcher.initialize();
launcher.configure();
launcher.validate();
launcher.start();
}
@AfterClass
public static void shutdown() {
launcher.stop();
}*/
// private boolean init = false;
private void initializeShiro() {
Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager); // sets a single shiro security manager to be used for entire jvm... fine for a stand-alone app but when running inside a web app container or in a multi-user env. it needs to be maintained by some container and set on every thread that will do work ...
}
@BeforeClass
public static void startup() {
log.debug("BeforeClass startup, is there an instance set? {}", launcher);
}
// from junit @BeforeClass, instantiate JunitMavenLauncher and call addModule() once for each module to add before calling startup()
@Before
public void startupInstance() throws IOException {
if( launcher == null ) {
log.debug("Before first test, initializing instance");
// init = true;
launcher = this;
initializeEnvironment();
loadModules();
registerExtensions();
// initialize();
initializeShiro(); // XXX TODO this is here only for junit testing as a custom method to initialize using shiro.ini ; in production this needs to be a plugin, maybe mtwilson-shiro-jdbc which relies on My.configuration() to get the encrypted/integrity-protected shiro.ini from the database and uses that...
// configure();
// validate();
// start();
}
}
@AfterClass
public static void shutdown() {
log.debug("AfterClass shutdown, stopping the current instance");
launcher.stop();
launcher = null;
}
}