/*
* JBoss, the OpenSource J2EE webOS
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jboss.tools;
import java.net.*;
import java.lang.reflect.*;
import java.io.*;
import java.util.*;
/**
* Starts multiple applications using seperate classloaders.
* This allows multiple applications to co-exist even if they typicaly could not due to
* class version problems. Each application is started in it's own thread.
*
* Usage is Boot [-debug] -cp app-classpath app-class-name app-arguments ( , -cp app-classpath app-class-name app-arguments )*
*
* Where:
* app-classpath is a comma seperated URL form classpath to the application classes.
* app-class-name is the class that will be started
* app-arguments will be the String[] that will be passed to the main method of the application class
*
* Jboss + Another Application boot example:
* Boot -cp file:run.jar org.jboss.Main default , -cp file:./myapp.jar,file:./util.jar test.App2TEST arg1 arg2
* Would start the JBoss Server using the default configuration and it would
* start the test.App2TEST application.
* Important Note: Notice that there are spaces before and after the ","!!!
*
* You can now boot other applications via ths Boot class from withing one of the applications
* that was booted by the Boot.
*
* Example usage:
*
* Boot b = Boot.getInstance();
* Boot.ApplicationBoot ab = b.createApplicationBoot();
* ab.applicationClass = "org.jboss.Main"
* ab.classpath.add(new URL("file:run.jar"));
* ab.args.add("default");
*
* // this would start the application in a new thread.
* b.startApplication( ab );
*
* // Would boot the appp in the current thread.
* ab.boot();
*
*
*
* @author Hiram Chirino
*/
public class Boot
{
/**
* Indicates whether this instance is running in debug mode.
*/
protected boolean verbose = false;
/**
* For each booted application, we will store a ApplicationBoot object in this linked list.
*/
protected LinkedList applicationBoots = new LinkedList();
static Boot instance;
ThreadGroup bootThreadGroup;
/**
* If boot is accessed via an API, force the use of the getInstance() method.
*/
protected Boot () {
instance = this;
bootThreadGroup = Thread.currentThread().getThreadGroup();
}
public static Boot getInstance() {
if( instance == null )
instance = new Boot();
return instance;
}
/**
* Represents an application that can be booted.
*/
public class ApplicationBoot implements Runnable
{
/** LinkedList of URL that will be used to load the application's classes and resources */
public LinkedList classpath = new LinkedList();
/** The applications class that will be executed. */
public String applicationClass;
/** The aruments that will appsed to the application. */
public LinkedList args = new LinkedList();
protected URLClassLoader classloader;
protected Thread bootThread;
/**
* This is what actually loads the application classes and
* invokes the main method. We send any unhandled exceptions to
* System.err
*/
public void run()
{
try
{
boot();
}
catch (Throwable e)
{
System.err.println("Exception durring " + applicationClass + " application run: ");
e.printStackTrace(System.err);
}
}
/**
* This is what actually loads the application classes and
* invokes the main method.
*/
public void boot()
throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException
{
verbose("Booting: "+applicationClass);
verbose("Classpath: "+classpath);
verbose("Arguments: "+args);
bootThread = Thread.currentThread();
URL urls[] = new URL[classpath.size()];
urls = (URL[]) classpath.toArray(urls);
String passThruArgs[] = new String[args.size()];
passThruArgs = (String[]) args.toArray(passThruArgs);
// Save the current loader so we can restore it.
ClassLoader oldCL = Thread.currentThread().getContextClassLoader();
try {
classloader = new URLClassLoader(urls); // The parent is the system CL
Thread.currentThread().setContextClassLoader(classloader);
Class appClass = classloader.loadClass(applicationClass);
Method mainMethod = appClass.getMethod("main", new Class[] { String[].class });
mainMethod.invoke(null, new Object[] { passThruArgs });
}
catch (InvocationTargetException e)
{
if (e.getTargetException() instanceof Error)
throw (Error) e.getTargetException();
else
throw e;
}
finally
{
// Restore the previous classloader ( in case we were called directly
// an not via a Thread.start() )
Thread.currentThread().setContextClassLoader(oldCL);
}
}
}
/**
* This can be used to boot another application
* From within another one.
*
* @returns ApplicationBoot data structure that must be configured before the application can be booted.
*/
public ApplicationBoot createApplicationBoot() {
return new ApplicationBoot();
}
/**
* Boots the application in a new threadgroup and thread.
*
* @param bootData the application to boot.
* @exception thrown if a problem occurs during launching
*/
synchronized public void startApplication(ApplicationBoot bootData) throws Exception
{
if( bootData == null )
throw new NullPointerException("Invalid argument: bootData argument was null");
applicationBoots.add(bootData);
ThreadGroup threads = new ThreadGroup(bootThreadGroup, bootData.applicationClass);
new Thread(threads, bootData, "main").start();
}
synchronized public ApplicationBoot[] getStartedApplications() {
ApplicationBoot rc[] = new ApplicationBoot[applicationBoots.size()];
return (ApplicationBoot[])applicationBoots.toArray(rc);
}
/** logs verbose message to the console */
protected void verbose(String msg) {
if( verbose )
System.out.println("[Boot] "+msg);
}
//////////////////////////////////////////////////////////////////////
//
// THE FOLLOWING SET OF FUNCTIONS ARE RELATED TO PROCESSING COMMAND LINE
// ARGUMENTS.
//
//////////////////////////////////////////////////////////////////////
protected static final String HELP = "-help";
protected static final String VERBOSE = "-verbose";
protected static final String BOOT_APP_SEPERATOR = System.getProperty("org.jboss.Boot.APP_SEPERATOR", ",");
protected static final String CP = "-cp";
protected static class InvalidCommandLineException extends Exception {
/**
* Constructor for InvalidCommandLineException.
* @param s
*/
public InvalidCommandLineException(String s)
{
super(s);
}
}
/**
* Main entry point when called from the command line
* @param args the command line arguments
*/
public static void main(String[] args)
{
Boot boot = Boot.getInstance();
// Put the args in a linked list since it easier to work with.
LinkedList llargs = new LinkedList();
for (int i = 0; i < args.length; i++)
llargs.add(args[i]);
try {
LinkedList ab = boot.processCommandLine(llargs);
Iterator i = ab.iterator();
while (i.hasNext())
{
ApplicationBoot bootData = (ApplicationBoot) i.next();
boot.startApplication(bootData);
}
} catch ( InvalidCommandLineException e ) {
System.err.println("Invalid Usage: "+e.getMessage());
System.err.println();
showUsage();
System.exit(1);
} catch ( Throwable e ) {
System.err.println("Failure occured while executing application: ");
e.printStackTrace(System.err);
System.exit(1);
}
}
/**
* This method is here so that if JBoss is running under
* Alexandria (An NT Service Installer), Alexandria can shutdown
* the system down correctly.
*/
public static void systemExit(String argv[])
{
System.exit(0);
}
/**
* Processes the Boot class's command line arguments
*
* @return a linked list with ApplicationBoot objects
* @param args the command line arguments
*/
protected LinkedList processCommandLine(LinkedList args) throws Exception
{
LinkedList rc = new LinkedList();
processBootOptions(args);
while (args.size() > 0)
{
ApplicationBoot d = processAppBootCommandLine(args);
if (d != null)
rc.add(d);
}
if (rc.size() == 0)
{
throw new InvalidCommandLineException("An application class name must be provided.");
}
return rc;
}
/**
* Processes to global options.
*
* @param args the command line arguments
*/
protected void processBootOptions(LinkedList args) throws Exception
{
Iterator i = args.iterator();
while (i.hasNext())
{
String arg = (String) i.next();
if (arg.equalsIgnoreCase(VERBOSE))
{
verbose = true;
i.remove();
continue;
}
if (arg.equalsIgnoreCase(HELP))
{
showUsage();
System.exit(0);
}
// Didn't recognize it a boot option, then we must have started the application
// boot options.
return;
}
}
protected static void showUsage()
{
String programName = System.getProperty("org.jboss.Boot.proces-name", "boot");
System.out.println("usage: " + programName + " [boot-options] [app-options] class [args..]");
System.out.println(" to execute a class");
System.out.println(" or " + programName + " [boot-options] [app-options] class-1 [args..] , ... , [app-options] class-n [args..]");
System.out.println(" to execute multiple classes");
System.out.println();
System.out.println("boot-options:");
System.out.println(" -help show this help message");
System.out.println(" -verbose display detail messages regarding the boot process.");
System.out.println("app-options:");
System.out.println(" -cp ");
System.out.println(" set search path for application classes and resources");
System.out.println();
}
/**
* Processes the command line argumenst for the next application on the command line.
*
* @param args the command line arguments
*/
protected ApplicationBoot processAppBootCommandLine(LinkedList args) throws Exception
{
ApplicationBoot rc = new ApplicationBoot();
Iterator i = args.iterator();
while (i.hasNext())
{
String arg = (String) i.next();
i.remove();
if (rc.applicationClass == null)
{
if (arg.equalsIgnoreCase(CP))
{
if (!i.hasNext())
throw new InvalidCommandLineException("Invalid option: classpath missing after the " + CP + " option.");
String cp = (String) i.next();
i.remove();
StringTokenizer st = new StringTokenizer(cp, ",", false);
while (st.hasMoreTokens())
{
String t = st.nextToken();
if (t.length() == 0)
continue;
try
{
URL u = new URL(t);
rc.classpath.add(u);
}
catch (MalformedURLException e)
{
throw new InvalidCommandLineException("Application classpath value was invalid: " + e.getMessage());
}
}
continue;
}
rc.applicationClass = arg;
continue;
}
else
{
if (arg.equalsIgnoreCase(BOOT_APP_SEPERATOR))
{
break;
}
rc.args.add(arg);
}
}
if (rc.applicationClass == null)
return null;
return rc;
}
}