/*
* Copyright (C) 2013 Intel Corporation
* All rights reserved.
*/
package com.intel.mtwilson.launcher;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.io.IOUtils;
import com.intel.dcsg.cpg.performance.AlarmClock;
import com.intel.dcsg.cpg.util.ArrayIterator;
import com.intel.dcsg.cpg.module.Container;
import com.intel.dcsg.cpg.classpath.MavenResolver;
import com.intel.dcsg.cpg.module.Module;
import com.intel.dcsg.cpg.module.ModuleRepository;
import com.intel.dcsg.cpg.module.ModuleUtil;
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.MultiJarFileClassLoader;
import com.intel.dcsg.cpg.module.ContainerException;
import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.jar.Manifest;
/**
* Given some names of artifacts in the local maven repository, this launcher starts a Container and activates the
* dependencies directly from the maven repository without copying them to any temporary directory.
*
* This launcher is more convenient to use in junit tests together with netbeans and maven because each test can define
* exactly which modules should be involved. Unlike the DirectoryLauncher, this launcher does not automatically scan any
* location to detect modules because it's assumed that the repository is large and that not all available modules
* should be started (this assumption makes it easier to create focused junit tests)
*
* It helps to define modules as groupId:artifactId:version when using this launcher, instead of artifactId-version.jar
* as with the DirectoryLauncher.
*
* Example of launching a container with 2 modules: MavenLauncher launcher = new MavenLauncher();
* launcher.getModuleNames().add("com.intel.mtwilson.plugins:mtwilson-version:1.2-SNAPSHOT");
* launcher.getModuleNames().add("com.intel.mtwilson.plugins:mtwilson-status:1.2-SNAPSHOT"); launcher.launch();
*
*
* System properties: mtwilson.jmod.dir=/path/to/jar/directory (needed to load modules from a directory)
* localRepository=~/.m2/repository (only needed if you use the MavenResolver)
*
* @author jbuhacoff
*/
public class MavenLauncher {
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MavenLauncher.class);
private final MavenResolver resolver = new MavenResolver();
private final Properties configuration;
private final HashSet<String> moduleNames = new HashSet<String>();
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 MavenLauncher() {
this(new Properties());
}
public MavenLauncher(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;
}
public Container getContainer() {
return container;
}
public void setContainer(Container container) {
this.container = container;
}
public void init() {
// 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();
}
}
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;
}
/**
* Initialize everything but do NOT start the event loop (caller must start it and stop it as needed)
*/
public void launch() throws IOException, ContainerException {
init();
loadModules();
// add a shutdown hook so we can automatically shut down the container if the VM is exiting
addShutdownHook();
// now start all modules we loaded and registered with the container
container.start();
// now list the registered modules
log.debug("There are {} registered modules", container.getModules().size());
for (Module module : container.getModules()) {
log.debug("Module: {};active={}", module.getImplementationTitle() + "-" + module.getImplementationVersion(), (module.isActive() ? "yes" : "no"));
}
}
public void loadModules() throws IOException {
for (String moduleName : moduleNames) {
File moduleJarFile = locateModuleJarFile(moduleName);
if (ModuleUtil.isModule(moduleJarFile)) {
Manifest manifest = JarUtil.readManifest(moduleJarFile);
// Set<File> classpath = resolver.resolveClasspath(module.getManifest());
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 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);
} else {
log.warn("Module {} is missing {} jars from classpath", module.getImplementationTitle(), missingArtifacts.size());
}
}
}
log.debug("Found {} modules", container.getModules().size());
}
/**
* Note: this method never returns! Call stopEventLoop() from another thread to terminate.
*/
public void startEventLoop() {
AlarmClock alarm = new AlarmClock(1, TimeUnit.SECONDS);
while (continueEventLoop) {
try {
alarm.sleep();
} catch (Exception e) {
log.trace("Interrupted sleep", e);
}
}
}
public void stopEventLoop() {
continueEventLoop = false;
}
private void addShutdownHook() {
Runtime.getRuntime().addShutdownHook(new Thread("MtWilson Shutdown Hook") {
@Override
public void run() {
try {
if (container != null) {
log.debug("Waiting for modules to deactivate");
container.stop();
}
} catch (Exception ex) {
System.err.println("Error stopping container: " + ex);
}
}
});
}
/**
* When setting up a test environment you can write: launcher.module("groupId:artifactId:version") to add another
* module to the set that will be loaded.
*
* @param mavenGroupIdArtifactIdVersion
*/
public void module(String mavenGroupIdArtifactIdVersion) {
moduleNames.add(mavenGroupIdArtifactIdVersion);
}
/**
* When setting up a test environment you can write:
* launcher.module().groupId("groupId").artifactId("artifactId").version("version").add(); to add another module to
* the set that will be loaded.
*
* @param mavenGroupIdArtifactIdVersion
*/
public MavenArtifactBuilder module() {
return new MavenArtifactBuilder(this);
}
public static class MavenArtifactBuilder {
private MavenLauncher launcher;
private String groupId, artifactId, version;
protected MavenArtifactBuilder(MavenLauncher launcher) {
this.launcher = launcher;
}
public MavenArtifactBuilder groupId(String groupId) {
this.groupId = groupId;
return this;
}
public MavenArtifactBuilder artifactId(String artifactId) {
this.artifactId = artifactId;
return this;
}
public MavenArtifactBuilder version(String version) {
this.version = version;
return this;
}
public void add() {
launcher.module(groupId + ":" + artifactId + ":" + version);
}
}
}