/* * The contents of this file are subject to the Mozilla Public License * Version 1.1 (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.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * The Original Code is the Kowari Metadata Store. * * The Initial Developer of the Original Code is Plugged In Software Pty * Ltd (http://www.pisoftware.com, mailto:info@pisoftware.com). Portions * created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002 * Plugged In Software Pty Ltd. All Rights Reserved. * * Contributor(s): Duraspace. * * [NOTE: The text of this Exhibit A may differ slightly from the text * of the notices in the Source Code files of the Original Code. You * should use the text of this Exhibit A rather than the text found in the * Original Code Source Code for Your Modifications.] * */ package org.mulgara.util; // Java 2 standard packages import java.io.*; import java.lang.reflect.*; import java.net.*; import java.util.*; import java.util.jar.*; /** * Bootstraps the execution of the executable JAR by including embedded JAR * files in the classpath. <P/> * * This class is only meant to be used from within the executable JAR. It will * throw an error if you try to execute it from outside a JAR file. <P/> * * To use this class, add the following to the attributes to the manifest of * your enclosing JAR: <PRE> * Main-Class: org.mulgara.Bootstrap * Embedded-Jar: jar1filename.jar, jar2filename.jar,jar3filename.jar * Embedded-Main-Class: com.foo.Bar * </PRE> The <CODE>Main-Class</CODE> attribute tells the JVM which class to * execute (this class) when run as <KBD>java -jar <jar-file></KBD> . The * <CODE>Embedded-Jar</CODE> attribute specifies the names of the embedded JAR * files to be included in the classpath. The <CODE>Embedded-Main-Class</CODE> * attribute tells the bootstrap loader which class to execute once the JARs * have beed added to the classpath (ie. this is the actual class you want to * run). <P/> * * Consult the JAR <A href="http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html"> * JAR file specification</A> for more details on manifest attributes. <P/> * * At the moment, this class extracts embedded JAR files to the temp directory * (defined by the system property <CODE>java.io.tmpdir</CODE>) using * java.io.File.createTempFile() and adds them to the classpath. A better way to * implement this would be to write a real classloader. * * @created 2000-08-09 * * @author Tom Adams * * @version $Revision: 1.9 $ * * @modified $Date: 2005/01/05 04:59:29 $ * * @company <A href="mailto:info@PIsoftware.com">Plugged In Software</A> * * @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a> */ public class Bootstrap extends URLClassLoader { // // Constants // /** * the key to retrieve an embedded jar file name * */ private final static String EMBEDDED_JAR_KEY = "Embedded-Jar"; /** * the key to retrieve the executable class * */ private final static String EMBEDDED_MAIN_CLASS_KEY = "Embedded-Main-Class"; /** * Used by the addToSystemClasspath hack */ private static final Class<?>[] parameters = new Class[]{URL.class}; /** * create a 100K temp buffer to store the JAR bytes in * */ //static private byte [] buf = new byte [10240000]; private static byte[] buf = new byte[102400]; // // Members // /** the array of JAR files to load */ @SuppressWarnings("unused") private URL[] jarURLs = null; // // Constructors // /** * Creates a new bootstrap class loader, and loads the <code>jarURLs</code>. * * @param jarURLs an array of URLs pointing to the jar files to be loaded */ public Bootstrap(URL[] jarURLs) { // call the super class constructor super(jarURLs, ClassLoader.getSystemClassLoader()); //try to expose The Server Jars to the System classpath try { addToSystemClasspath(jarURLs); } catch (Exception e) { new Exception("Failed to add Embedded Jars to the System Classpath", e).printStackTrace(); } // set the list of URLs this.jarURLs = jarURLs; } /** * Loads a comma separated list of embedded JAR files specified by an <CODE>Embedded-Jar</CODE> * manifest attribute, then executes a class specified by an <CODE>Embedded-Main-Class</CODE> * manifest attribute. * * @param args PARAMETER TO DO */ public static void main(String[] args) { // Here's how this works... We need to get at the manifest of the JAR this // class is contained within. To do this we need to know the name of a // resource within the JAR. // // The only resource that we *know* is in the JAR is this class. So we // get the resource associated with this class and get the JAR's manifest. // // From the manifest we read the attributes we care about, write the // embedded JARs to a temporary directory, load them, then execute the main // class. try { // retrieve the manifest attributes Attributes manifestAttr = retrieveManifestAttributes(); // throw and error if we couldn't get any manifest attributes if (manifestAttr == null) throw new Exception("No manifest attributes found for JAR"); // get the name of the embedded main class String embeddedMainClass = manifestAttr.getValue(EMBEDDED_MAIN_CLASS_KEY); // it's pointless to continue without a main class if (embeddedMainClass == null) { throw new Exception("No Embedded-Main-Class attribute in manifest"); } // Set the path of the jar as a System property - mulgara.jar.path URL bootURL = ClassLoader.getSystemResource("org/mulgara/util/Bootstrap.class"); String bootURLString = bootURL.toString(); String preString = "jar:file:"; int startIndex = preString.length(); int bangIndex = bootURLString.indexOf('!'); String jarPath = bootURLString.substring(startIndex, bangIndex); System.setProperty("mulgara.jar.path", jarPath); // ************************************************************************ // HACK: This is a hack to get the ARP parser working inside the bootstrapper. // It should be removed once we work out a real solution to the // dodgy error message problem. // set the xerces system property if we're executing the Mulgara server if (embeddedMainClass.equals("org.mulgara.server.EmbeddedMulgaraServer")) { System.setProperty("org.mulgara.xml.ResourceDocumentBuilderFactory", "org.apache.xerces.jaxp.DocumentBuilderFactoryImpl"); } // ************************************************************************ // get a list of URLs to the embedded JARs LinkedList<URL> jarURLs = getEmbeddedJarURLs(manifestAttr); // create a new bootstrap classloader Bootstrap loader = new Bootstrap((URL[])jarURLs.toArray(new URL[jarURLs.size()])); // set the context class loader to the bootstrap Thread.currentThread().setContextClassLoader(loader); // invokes the 'real' main class loader.invokeClass(embeddedMainClass, args); } catch (Exception e) { // print the contents of the exception System.err.println("Unable to bootstrap embedded main class: " + e.toString()); System.err.println(">> Stack trace:"); e.printStackTrace(); // get the underlying cause Throwable rootCause = null; Throwable cause = e.getCause(); while (cause != null) { rootCause = cause; cause = cause.getCause(); } // print a stack trace on it if (rootCause != null) { System.err.println(">> Root cause stack trace:"); System.err.flush(); rootCause.printStackTrace(); } } } /** * Shutdown the current application by forcing the runtime shutdown hooks to be executed. * * @param args command line arguments */ public static void shutdown(String[] args) { System.exit(0); } /** * Hack used to add the Embedded Jars to the system classpath. * * @param urls URLs of JARs to embed * @throws IOException Caused by any problem updating the system class path */ public static void addToSystemClasspath(URL[] urls) throws IOException { if (urls == null) throw new IllegalArgumentException("null 'urls' parameter."); //add each for (int i = 0; i < urls.length; i++) { addToSystemClasspath(urls[i]); } } /** * Adds the jar (url) to the system classpath. * * @param url URL to add to the system classpath. * @throws IOException Caused by any problem updating the system class path */ public static void addToSystemClasspath(URL url) throws IOException { URLClassLoader sysloader = (URLClassLoader)ClassLoader.getSystemClassLoader(); Class<URLClassLoader> sysclass = URLClassLoader.class; try { Method method = sysclass.getDeclaredMethod("addURL", parameters); method.setAccessible(true); method.invoke(sysloader, new Object[] {url}); } catch (Throwable t) { t.printStackTrace(); throw new IOException("Error, could not add URL to system classloader"); } } /** * Returns a list of URLs to the embedded jar files. * * @param manifestAttr the manifest attributes of the jar to retrieve the * embedded jar names from * @return a list of URLs to the embedded jar files, null if no embedded jars found */ private static LinkedList<URL> getEmbeddedJarURLs(Attributes manifestAttr) { // check the parameters if (manifestAttr == null) throw new IllegalArgumentException("Null manifest attribute"); // create a list to hold the JAR resources LinkedList<URL> jarURLs = new LinkedList<URL>(); // get the list of embedded jars String embeddedJarList = manifestAttr.getValue(EMBEDDED_JAR_KEY); if (embeddedJarList != null ) { // tokense the list of jar files StringTokenizer jarTokenizer = new StringTokenizer(embeddedJarList, " ,\t\f"); // add a URL for each embedded jar to the array while (jarTokenizer.hasMoreTokens()) { // write this JAR to a temp file and get its URL URL jarURL = writeTempJARFile(jarTokenizer.nextToken()); if (jarURL != null) jarURLs.add(jarURL); } } // we don't want the buffer hanging around buf = null; // return the URLs return jarURLs; } /** * Retrieves the manifest attributes of the JAR from which this file is running. * * @return the manifest attributes of the JAR from which this class is running * @throws Exception if unable to retrieve the JAR resource for a class */ private static Attributes retrieveManifestAttributes() throws Exception { // get the name of this class with all the "."s replaced with "/"s String className = Bootstrap.class.getName().replace('.', '/'); // create a new URL pointing to the resource representing this class URL classURL = ClassLoader.getSystemResource(className + ".class"); // throw an error if we could not get it if (classURL == null) throw new Exception("Unable to retrieve JAR resource for " + className); // end if // open a connection to the class resource URLConnection urlConn = classURL.openConnection(); // make sure that we're executing from within a JAR if ( (urlConn == null) || ! (urlConn instanceof JarURLConnection)) { throw new Exception("Bootstrap class must be executed from within a JAR"); } // return its manifest attributes return ((JarURLConnection)urlConn).getMainAttributes(); } /** * Writes an embedded JAR file to a temporary file, and returns a URL to the * temporary file. * * @param embeddedJARFilename the name of the embedded JAR file to retrieve * @return the URL of the temporary JAR file, null if we were unable to * retrieve a the embedded JAR or something went wrong writing a temp file */ private static URL writeTempJARFile(String embeddedJARFilename) { URL embeddedJarURL = null; try { // get the embedded jar as a stream InputStream jarIn = ClassLoader.getSystemResourceAsStream(embeddedJARFilename); // check that the embedded filename is valid if (jarIn == null) { throw new IOException("Embedded JAR: " + embeddedJARFilename + " does not exist in enclosing JAR."); } try { // create a temporary file to write the jar to (we may need to keep the // class on disk for windows weenies...) File tmpJarFile = File.createTempFile("mulgara", ".jar"); tmpJarFile.deleteOnExit(); // set the embedded JAR's URL embeddedJarURL = tmpJarFile.toURI().toURL(); // get a stream so we can write to it FileOutputStream out = new FileOutputStream(tmpJarFile); try { // write the embedded jar to disk int n; while ((n = jarIn.read(buf)) != -1) out.write(buf, 0, n); } finally { out.close(); } } finally { jarIn.close(); } } catch (IOException ioe) { System.err.println(ioe); } // return the temp file's URL return embeddedJarURL; } /** * Invokes the <CODE>main()</CODE> or or <CODE>shutdown()</CODE> method of the * class <CODE>className</CODE> with the given array of arguments. The class * must define a <CODE>public static void xxx()</CODE> that takes an array of * String. * * @param className the name of the class to execute * @param args the arguments for the class' main method * @throws ClassNotFoundException if the specified class could not be found * @throws NoSuchMethodException if the specified class does not contain a * "main" method * @throws InvocationTargetException if the application raised an exception * @throws IllegalAccessException if we do not have access to invoke the * method */ private void invokeClass(String className, String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { // by default the main method is requested String methodRequest = "main"; // check for a shutdown request for (int i = 0; i < args.length; i++) { if (args[i].equalsIgnoreCase("--shutdown") || args[i].equalsIgnoreCase("-x")) { // change the method to a shutdown methodRequest = "shutdown"; } } // load the class Class<?> c = this.loadClass(className); // get its main method Method m = c.getMethod(methodRequest, new Class[] {args.getClass()}); m.setAccessible(true); // retrieve its modifiers int mods = m.getModifiers(); // make sure that it is a public static void method if ((m.getReturnType() != void.class) || !Modifier.isStatic(mods) || !Modifier.isPublic(mods)) { throw new NoSuchMethodException(methodRequest); } // invoke it! m.invoke(null, new Object[] {args}); } }