Launcher.java
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.codehaus.plexus.classworlds.launcher;
/*
* Copyright 2001-2006 Codehaus Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import org.codehaus.plexus.classworlds.ClassWorld;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.codehaus.plexus.classworlds.realm.DuplicateRealmException;
import org.codehaus.plexus.classworlds.realm.NoSuchRealmException;
/**
* <p>Command-line invokable application launcher.</p>
*
* <p>This launcher class assists in the creation of classloaders and <code>ClassRealm</code>s
* from a configuration file and the launching of the application's <code>main</code>
* method from the correct class loaded through the correct classloader.</p>
*
* <p> The path to the configuration file is specified using the <code>classworlds.conf</code>
* system property, typically specified using the <code>-D</code> switch to
* <code>java</code>.</p>
*
* @author <a href="mailto:bob@eng.werken.com">bob mcwhirter</a>
*/
public class Launcher {
protected static final String CLASSWORLDS_CONF = "classworlds.conf";
protected static final String UBERJAR_CONF_DIR = "WORLDS-INF/conf/";
protected ClassLoader systemClassLoader;
protected String mainClassName;
protected String mainRealmName;
protected ClassWorld world;
private int exitCode = 0;
public Launcher() {
this.systemClassLoader = Thread.currentThread().getContextClassLoader();
}
public void setSystemClassLoader(ClassLoader loader) {
this.systemClassLoader = loader;
}
public ClassLoader getSystemClassLoader() {
return this.systemClassLoader;
}
public int getExitCode() {
return exitCode;
}
public void setAppMain(String mainClassName, String mainRealmName) {
this.mainClassName = mainClassName;
this.mainRealmName = mainRealmName;
}
public String getMainRealmName() {
return this.mainRealmName;
}
public String getMainClassName() {
return this.mainClassName;
}
public void setWorld(ClassWorld world) {
this.world = world;
}
public ClassWorld getWorld() {
return this.world;
}
/**
* Configure from a file.
*
* @param is The config input stream.
* @throws IOException If an error occurs reading the config file.
* @throws MalformedURLException If the config file contains invalid URLs.
* @throws ConfigurationException If the config file is corrupt.
* @throws DuplicateRealmException If the config file defines two realms
* with the same id.
* @throws NoSuchRealmException If the config file defines a main entry
* point in a non-existent realm.
*/
public void configure(InputStream is)
throws IOException, ConfigurationException, DuplicateRealmException, NoSuchRealmException {
Configurator configurator = new Configurator(this);
configurator.configure(is);
}
/**
* Retrieve the main entry class.
*
* @return The main entry class.
* @throws ClassNotFoundException If the class cannot be found.
* @throws NoSuchRealmException If the specified main entry realm does not exist.
*/
public Class<?> getMainClass() throws ClassNotFoundException, NoSuchRealmException {
return getMainRealm().loadClass(getMainClassName());
}
/**
* Retrieve the main entry realm.
*
* @return The main entry realm.
* @throws NoSuchRealmException If the specified main entry realm does not exist.
*/
public ClassRealm getMainRealm() throws NoSuchRealmException {
return getWorld().getRealm(getMainRealmName());
}
/**
* Retrieve the enhanced main entry method.
*
* @return The enhanced main entry method.
* @throws ClassNotFoundException If the main entry class cannot be found.
* @throws NoSuchMethodException If the main entry method cannot be found.
* @throws NoSuchRealmException If the main entry realm cannot be found.
*/
protected Method getEnhancedMainMethod()
throws ClassNotFoundException, NoSuchMethodException, NoSuchRealmException {
Class<?> cwClass = getMainRealm().loadClass(ClassWorld.class.getName());
Method m = getMainClass().getMethod("main", String[].class, cwClass);
int modifiers = m.getModifiers();
if (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers)) {
if (m.getReturnType() == Integer.TYPE || m.getReturnType() == Void.TYPE) {
return m;
}
}
throw new NoSuchMethodException("public static void main(String[] args, ClassWorld world)");
}
/**
* Retrieve the main entry method.
*
* @return The main entry method.
* @throws ClassNotFoundException If the main entry class cannot be found.
* @throws NoSuchMethodException If the main entry method cannot be found.
* @throws NoSuchRealmException If the main entry realm cannot be found.
*/
protected Method getMainMethod() throws ClassNotFoundException, NoSuchMethodException, NoSuchRealmException {
Method m = getMainClass().getMethod("main", String[].class);
int modifiers = m.getModifiers();
if (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers)) {
if (m.getReturnType() == Integer.TYPE || m.getReturnType() == Void.TYPE) {
return m;
}
}
throw new NoSuchMethodException("public static void main(String[] args) in " + getMainClass());
}
/**
* Launch the application.
*
* @param args The application args.
* @throws ClassNotFoundException If the main entry class cannot be found.
* @throws IllegalAccessException If the method cannot be accessed.
* @throws InvocationTargetException If the target of the invokation is invalid.
* @throws NoSuchMethodException If the main entry method cannot be found.
* @throws NoSuchRealmException If the main entry realm cannot be found.
*/
public void launch(String[] args)
throws ClassNotFoundException, IllegalAccessException, InvocationTargetException, NoSuchMethodException,
NoSuchRealmException {
try {
launchEnhanced(args);
return;
} catch (NoSuchMethodException e) {
// ignore
}
launchStandard(args);
}
/**
* <p>Attempt to launch the application through the enhanced main method.</p>
*
* <p>This will seek a method with the exact signature of:</p>
* <pre>
* public static void main(String[] args, ClassWorld world)
* </pre>
*
* @param args The application args.
* @throws ClassNotFoundException If the main entry class cannot be found.
* @throws IllegalAccessException If the method cannot be accessed.
* @throws InvocationTargetException If the target of the invokation is
* invalid.
* @throws NoSuchMethodException If the main entry method cannot be found.
* @throws NoSuchRealmException If the main entry realm cannot be found.
*/
protected void launchEnhanced(String[] args)
throws ClassNotFoundException, IllegalAccessException, InvocationTargetException, NoSuchMethodException,
NoSuchRealmException {
ClassRealm mainRealm = getMainRealm();
Class<?> mainClass = getMainClass();
Method mainMethod = getEnhancedMainMethod();
ClassLoader cl = mainRealm;
// ----------------------------------------------------------------------
// This is what the classloader for the main realm looks like when we
// boot from the command line:
// ----------------------------------------------------------------------
// [ AppLauncher$AppClassLoader ] : $CLASSPATH envar
// ^
// |
// |
// [ AppLauncher$ExtClassLoader ] : ${java.home}/jre/lib/ext/*.jar
// ^
// |
// |
// [ Strategy ]
// ----------------------------------------------------------------------
Thread.currentThread().setContextClassLoader(cl);
Object ret = mainMethod.invoke(mainClass, args, getWorld());
if (ret instanceof Integer) {
exitCode = (Integer) ret;
}
Thread.currentThread().setContextClassLoader(systemClassLoader);
}
/**
* <p>Attempt to launch the application through the standard main method.</p>
*
* <p>This will seek a method with the exact signature of:</p>
*
* <pre>
* public static void main(String[] args)
* </pre>
*
* @param args The application args.
* @throws ClassNotFoundException If the main entry class cannot be found.
* @throws IllegalAccessException If the method cannot be accessed.
* @throws InvocationTargetException If the target of the invokation is
* invalid.
* @throws NoSuchMethodException If the main entry method cannot be found.
* @throws NoSuchRealmException If the main entry realm cannot be found.
*/
protected void launchStandard(String[] args)
throws ClassNotFoundException, IllegalAccessException, InvocationTargetException, NoSuchMethodException,
NoSuchRealmException {
ClassRealm mainRealm = getMainRealm();
Class<?> mainClass = getMainClass();
Method mainMethod = getMainMethod();
Thread.currentThread().setContextClassLoader(mainRealm);
Object ret = mainMethod.invoke(mainClass, new Object[] {args});
if (ret instanceof Integer) {
exitCode = (Integer) ret;
}
Thread.currentThread().setContextClassLoader(systemClassLoader);
}
// ------------------------------------------------------------
// Class methods
// ------------------------------------------------------------
/**
* Launch the launcher from the command line.
* Will exit using System.exit with an exit code of 0 for success, 100 if there was an unknown exception,
* or some other code for an application error.
*
* @param args The application command-line arguments.
*/
public static void main(String[] args) {
try {
int exitCode = mainWithExitCode(args);
System.exit(exitCode);
} catch (Exception e) {
e.printStackTrace();
System.exit(100);
}
}
/**
* Launch the launcher.
*
* @param args The application command-line arguments.
* @return an integer exit code
* @throws Exception If an error occurs.
*/
public static int mainWithExitCode(String[] args) throws Exception {
String classworldsConf = System.getProperty(CLASSWORLDS_CONF);
InputStream is;
Launcher launcher = new Launcher();
ClassLoader cl = Thread.currentThread().getContextClassLoader();
launcher.setSystemClassLoader(cl);
if (classworldsConf != null) {
is = Files.newInputStream(Paths.get(classworldsConf));
} else {
if ("true".equals(System.getProperty("classworlds.bootstrapped"))) {
is = cl.getResourceAsStream(UBERJAR_CONF_DIR + CLASSWORLDS_CONF);
} else {
is = cl.getResourceAsStream(CLASSWORLDS_CONF);
}
}
if (is == null) {
throw new Exception("classworlds configuration not specified nor found in the classpath");
}
launcher.configure(is);
is.close();
try {
launcher.launch(args);
} catch (InvocationTargetException e) {
ClassRealm realm = launcher.getWorld().getRealm(launcher.getMainRealmName());
URL[] constituents = realm.getURLs();
System.out.println("---------------------------------------------------");
for (int i = 0; i < constituents.length; i++) {
System.out.println("constituent[" + i + "]: " + constituents[i]);
}
System.out.println("---------------------------------------------------");
// Decode ITE (if we can)
Throwable t = e.getTargetException();
if (t instanceof Exception) {
throw (Exception) t;
}
if (t instanceof Error) {
throw (Error) t;
}
// Else just toss the ITE
throw e;
}
return launcher.getExitCode();
}
}