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 WrapperStartStopApp 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.WrapperStartStopApp.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.WrapperStartStopApp.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 WrapperStartStopApp implements WrapperListener, Runnable {
087: /**
088: * Application's start main method
089: */
090: private Method m_startMainMethod;
091:
092: /**
093: * Command line arguments to be passed on to the start main method
094: */
095: private String[] m_startMainArgs;
096:
097: /**
098: * Application's stop main method
099: */
100: private Method m_stopMainMethod;
101:
102: /**
103: * Should the stop process force the JVM to exit, or wait for all threads
104: * to die on their own.
105: */
106: private boolean m_stopWait;
107:
108: /**
109: * Command line arguments to be passed on to the stop main method
110: */
111: private String[] m_stopMainArgs;
112:
113: /**
114: * Gets set to true when the thread used to launch the application
115: * actuially starts.
116: */
117: private boolean m_mainStarted;
118:
119: /**
120: * Gets set to true when the thread used to launch the application
121: * completes.
122: */
123: private boolean m_mainComplete;
124:
125: /**
126: * Exit code to be returned if the application fails to start.
127: */
128: private Integer m_mainExitCode;
129:
130: /**
131: * Flag used to signify that the start method has completed.
132: */
133: private boolean m_startComplete;
134:
135: /*---------------------------------------------------------------
136: * Constructors
137: *-------------------------------------------------------------*/
138: protected WrapperStartStopApp(String args[]) {
139:
140: // Initialize the WrapperManager class on startup by referencing it.
141: Class wmClass = WrapperManager.class;
142:
143: // Get the class name of the application
144: if (args.length < 5) {
145: System.out
146: .println("WrapperStartStopApp: Not enough argments. Minimum 5 required.");
147: showUsage();
148: WrapperManager.stop(1);
149: return; // Will not get here
150: }
151:
152: // Look for the start main method.
153: m_startMainMethod = getMainMethod(args[0]);
154: // Get the start arguments
155: String[] startArgs = getArgs(args, 1);
156:
157: // Where do the stop arguments start
158: int stopArgBase = 2 + startArgs.length;
159: if (args.length < stopArgBase + 3) {
160: System.out
161: .println("WrapperStartStopApp: Not enough argments. Minimum 3 after start "
162: + "arguments.");
163: showUsage();
164: WrapperManager.stop(1);
165: return; // Will not get here
166: }
167: // Look for the stop main method.
168: m_stopMainMethod = getMainMethod(args[stopArgBase]);
169: // Get the stopWait flag
170: if (args[stopArgBase + 1].equalsIgnoreCase("true")) {
171: m_stopWait = true;
172: } else if (args[stopArgBase + 1].equalsIgnoreCase("false")) {
173: m_stopWait = false;
174: } else {
175: System.out
176: .println("WrapperStartStopApp: The stop_wait argument must be either true "
177: + "or false.");
178: showUsage();
179: WrapperManager.stop(1);
180: return; // Will not get here
181: }
182: // Get the start arguments
183: m_stopMainArgs = getArgs(args, stopArgBase + 2);
184:
185: // Start the application. If the JVM was launched from the native
186: // Wrapper then the application will wait for the native Wrapper to
187: // call the application's start method. Otherwise the start method
188: // will be called immediately.
189: WrapperManager.start(this , startArgs);
190:
191: // This thread ends, the WrapperManager will start the application after the Wrapper has
192: // been propperly initialized by calling the start method above.
193: }
194:
195: protected WrapperStartStopApp(Method startMainMethod,
196: Method stopMainMethod, boolean stopWait,
197: String[] stopMainArgs) {
198: m_startMainMethod = startMainMethod;
199: m_stopMainMethod = stopMainMethod;
200: m_stopWait = stopWait;
201: m_stopMainArgs = stopMainArgs;
202: }
203:
204: /*---------------------------------------------------------------
205: * Runnable Methods
206: *-------------------------------------------------------------*/
207: /**
208: * Used to launch the application in a separate thread.
209: */
210: public void run() {
211: // Notify the start method that the thread has been started by the JVM.
212: synchronized (this ) {
213: m_mainStarted = true;
214: notifyAll();
215: }
216:
217: Throwable t = null;
218: try {
219: if (WrapperManager.isDebugEnabled()) {
220: System.out
221: .println("WrapperStartStopApp: invoking start main method");
222: }
223: m_startMainMethod.invoke(null,
224: new Object[] { m_startMainArgs });
225: if (WrapperManager.isDebugEnabled()) {
226: System.out
227: .println("WrapperStartStopApp: start main method completed");
228: }
229:
230: synchronized (this ) {
231: // Let the start() method know that the main method returned, in case it is
232: // still waiting.
233: m_mainComplete = true;
234: this .notifyAll();
235: }
236:
237: return;
238: } catch (IllegalAccessException e) {
239: t = e;
240: } catch (IllegalArgumentException e) {
241: t = e;
242: } catch (InvocationTargetException e) {
243: t = e.getTargetException();
244: if (t == null) {
245: t = e;
246: }
247: }
248:
249: // If we get here, then an error was thrown. If this happened quickly
250: // enough, the start method should be allowed to shut things down.
251: System.out.println();
252: System.out
253: .println("WrapperStartStopApp: Encountered an error running start main: "
254: + t);
255:
256: // We should print a stack trace here, because in the case of an
257: // InvocationTargetException, the user needs to know what exception
258: // their app threw.
259: t.printStackTrace();
260:
261: synchronized (this ) {
262: if (m_startComplete) {
263: // Shut down here.
264: WrapperManager.stop(1);
265: return; // Will not get here.
266: } else {
267: // Let start method handle shutdown.
268: m_mainComplete = true;
269: m_mainExitCode = new Integer(1);
270: this .notifyAll();
271: return;
272: }
273: }
274: }
275:
276: /*---------------------------------------------------------------
277: * WrapperListener Methods
278: *-------------------------------------------------------------*/
279: /**
280: * The start method is called when the WrapperManager is signalled by the
281: * native wrapper code that it can start its application. This
282: * method call is expected to return, so a new thread should be launched
283: * if necessary.
284: * If there are any problems, then an Integer should be returned, set to
285: * the desired exit code. If the application should continue,
286: * return null.
287: */
288: public Integer start(String[] args) {
289: // Decide whether or not to wait for the start main method to complete before returning.
290: boolean waitForStartMain = WrapperSystemPropertyUtil
291: .getBooleanProperty(WrapperStartStopApp.class.getName()
292: + ".waitForStartMain", false);
293: int maxStartMainWait = WrapperSystemPropertyUtil
294: .getIntProperty(WrapperStartStopApp.class.getName()
295: + ".maxStartMainWait", 2);
296: maxStartMainWait = Math.max(1, maxStartMainWait);
297:
298: // Decide the maximum number of times to loop waiting for the main start method.
299: int maxLoops;
300: if (waitForStartMain) {
301: maxLoops = Integer.MAX_VALUE;
302: if (WrapperManager.isDebugEnabled()) {
303: System.out
304: .println("WrapperStartStopApp: start(args) Will wait indefinitely "
305: + "for the main method to complete.");
306: }
307: } else {
308: maxLoops = maxStartMainWait; // 1s loops.
309: if (WrapperManager.isDebugEnabled()) {
310: System.out
311: .println("WrapperStartStopApp: start(args) Will wait up to "
312: + maxLoops
313: + " seconds for the main method to complete.");
314: }
315: }
316:
317: Thread mainThread = new Thread(this , "WrapperStartStopAppMain");
318: synchronized (this ) {
319: m_startMainArgs = args;
320: mainThread.start();
321:
322: // To avoid problems with the main thread starting slowly on heavily loaded systems,
323: // do not continue until the thread has actually started.
324: while (!m_mainStarted) {
325: try {
326: this .wait(1000);
327: } catch (InterruptedException e) {
328: // Continue.
329: }
330: }
331:
332: // Wait for startup main method to complete.
333: int loops = 0;
334: while ((loops < maxLoops) && (!m_mainComplete)) {
335: try {
336: this .wait(1000);
337: } catch (InterruptedException e) {
338: // Continue.
339: }
340:
341: if (!m_mainComplete) {
342: // If maxLoops is large then this could take a while. Notify the
343: // WrapperManager that we are still starting so it doesn't give up.
344: WrapperManager.signalStarting(5000);
345: }
346:
347: loops++;
348: }
349:
350: // Always set the flag stating that the start method completed. This is needed
351: // so the run method can decide whether or not it needs to be responsible for
352: // shutting down the JVM in the event of an exception thrown by the start main
353: // method.
354: m_startComplete = true;
355:
356: // The main exit code will be null unless an error was thrown by the start
357: // main method.
358: if (WrapperManager.isDebugEnabled()) {
359: System.out
360: .println("WrapperStartStopApp: start(args) end. Main Completed="
361: + m_mainComplete
362: + ", exitCode="
363: + m_mainExitCode);
364: }
365: return m_mainExitCode;
366: }
367: }
368:
369: /**
370: * Called when the application is shutting down.
371: */
372: public int stop(int exitCode) {
373: if (WrapperManager.isDebugEnabled()) {
374: System.out.println("WrapperStartStopApp: stop(" + exitCode
375: + ")");
376: }
377:
378: // Execute the main method in the stop class
379: Throwable t = null;
380: try {
381: if (WrapperManager.isDebugEnabled()) {
382: System.out
383: .println("WrapperStartStopApp: invoking stop main method");
384: }
385: m_stopMainMethod.invoke(null,
386: new Object[] { m_stopMainArgs });
387: if (WrapperManager.isDebugEnabled()) {
388: System.out
389: .println("WrapperStartStopApp: stop main method completed");
390: }
391:
392: if (m_stopWait) {
393: // This feature exists to make sure the stop process waits for the main
394: // application to fully shutdown. This can only be done by looking for
395: // and counting the number of non-daemon threads still running in the
396: // system.
397:
398: int systemThreadCount = WrapperSystemPropertyUtil
399: .getIntProperty(WrapperStartStopApp.class
400: .getName()
401: + ".systemThreadCount", 1);
402: systemThreadCount = Math.max(0, systemThreadCount);
403:
404: int threadCnt;
405: while ((threadCnt = getNonDaemonThreadCount()) > systemThreadCount) {
406: if (WrapperManager.isDebugEnabled()) {
407: System.out
408: .println("WrapperStartStopApp: stopping. Waiting for "
409: + (threadCnt - systemThreadCount)
410: + " threads to complete.");
411: }
412: try {
413: Thread.sleep(1000);
414: } catch (InterruptedException e) {
415: }
416: }
417: }
418:
419: // Success
420: return exitCode;
421: } catch (IllegalAccessException e) {
422: t = e;
423: } catch (IllegalArgumentException e) {
424: t = e;
425: } catch (InvocationTargetException e) {
426: t = e;
427: }
428:
429: // If we get here, then an error was thrown.
430: System.out.println("Encountered an error running stop main: "
431: + t);
432:
433: // We should print a stack trace here, because in the case of an
434: // InvocationTargetException, the user needs to know what exception
435: // their app threw.
436: t.printStackTrace();
437:
438: // Return a failure exit code
439: return 1;
440: }
441:
442: /**
443: * Called whenever the native wrapper code traps a system control signal
444: * against the Java process. It is up to the callback to take any actions
445: * necessary. Possible values are: WrapperManager.WRAPPER_CTRL_C_EVENT,
446: * WRAPPER_CTRL_CLOSE_EVENT, WRAPPER_CTRL_LOGOFF_EVENT, or
447: * WRAPPER_CTRL_SHUTDOWN_EVENT
448: */
449: public void controlEvent(int event) {
450: if ((event == WrapperManager.WRAPPER_CTRL_LOGOFF_EVENT)
451: && WrapperManager.isLaunchedAsService()) {
452: // Ignore
453: if (WrapperManager.isDebugEnabled()) {
454: System.out.println("WrapperStartStopApp: controlEvent("
455: + event + ") Ignored");
456: }
457: } else {
458: if (WrapperManager.isDebugEnabled()) {
459: System.out.println("WrapperStartStopApp: controlEvent("
460: + event + ") Stopping");
461: }
462: WrapperManager.stop(0);
463: // Will not get here.
464: }
465: }
466:
467: /*---------------------------------------------------------------
468: * Methods
469: *-------------------------------------------------------------*/
470: /**
471: * Returns a count of all non-daemon threads in the JVM, starting with the top
472: * thread group.
473: *
474: * @return Number of non-daemon threads.
475: */
476: private int getNonDaemonThreadCount() {
477: // Locate the top thread group.
478: ThreadGroup topGroup = Thread.currentThread().getThreadGroup();
479: while (topGroup.getParent() != null) {
480: topGroup = topGroup.getParent();
481: }
482:
483: // Get a list of all threads. Use an array that is twice the total number of
484: // threads as the number of running threads may be increasing as this runs.
485: Thread[] threads = new Thread[topGroup.activeCount() * 2];
486: topGroup.enumerate(threads, true);
487:
488: // Only count any non daemon threads which are
489: // still alive other than this thread.
490: int liveCount = 0;
491: for (int i = 0; i < threads.length; i++) {
492: /*
493: if ( threads[i] != null )
494: {
495: System.out.println( "Check " + threads[i].getName() + " daemon="
496: + threads[i].isDaemon() + " alive=" + threads[i].isAlive() );
497: }
498: */
499: if ((threads[i] != null) && threads[i].isAlive()) {
500: // Do not count this thread.
501: if ((Thread.currentThread() != threads[i])
502: && (!threads[i].isDaemon())) {
503: // Non-Daemon living thread
504: liveCount++;
505: //System.out.println( " -> Non-Daemon" );
506: }
507: }
508: }
509: //System.out.println( " => liveCount = " + liveCount );
510:
511: return liveCount;
512: }
513:
514: /**
515: * Returns the main method of the specified class. If there are any problems,
516: * an error message will be displayed and the Wrapper will be stopped. This
517: * method will only return if it has a valid method.
518: */
519: private Method getMainMethod(String className) {
520: // Look for the start class by name
521: Class mainClass;
522: try {
523: mainClass = Class.forName(className);
524: } catch (ClassNotFoundException e) {
525: System.out
526: .println("WrapperStartStopApp: Unable to locate the class "
527: + className + ": " + e);
528: showUsage();
529: WrapperManager.stop(1);
530: return null; // Will not get here
531: } catch (LinkageError e) {
532: System.out
533: .println("WrapperStartStopApp: Unable to locate the class "
534: + className + ": " + e);
535: showUsage();
536: WrapperManager.stop(1);
537: return null; // Will not get here
538: }
539:
540: // Look for the start method
541: Method mainMethod;
542: try {
543: // getDeclaredMethod will return any method named main in the specified class,
544: // while getMethod will only return public methods, but it will search up the
545: // inheritance path.
546: mainMethod = mainClass.getMethod("main",
547: new Class[] { String[].class });
548: } catch (NoSuchMethodException e) {
549: System.out
550: .println("WrapperStartStopApp: Unable to locate a public static main method in "
551: + "class " + className + ": " + e);
552: showUsage();
553: WrapperManager.stop(1);
554: return null; // Will not get here
555: } catch (SecurityException e) {
556: System.out
557: .println("WrapperStartStopApp: Unable to locate a public static main method in "
558: + "class " + className + ": " + e);
559: showUsage();
560: WrapperManager.stop(1);
561: return null; // Will not get here
562: }
563:
564: // Make sure that the method is public and static
565: int modifiers = mainMethod.getModifiers();
566: if (!(Modifier.isPublic(modifiers) && Modifier
567: .isStatic(modifiers))) {
568: System.out
569: .println("WrapperStartStopApp: The main method in class "
570: + className
571: + " must be declared public and static.");
572: showUsage();
573: WrapperManager.stop(1);
574: return null; // Will not get here
575: }
576:
577: return mainMethod;
578: }
579:
580: private String[] getArgs(String[] args, int argBase) {
581: // The arg at the arg base should be a count of the number of available arguments.
582: int argCount;
583: try {
584: argCount = Integer.parseInt(args[argBase]);
585: } catch (NumberFormatException e) {
586: System.out
587: .println("WrapperStartStopApp: Illegal argument count: "
588: + args[argBase]);
589: showUsage();
590: WrapperManager.stop(1);
591: return null; // Will not get here
592: }
593: if (argCount < 0) {
594: System.out
595: .println("WrapperStartStopApp: Illegal argument count: "
596: + args[argBase]);
597: showUsage();
598: WrapperManager.stop(1);
599: return null; // Will not get here
600: }
601:
602: // Make sure that there are enough arguments in the array.
603: if (args.length < argBase + 1 + argCount) {
604: System.out
605: .println("WrapperStartStopApp: Not enough argments. Argument count of "
606: + argCount + " was specified.");
607: showUsage();
608: WrapperManager.stop(1);
609: return null; // Will not get here
610: }
611:
612: // Create the argument array
613: String[] mainArgs = new String[argCount];
614: System.arraycopy(args, argBase + 1, mainArgs, 0, argCount);
615:
616: return mainArgs;
617: }
618:
619: /**
620: * Displays application usage
621: */
622: protected void showUsage() {
623: System.out.println();
624: System.out.println("WrapperStartStopApp Usage:");
625: System.out
626: .println(" java org.tanukisoftware.wrapper.WrapperStartStopApp {start_class} {start_arg_count} "
627: + "[start_arguments] {stop_class} {stop_wait} {stop_arg_count} [stop_arguments]");
628: System.out.println();
629: System.out.println("Where:");
630: System.out
631: .println(" start_class: The fully qualified class name to run to start the ");
632: System.out.println(" application.");
633: System.out
634: .println(" start_arg_count: The number of arguments to be passed to the start class's ");
635: System.out.println(" main method.");
636: System.out
637: .println(" start_arguments: The arguments that would normally be passed to the start ");
638: System.out.println(" class application.");
639: System.out
640: .println(" stop_class: The fully qualified class name to run to stop the ");
641: System.out.println(" application.");
642: System.out
643: .println(" stop_wait: When stopping, should the Wrapper wait for all threads to ");
644: System.out
645: .println(" complete before exiting (true/false).");
646: System.out
647: .println(" stop_arg_count: The number of arguments to be passed to the stop class's ");
648: System.out.println(" main method.");
649: System.out
650: .println(" stop_arguments: The arguments that would normally be passed to the stop ");
651: System.out.println(" class application.");
652: }
653:
654: /*---------------------------------------------------------------
655: * Main Method
656: *-------------------------------------------------------------*/
657: /**
658: * Used to Wrapper enable a standard Java application. This main
659: * expects the first argument to be the class name of the application
660: * to launch. All remaining arguments will be wrapped into a new
661: * argument list and passed to the main method of the specified
662: * application.
663: */
664: public static void main(String args[]) {
665: new WrapperStartStopApp(args);
666: }
667: }
|