001: package org.tanukisoftware.wrapper;
002:
003: /*
004: * Copyright (c) 1999, 2006 Tanuki Software Inc.
005: *
006: * Permission is hereby granted, free of charge, to any person
007: * obtaining a copy of the Java Service Wrapper and associated
008: * documentation files (the "Software"), to deal in the Software
009: * without restriction, including without limitation the rights
010: * to use, copy, modify, merge, publish, distribute, sub-license,
011: * and/or sell copies of the Software, and to permit persons to
012: * whom the Software is furnished to do so, subject to the
013: * following conditions:
014: *
015: * The above copyright notice and this permission notice shall be
016: * included in all copies or substantial portions of the Software.
017: *
018: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
019: * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
020: * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
021: * NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
022: * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
023: * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
024: * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
025: * OTHER DEALINGS IN THE SOFTWARE.
026: *
027: *
028: * Portions of the Software have been derived from source code
029: * developed by Silver Egg Technology under the following license:
030: *
031: * Copyright (c) 2001 Silver Egg Technology
032: *
033: * Permission is hereby granted, free of charge, to any person
034: * obtaining a copy of this software and associated documentation
035: * files (the "Software"), to deal in the Software without
036: * restriction, including without limitation the rights to use,
037: * copy, modify, merge, publish, distribute, sub-license, and/or
038: * sell copies of the Software, and to permit persons to whom the
039: * Software is furnished to do so, subject to the following
040: * conditions:
041: *
042: * The above copyright notice and this permission notice shall be
043: * included in all copies or substantial portions of the Software.
044: */
045:
046: import java.lang.reflect.InvocationTargetException;
047: import java.lang.reflect.Method;
048: import java.lang.reflect.Modifier;
049:
050: /**
051: * By default the WrapperSimpleApp will only wait for 2 seconds for the main
052: * method of the start class to complete. This was done because the main
053: * methods of many applications never return. It is possible to force the
054: * class to wait for the startup main method to complete by defining the
055: * following system property when launching the JVM (defaults to FALSE):
056: * -Dorg.tanukisoftware.wrapper.WrapperSimpleApp.waitForStartMain=TRUE
057: * <p>
058: * Using the waitForStartMain property will cause the startup to wait
059: * indefinitely. This is fine if the main method will always return
060: * within a predefined period of time. But if there is any chance that
061: * it could hang, then the maxStartMainWait property may be a better
062: * option. It allows the 2 second wait time to be overridden. To wait
063: * for up to 5 minutes for the startup main method to complete, set
064: * the property to 300 as follows (defaults to 2 seconds):
065: * -Dorg.tanukisoftware.wrapper.WrapperSimpleApp.maxStartMainWait=300
066: * <p>
067: * It is possible to extend this class but make absolutely sure that any
068: * overridden methods call their super method or the class will fail to
069: * function correctly. Most users will have no need to override this
070: * class.
071: * <p>
072: * NOTE - The main methods of many applications are designed not to
073: * return. In these cases, you must either stick with the default 2 second
074: * startup timeout or specify a slightly longer timeout, using the
075: * maxStartMainWait property, to simulate the amount of time your application
076: * takes to start up.
077: * <p>
078: * WARNING - If the waitForStartMain is specified for an application
079: * whose start method never returns, the Wrapper will appear at first to be
080: * functioning correctly. However the Wrapper will never enter a running
081: * state, this means that the Windows Service Manager and several of the
082: * Wrapper's error recovery mechanisms will not function correctly.
083: *
084: * @author Leif Mortenson <leif@tanukisoftware.com>
085: */
086: public class WrapperSimpleApp implements WrapperListener, Runnable {
087: /**
088: * Application's main method
089: */
090: private Method m_mainMethod;
091:
092: /**
093: * Command line arguments to be passed on to the application
094: */
095: private String[] m_appArgs;
096:
097: /**
098: * Gets set to true when the thread used to launch the application
099: * actuially starts.
100: */
101: private boolean m_mainStarted;
102:
103: /**
104: * Gets set to true when the thread used to launch the application
105: * completes.
106: */
107: private boolean m_mainComplete;
108:
109: /**
110: * Exit code to be returned if the application fails to start.
111: */
112: private Integer m_mainExitCode;
113:
114: /**
115: * Flag used to signify that the start method has completed.
116: */
117: private boolean m_startComplete;
118:
119: /*---------------------------------------------------------------
120: * Constructors
121: *-------------------------------------------------------------*/
122: /**
123: * Creates an instance of a WrapperSimpleApp.
124: *
125: * @param The full list of arguments passed to the JVM.
126: */
127: protected WrapperSimpleApp(String args[]) {
128:
129: // Initialize the WrapperManager class on startup by referencing it.
130: Class wmClass = WrapperManager.class;
131:
132: // Get the class name of the application
133: if (args.length < 1) {
134: showUsage();
135: WrapperManager.stop(1);
136: return; // Will not get here
137: }
138:
139: // Look for the specified class by name
140: Class mainClass;
141: try {
142: mainClass = Class.forName(args[0]);
143: } catch (ClassNotFoundException e) {
144: System.out
145: .println("WrapperSimpleApp: Unable to locate the class "
146: + args[0] + ": " + e);
147: showUsage();
148: WrapperManager.stop(1);
149: return; // Will not get here
150: } catch (LinkageError e) {
151: System.out
152: .println("WrapperSimpleApp: Unable to locate the class "
153: + args[0] + ": " + e);
154: showUsage();
155: WrapperManager.stop(1);
156: return; // Will not get here
157: }
158:
159: // Look for the main method
160: try {
161: // getDeclaredMethod will return any method named main in the specified class,
162: // while getMethod will only return public methods, but it will search up the
163: // inheritance path.
164: m_mainMethod = mainClass.getMethod("main",
165: new Class[] { String[].class });
166: } catch (NoSuchMethodException e) {
167: System.out
168: .println("WrapperSimpleApp: Unable to locate a public static main method in class "
169: + args[0] + ": " + e);
170: showUsage();
171: WrapperManager.stop(1);
172: return; // Will not get here
173: } catch (SecurityException e) {
174: System.out
175: .println("WrapperSimpleApp: Unable to locate a public static main method in class "
176: + args[0] + ": " + e);
177: showUsage();
178: WrapperManager.stop(1);
179: return; // Will not get here
180: }
181:
182: // Make sure that the method is public and static
183: int modifiers = m_mainMethod.getModifiers();
184: if (!(Modifier.isPublic(modifiers) && Modifier
185: .isStatic(modifiers))) {
186: System.out
187: .println("WrapperSimpleApp: The main method in class "
188: + args[0]
189: + " must be declared public and static.");
190: showUsage();
191: WrapperManager.stop(1);
192: return; // Will not get here
193: }
194:
195: // Build the application args array
196: String[] appArgs = new String[args.length - 1];
197: System.arraycopy(args, 1, appArgs, 0, appArgs.length);
198:
199: // Start the application. If the JVM was launched from the native
200: // Wrapper then the application will wait for the native Wrapper to
201: // call the application's start method. Otherwise the start method
202: // will be called immediately.
203: WrapperManager.start(this , appArgs);
204:
205: // This thread ends, the WrapperManager will start the application after the Wrapper has
206: // been properly initialized by calling the start method above.
207: }
208:
209: /*---------------------------------------------------------------
210: * Runnable Methods
211: *-------------------------------------------------------------*/
212: /**
213: * Used to launch the application in a separate thread.
214: */
215: public void run() {
216: // Notify the start method that the thread has been started by the JVM.
217: synchronized (this ) {
218: m_mainStarted = true;
219: notifyAll();
220: }
221:
222: Throwable t = null;
223: try {
224: if (WrapperManager.isDebugEnabled()) {
225: System.out
226: .println("WrapperSimpleApp: invoking main method");
227: }
228: m_mainMethod.invoke(null, new Object[] { m_appArgs });
229: if (WrapperManager.isDebugEnabled()) {
230: System.out
231: .println("WrapperSimpleApp: main method completed");
232: }
233:
234: synchronized (this ) {
235: // Let the start() method know that the main method returned, in case it is
236: // still waiting.
237: m_mainComplete = true;
238: this .notifyAll();
239: }
240:
241: return;
242: } catch (IllegalAccessException e) {
243: t = e;
244: } catch (IllegalArgumentException e) {
245: t = e;
246: } catch (InvocationTargetException e) {
247: t = e.getTargetException();
248: if (t == null) {
249: t = e;
250: }
251: }
252:
253: // If we get here, then an error was thrown. If this happened quickly
254: // enough, the start method should be allowed to shut things down.
255: System.out.println();
256: System.out
257: .println("WrapperSimpleApp: Encountered an error running main: "
258: + t);
259:
260: // We should print a stack trace here, because in the case of an
261: // InvocationTargetException, the user needs to know what exception
262: // their app threw.
263: t.printStackTrace();
264:
265: synchronized (this ) {
266: if (m_startComplete) {
267: // Shut down here.
268: WrapperManager.stop(1);
269: return; // Will not get here.
270: } else {
271: // Let start method handle shutdown.
272: m_mainComplete = true;
273: m_mainExitCode = new Integer(1);
274: this .notifyAll();
275: return;
276: }
277: }
278: }
279:
280: /*---------------------------------------------------------------
281: * WrapperListener Methods
282: *-------------------------------------------------------------*/
283: /**
284: * The start method is called when the WrapperManager is signalled by the
285: * native wrapper code that it can start its application. This
286: * method call is expected to return, so a new thread should be launched
287: * if necessary.
288: * If there are any problems, then an Integer should be returned, set to
289: * the desired exit code. If the application should continue,
290: * return null.
291: */
292: public Integer start(String[] args) {
293: // Decide whether or not to wait for the start main method to complete before returning.
294: boolean waitForStartMain = WrapperSystemPropertyUtil
295: .getBooleanProperty(WrapperSimpleApp.class.getName()
296: + ".waitForStartMain", false);
297: int maxStartMainWait = WrapperSystemPropertyUtil
298: .getIntProperty(WrapperSimpleApp.class.getName()
299: + ".maxStartMainWait", 2);
300: maxStartMainWait = Math.max(1, maxStartMainWait);
301:
302: // Decide the maximum number of times to loop waiting for the main start method.
303: int maxLoops;
304: if (waitForStartMain) {
305: maxLoops = Integer.MAX_VALUE;
306: if (WrapperManager.isDebugEnabled()) {
307: System.out
308: .println("WrapperSimpleApp: start(args) Will wait indefinitely "
309: + "for the main method to complete.");
310: }
311: } else {
312: maxLoops = maxStartMainWait; // 1s loops.
313: if (WrapperManager.isDebugEnabled()) {
314: System.out
315: .println("WrapperSimpleApp: start(args) Will wait up to "
316: + maxLoops
317: + " seconds for the main method to complete.");
318: }
319: }
320:
321: Thread mainThread = new Thread(this , "WrapperSimpleAppMain");
322: synchronized (this ) {
323: m_appArgs = args;
324: mainThread.start();
325:
326: // To avoid problems with the main thread starting slowly on heavily loaded systems,
327: // do not continue until the thread has actually started.
328: while (!m_mainStarted) {
329: try {
330: this .wait(1000);
331: } catch (InterruptedException e) {
332: // Continue.
333: }
334: }
335:
336: // Wait for startup main method to complete.
337: int loops = 0;
338: while ((loops < maxLoops) && (!m_mainComplete)) {
339: try {
340: this .wait(1000);
341: } catch (InterruptedException e) {
342: // Continue.
343: }
344:
345: if (!m_mainComplete) {
346: // If maxLoops is large then this could take a while. Notify the
347: // WrapperManager that we are still starting so it doesn't give up.
348: WrapperManager.signalStarting(5000);
349: }
350:
351: loops++;
352: }
353:
354: // Always set the flag stating that the start method completed. This is needed
355: // so the run method can decide whether or not it needs to be responsible for
356: // shutting down the JVM in the event of an exception thrown by the start main
357: // method.
358: m_startComplete = true;
359:
360: // The main exit code will be null unless an error was thrown by the start
361: // main method.
362: if (WrapperManager.isDebugEnabled()) {
363: System.out
364: .println("WrapperSimpleApp: start(args) end. Main Completed="
365: + m_mainComplete
366: + ", exitCode="
367: + m_mainExitCode);
368: }
369: return m_mainExitCode;
370: }
371: }
372:
373: /**
374: * Called when the application is shutting down.
375: */
376: public int stop(int exitCode) {
377: if (WrapperManager.isDebugEnabled()) {
378: System.out.println("WrapperSimpleApp: stop(" + exitCode
379: + ")");
380: }
381:
382: // Normally an application will be asked to shutdown here. Standard Java applications do
383: // not have shutdown hooks, so do nothing here. It will be as if the user hit CTRL-C to
384: // kill the application.
385: return exitCode;
386: }
387:
388: /**
389: * Called whenever the native wrapper code traps a system control signal
390: * against the Java process. It is up to the callback to take any actions
391: * necessary. Possible values are: WrapperManager.WRAPPER_CTRL_C_EVENT,
392: * WRAPPER_CTRL_CLOSE_EVENT, WRAPPER_CTRL_LOGOFF_EVENT, or
393: * WRAPPER_CTRL_SHUTDOWN_EVENT
394: */
395: public void controlEvent(int event) {
396: if ((event == WrapperManager.WRAPPER_CTRL_LOGOFF_EVENT)
397: && WrapperManager.isLaunchedAsService()) {
398: // Ignore
399: if (WrapperManager.isDebugEnabled()) {
400: System.out.println("WrapperSimpleApp: controlEvent("
401: + event + ") Ignored");
402: }
403: } else {
404: if (WrapperManager.isDebugEnabled()) {
405: System.out.println("WrapperSimpleApp: controlEvent("
406: + event + ") Stopping");
407: }
408: WrapperManager.stop(0);
409: // Will not get here.
410: }
411: }
412:
413: /*---------------------------------------------------------------
414: * Methods
415: *-------------------------------------------------------------*/
416: /**
417: * Displays application usage
418: */
419: protected void showUsage() {
420: System.out.println();
421: System.out.println("WrapperSimpleApp Usage:");
422: System.out
423: .println(" java org.tanukisoftware.wrapper.WrapperSimpleApp {app_class} [app_arguments]");
424: System.out.println();
425: System.out.println("Where:");
426: System.out
427: .println(" app_class: The fully qualified class name of the application to run.");
428: System.out
429: .println(" app_arguments: The arguments that would normally be passed to the");
430: System.out.println(" application.");
431: }
432:
433: /*---------------------------------------------------------------
434: * Main Method
435: *-------------------------------------------------------------*/
436: /**
437: * Used to Wrapper enable a standard Java application. This main
438: * expects the first argument to be the class name of the application
439: * to launch. All remaining arguments will be wrapped into a new
440: * argument list and passed to the main method of the specified
441: * application.
442: */
443: public static void main(String args[]) {
444: new WrapperSimpleApp(args);
445: }
446: }
|