package org.tanukisoftware.wrapper; /* * Copyright (c) 1999, 2009 Tanuki Software, Ltd. * http://www.tanukisoftware.com * All rights reserved. * * This software is the proprietary information of Tanuki Software. * You shall use it only in accordance with the terms of the * license agreement you entered into with Tanuki Software. * http://wrapper.tanukisoftware.org/doc/english/licenseOverview.html */ import java.io.File; import java.io.IOException; 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.net.URLClassLoader; import java.util.StringTokenizer; import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.jar.Manifest; /** * By default the WrapperJarApp will only wait for 2 seconds for the main * method of the start class to complete. This was done because the main * methods of many applications never return. It is possible to force the * class to wait for the startup main method to complete by defining the * following system property when launching the JVM (defaults to FALSE): * -Dorg.tanukisoftware.wrapper.WrapperJarApp.waitForStartMain=TRUE * <p> * Using the waitForStartMain property will cause the startup to wait * indefinitely. This is fine if the main method will always return * within a predefined period of time. But if there is any chance that * it could hang, then the maxStartMainWait property may be a better * option. It allows the 2 second wait time to be overridden. To wait * for up to 5 minutes for the startup main method to complete, set * the property to 300 as follows (defaults to 2 seconds): * -Dorg.tanukisoftware.wrapper.WrapperJarApp.maxStartMainWait=300 * <p> * It is possible to extend this class but make absolutely sure that any * overridden methods call their super method or the class will fail to * function correctly. Most users will have no need to override this * class. Remember that if overridden, the main method will also need to * be recreated in the child class to make sure that the correct instance * is created. * <p> * NOTE - The main methods of many applications are designed not to * return. In these cases, you must either stick with the default 2 second * startup timeout or specify a slightly longer timeout, using the * maxStartMainWait property, to simulate the amount of time your application * takes to start up. * <p> * WARNING - If the waitForStartMain is specified for an application * whose start method never returns, the Wrapper will appear at first to be * functioning correctly. However the Wrapper will never enter a running * state, this means that the Windows Service Manager and several of the * Wrapper's error recovery mechanisms will not function correctly. * * @author Leif Mortenson <leif@tanukisoftware.com> */ public class WrapperJarApp implements WrapperListener, Runnable { /** Info level log channel */ private static WrapperPrintStream m_outInfo; /** Error level log channel */ private static WrapperPrintStream m_outError; /** Debug level log channel */ private static WrapperPrintStream m_outDebug; /** * Application's main method */ private Method m_mainMethod; /** * Command line arguments to be passed on to the application */ private String[] m_appArgs; /** * Gets set to true when the thread used to launch the application * actuially starts. */ private boolean m_mainStarted; /** * Gets set to true when the thread used to launch the application * completes. */ private boolean m_mainComplete; /** * Exit code to be returned if the application fails to start. */ private Integer m_mainExitCode; /** * Flag used to signify that the start method has completed. */ private boolean m_startComplete; /*--------------------------------------------------------------- * Constructors *-------------------------------------------------------------*/ /** * Creates an instance of a WrapperJarApp. * * @param args The full list of arguments passed to the JVM. */ protected WrapperJarApp( String args[] ) { // Initialize the WrapperManager class on startup by referencing it. Class wmClass = WrapperManager.class; // Set up some log channels m_outInfo = new WrapperPrintStream( System.out, "WrapperJarApp: " ); m_outError = new WrapperPrintStream( System.out, "WrapperJarApp: " ); m_outDebug = new WrapperPrintStream( System.out, "WrapperJarApp Debug: " ); // Get the class name of the application if ( args.length < 1 ) { showUsage(); WrapperManager.stop( 1 ); return; // Will not get here } // Look for the specified jar file. File file = new File( args[0] ); if ( !file.exists() ) { m_outError.println( "Unable to locate the jar file " + args[0] ); showUsage(); WrapperManager.stop( 1 ); return; // Will not get here } File parent = file.getParentFile(); JarFile jarFile; try { jarFile = new JarFile( file ); } catch ( IOException e ) { m_outError.println( "Unable to open the jar file " + args[0] + " : " + e ); showUsage(); WrapperManager.stop( 1 ); return; // Will not get here } Manifest manifest; try { manifest = jarFile.getManifest(); } catch ( IOException e ) { m_outError.println( "Unable to access the jar's manifest file " + args[0] + " : " + e ); showUsage(); WrapperManager.stop( 1 ); return; // Will not get here } Attributes attributes = manifest.getMainAttributes(); String mainClassName = attributes.getValue( "Main-Class" ); String classPath = attributes.getValue( "Class-Path" ); if ( WrapperManager.isDebugEnabled() ) { m_outDebug.println( "Jar Main-Class: " + mainClassName ); } URL[] classURLs; if ( ( classPath != null ) && ( !classPath.equals( "" ) ) ) { if ( WrapperManager.isDebugEnabled() ) { m_outDebug.println( "Jar Class-Path: " + classPath ); } StringTokenizer st = new StringTokenizer( classPath, " \n\r" ); classURLs = new URL[st.countTokens() + 1]; // Store the main jar in the classpath. try { classURLs[0] = new URL( "file:" + file.getAbsolutePath() ); } catch ( MalformedURLException e ) { m_outError.println( "Unable to add jar to classpath: " + e ); showUsage(); WrapperManager.stop( 1 ); return; // Will not get here } if ( WrapperManager.isDebugEnabled() ) { m_outDebug.println( " Classpath[0]=" + classURLs[0] ); } // Add any other jars in the manifest classpath relative to the location of the main jar. for ( int i = 1; st.hasMoreTokens(); i++ ) { String classEntry = st.nextToken(); try { classURLs[i] = new URL( "file:" + new File( parent, classEntry).getAbsolutePath() ); } catch ( MalformedURLException e ) { m_outError.println( "Malformed classpath in the jar's manifest file " + args[0] + " : " + e ); showUsage(); WrapperManager.stop( 1 ); return; // Will not get here } if ( WrapperManager.isDebugEnabled() ) { m_outDebug.println( " Classpath[" + i + "]=" + classURLs[i] ); } } } else { if ( WrapperManager.isDebugEnabled() ) { m_outDebug.println( "Jar Class-Path: Not specified." ); } classURLs = new URL[1]; // Store the main jar in the classpath. try { classURLs[0] = new URL( "file:" + file.getAbsolutePath() ); } catch ( MalformedURLException e ) { m_outError.println( "Unable to add jar to classpath: " + e ); showUsage(); WrapperManager.stop( 1 ); return; // Will not get here } if ( WrapperManager.isDebugEnabled() ) { m_outDebug.println( " Classpath[0]=" + classURLs[0] ); } } URLClassLoader cl = URLClassLoader.newInstance( classURLs, this.getClass().getClassLoader() ); // Look for the specified class by name Class mainClass; try { mainClass = Class.forName( mainClassName, true, cl ); } catch ( ClassNotFoundException e ) { m_outError.println( "Unable to locate the class " + mainClassName + ": " + e ); showUsage(); WrapperManager.stop( 1 ); return; // Will not get here } catch ( ExceptionInInitializerError e ) { m_outError.println( "Class " + mainClassName + " found but could not be initialized due to:" ); e.printStackTrace( m_outError ); WrapperManager.stop( 1 ); return; // Will not get here } catch ( LinkageError e ) { m_outError.println( "Class " + mainClassName + " found but could not be initialized: " + e ); WrapperManager.stop( 1 ); return; // Will not get here } // Look for the main method try { // getDeclaredMethod will return any method named main in the specified class, // while getMethod will only return public methods, but it will search up the // inheritance path. m_mainMethod = mainClass.getMethod( "main", new Class[] { String[].class } ); } catch ( NoSuchMethodException e ) { m_outError.println( "Unable to locate a public static main method in class " + args[0] + ": " + e ); showUsage(); WrapperManager.stop( 1 ); return; // Will not get here } catch ( SecurityException e ) { m_outError.println( "Unable to locate a public static main method in class " + args[0] + ": " + e ); showUsage(); WrapperManager.stop( 1 ); return; // Will not get here } // Make sure that the method is public and static int modifiers = m_mainMethod.getModifiers(); if ( !( Modifier.isPublic( modifiers ) && Modifier.isStatic( modifiers ) ) ) { m_outError.println( "The main method in class " + args[0] + " must be declared public and static." ); showUsage(); WrapperManager.stop( 1 ); return; // Will not get here } // Build the application args array String[] appArgs = new String[args.length - 1]; System.arraycopy( args, 1, appArgs, 0, appArgs.length ); // Start the application. If the JVM was launched from the native // Wrapper then the application will wait for the native Wrapper to // call the application's start method. Otherwise the start method // will be called immediately. WrapperManager.start( this, appArgs ); // This thread ends, the WrapperManager will start the application after the Wrapper has // been properly initialized by calling the start method above. } /*--------------------------------------------------------------- * Runnable Methods *-------------------------------------------------------------*/ /** * Used to launch the application in a separate thread. */ public void run() { // Notify the start method that the thread has been started by the JVM. synchronized( this ) { m_mainStarted = true; notifyAll(); } Throwable t = null; try { if ( WrapperManager.isDebugEnabled() ) { m_outDebug.println( "invoking main method" ); } m_mainMethod.invoke( null, new Object[] { m_appArgs } ); if ( WrapperManager.isDebugEnabled() ) { m_outDebug.println( "main method completed" ); } synchronized(this) { // Let the start() method know that the main method returned, in case it is // still waiting. m_mainComplete = true; this.notifyAll(); } return; } catch ( IllegalAccessException e ) { t = e; } catch ( IllegalArgumentException e ) { t = e; } catch ( InvocationTargetException e ) { t = e.getTargetException(); if ( t == null ) { t = e; } } // If we get here, then an error was thrown. If this happened quickly // enough, the start method should be allowed to shut things down. m_outInfo.println(); m_outError.println( "Encountered an error running main:" ); // We should print a stack trace here, because in the case of an // InvocationTargetException, the user needs to know what exception // their app threw. t.printStackTrace( m_outError ); synchronized(this) { if ( m_startComplete ) { // Shut down here. WrapperManager.stop( 1 ); return; // Will not get here. } else { // Let start method handle shutdown. m_mainComplete = true; m_mainExitCode = new Integer( 1 ); this.notifyAll(); return; } } } /*--------------------------------------------------------------- * WrapperListener Methods *-------------------------------------------------------------*/ /** * The start method is called when the WrapperManager is signalled by the * native wrapper code that it can start its application. This * method call is expected to return, so a new thread should be launched * if necessary. * If there are any problems, then an Integer should be returned, set to * the desired exit code. If the application should continue, * return null. */ public Integer start( String[] args ) { // Decide whether or not to wait for the start main method to complete before returning. boolean waitForStartMain = WrapperSystemPropertyUtil.getBooleanProperty( WrapperJarApp.class.getName() + ".waitForStartMain", false ); int maxStartMainWait = WrapperSystemPropertyUtil.getIntProperty( WrapperJarApp.class.getName() + ".maxStartMainWait", 2 ); maxStartMainWait = Math.max( 1, maxStartMainWait ); // Decide the maximum number of times to loop waiting for the main start method. int maxLoops; if ( waitForStartMain ) { maxLoops = Integer.MAX_VALUE; if ( WrapperManager.isDebugEnabled() ) { m_outDebug.println( "start(args) Will wait indefinitely for the main method to complete." ); } } else { maxLoops = maxStartMainWait; // 1s loops. if ( WrapperManager.isDebugEnabled() ) { m_outDebug.println( "start(args) Will wait up to " + maxLoops + " seconds for the main method to complete." ); } } Thread mainThread = new Thread( this, "WrapperJarAppMain" ); synchronized(this) { m_appArgs = args; mainThread.start(); // To avoid problems with the main thread starting slowly on heavily loaded systems, // do not continue until the thread has actually started. while ( !m_mainStarted ) { try { this.wait( 1000 ); } catch ( InterruptedException e ) { // Continue. } } // Wait for startup main method to complete. int loops = 0; while ( ( loops < maxLoops ) && ( !m_mainComplete ) ) { try { this.wait( 1000 ); } catch ( InterruptedException e ) { // Continue. } if ( !m_mainComplete ) { // If maxLoops is large then this could take a while. Notify the // WrapperManager that we are still starting so it doesn't give up. WrapperManager.signalStarting( 5000 ); } loops++; } // Always set the flag stating that the start method completed. This is needed // so the run method can decide whether or not it needs to be responsible for // shutting down the JVM in the event of an exception thrown by the start main // method. m_startComplete = true; // The main exit code will be null unless an error was thrown by the start // main method. if ( WrapperManager.isDebugEnabled() ) { m_outDebug.println( "start(args) end. Main Completed=" + m_mainComplete + ", exitCode=" + m_mainExitCode ); } return m_mainExitCode; } } /** * Called when the application is shutting down. */ public int stop( int exitCode ) { if ( WrapperManager.isDebugEnabled() ) { m_outDebug.println( "stop(" + exitCode + ")" ); } // Normally an application will be asked to shutdown here. Standard Java applications do // not have shutdown hooks, so do nothing here. It will be as if the user hit CTRL-C to // kill the application. return exitCode; } /** * Called whenever the native wrapper code traps a system control signal * against the Java process. It is up to the callback to take any actions * necessary. Possible values are: WrapperManager.WRAPPER_CTRL_C_EVENT, * WRAPPER_CTRL_CLOSE_EVENT, WRAPPER_CTRL_LOGOFF_EVENT, or * WRAPPER_CTRL_SHUTDOWN_EVENT */ public void controlEvent( int event ) { if ( ( event == WrapperManager.WRAPPER_CTRL_LOGOFF_EVENT ) && ( WrapperManager.isLaunchedAsService() || WrapperManager.isIgnoreUserLogoffs() ) ) { // Ignore m_outInfo.println( "User logged out. Ignored." ); } else { if ( WrapperManager.isDebugEnabled() ) { m_outDebug.println( "controlEvent(" + event + ") Stopping" ); } WrapperManager.stop( 0 ); // Will not get here. } } /*--------------------------------------------------------------- * Methods *-------------------------------------------------------------*/ /** * Displays application usage */ protected void showUsage() { // Show this output without headers. System.out.println(); System.out.println( "WrapperJarApp Usage:" ); System.out.println( " java org.tanukisoftware.wrapper.WrapperJarApp {jar_file} [app_arguments]" ); System.out.println(); System.out.println( "Where:" ); System.out.println( " jar_file: The jar file to run." ); System.out.println( " app_arguments: The arguments that would normally be passed to the" ); System.out.println( " application." ); } /*--------------------------------------------------------------- * Main Method *-------------------------------------------------------------*/ /** * Used to Wrapper enable a standard Java application. This main * expects the first argument to be the class name of the application * to launch. All remaining arguments will be wrapped into a new * argument list and passed to the main method of the specified * application. * * @param args Arguments passed to the application. */ public static void main( String args[] ) { new WrapperJarApp( args ); } }