KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > tanukisoftware > wrapper > WrapperActionServer


1 package org.tanukisoftware.wrapper;
2
3 /*
4  * Copyright (c) 1999, 2006 Tanuki Software Inc.
5  *
6  * Permission is hereby granted, free of charge, to any person
7  * obtaining a copy of the Java Service Wrapper and associated
8  * documentation files (the "Software"), to deal in the Software
9  * without restriction, including without limitation the rights
10  * to use, copy, modify, merge, publish, distribute, sub-license,
11  * and/or sell copies of the Software, and to permit persons to
12  * whom the Software is furnished to do so, subject to the
13  * following conditions:
14  *
15  * The above copyright notice and this permission notice shall be
16  * included in all copies or substantial portions of the Software.
17  *
18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20  * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21  * NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22  * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25  * OTHER DEALINGS IN THE SOFTWARE.
26  */

27
28 import java.io.IOException JavaDoc;
29 import java.io.InterruptedIOException JavaDoc;
30 import java.lang.reflect.InvocationTargetException JavaDoc;
31 import java.lang.reflect.Method JavaDoc;
32 import java.net.InetAddress JavaDoc;
33 import java.net.Socket JavaDoc;
34 import java.net.SocketException JavaDoc;
35 import java.net.ServerSocket JavaDoc;
36 import java.util.Hashtable JavaDoc;
37
38 import org.tanukisoftware.wrapper.WrapperManager;
39
40 /**
41  * If an application instantiates an instance of this class, the JVM will
42  * listen on the specified port for connections. When a connection is
43  * detected, the first byte of input will be read from the socket and
44  * then the connection will be immediately closed. An action will then
45  * be performed based on the byte read from the stream.
46  * <p>
47  * The easiest way to invoke an action manually is to telnet to the specified
48  * port and then type the single command key.
49  * <code>telnet localhost 9999</code>, for example.
50  * <p>
51  * Valid commands include:
52  * <ul>
53  * <li><b>S</b> : Shutdown cleanly.</li>
54  * <li><b>H</b> : Immediate forced shutdown.</li>
55  * <li><b>R</b> : Restart</li>
56  * <li><b>D</b> : Perform a Thread Dump</li>
57  * <li><b>U</b> : Unexpected shutdown. (Simulate a crash for testing)</li>
58  * <li><b>V</b> : Cause an access violation. (For testing)</li>
59  * <li><b>G</b> : Make the JVM appear to be hung. (For testing)</li>
60  * </ul>
61  * Additional user defined actions can be defined by calling the
62  * {@link #registerAction( byte command, Runnable action )} method.
63  * The Wrapper project reserves the right to define any upper case
64  * commands in the future. To avoid future conflicts, please use lower
65  * case for user defined commands.
66  * <p>
67  * This application will work even in most deadlock situations because the
68  * thread is in issolation from the rest of the application. If the JVM
69  * is truely hung, this class will fail to accept connections but the
70  * Wrapper itself will detect the hang and restart the JVM externally.
71  * <p>
72  * The following code can be used in your application to start up the
73  * WrapperActionServer with all default actions enabled:
74  * <pre>
75  * int port = 9999;
76  * WrapperActionServer server = new WrapperActionServer( port );
77  * server.enableShutdownAction( true );
78  * server.enableHaltExpectedAction( true );
79  * server.enableRestartAction( true );
80  * server.enableThreadDumpAction( true );
81  * server.enableHaltUnexpectedAction( true );
82  * server.enableAccessViolationAction( true );
83  * server.enableAppearHungAction( true );
84  * server.start();
85  * </pre>
86  * Then remember to stop the server when your application shuts down:
87  * <pre>
88  * server.stop();
89  * </pre>
90  *
91  * @author Leif Mortenson <leif@tanukisoftware.com>
92  */

93 public class WrapperActionServer
94     implements Runnable JavaDoc
95 {
96     /** Command to invoke a shutdown action. */
97     public final static byte COMMAND_SHUTDOWN = (byte)'S';
98     /** Command to invoke an expected halt action. */
99     public final static byte COMMAND_HALT_EXPECTED = (byte)'H';
100     /** Command to invoke a restart action. */
101     public final static byte COMMAND_RESTART = (byte)'R';
102     /** Command to invoke a thread dump action. */
103     public final static byte COMMAND_DUMP = (byte)'D';
104     /** Command to invoke an unexpected halt action. */
105     public final static byte COMMAND_HALT_UNEXPECTED = (byte)'U';
106     /** Command to invoke an access violation. */
107     public final static byte COMMAND_ACCESS_VIOLATION = (byte)'V';
108     /** Command to invoke an appear hung action. */
109     public final static byte COMMAND_APPEAR_HUNG = (byte)'G';
110
111     /** The address to bind the port server to. Null for any address. */
112     private InetAddress JavaDoc m_bindAddr;
113     
114     /** The port to listen on for connections. */
115     private int m_port;
116     
117     /** Reference to the worker thread. */
118     private Thread JavaDoc m_runner;
119     
120     /** Flag set when the m_runner thread has been asked to stop. */
121     private boolean m_runnerStop = false;
122     
123     /** Reference to the ServerSocket. */
124     private ServerSocket JavaDoc m_serverSocket;
125     
126     /** Table of all the registered actions. */
127     private Hashtable JavaDoc m_actions = new Hashtable JavaDoc();
128     
129     /*---------------------------------------------------------------
130      * Constructors
131      *-------------------------------------------------------------*/

132     /**
133      * Creates and starts WrapperActionServer instance bound to the
134      * specified port and address.
135      *
136      * @param port Port on which to listen for connections.
137      * @param bindAddress Address to bind to.
138      */

139     public WrapperActionServer( int port, InetAddress JavaDoc bindAddress )
140     {
141         m_port = port;
142         m_bindAddr = bindAddress;
143     }
144     
145     /**
146      * Creates and starts WrapperActionServer instance bound to the
147      * specified port. The socket will bind to all addresses and
148      * should be concidered a security risk.
149      *
150      * @param port Port on which to listen for connections.
151      */

152     public WrapperActionServer( int port )
153     {
154         this( port, null );
155     }
156     
157     /*---------------------------------------------------------------
158      * Runnable Methods
159      *-------------------------------------------------------------*/

160     /**
161      * Thread which will listen for connections on the socket.
162      */

163     public void run()
164     {
165         if ( Thread.currentThread() != m_runner )
166         {
167             throw new IllegalStateException JavaDoc( "Private method." );
168         }
169         
170         try
171         {
172             while ( !m_runnerStop )
173             {
174                 try
175                 {
176                     int command;
177                     Socket JavaDoc socket = m_serverSocket.accept();
178                     try
179                     {
180                         // Set a short timeout of 15 seconds,
181
// so connections will be promptly closed if left idle.
182
socket.setSoTimeout( 15000 );
183                         
184                         // Read a single byte.
185
command = socket.getInputStream().read();
186                     }
187                     finally
188                     {
189                         socket.close();
190                     }
191                     
192                     if ( command >= 0 )
193                     {
194                         Runnable JavaDoc action;
195                         synchronized( m_actions )
196                         {
197                             action = (Runnable JavaDoc)m_actions.get( new Integer JavaDoc( command ) );
198                         }
199                         
200                         if ( action != null )
201                         {
202                             try
203                             {
204                                 action.run();
205                             }
206                             catch ( Throwable JavaDoc t )
207                             {
208                                 System.out.println(
209                                     "WrapperActionServer: Error processing action." );
210                                 t.printStackTrace();
211                             }
212                         }
213                     }
214                 }
215                 catch ( Throwable JavaDoc t )
216                 {
217                     // Check for throwable type this way rather than with seperate catches
218
// to work around a problem where InterruptedException can be thrown
219
// when the compiler gives an error saying that it can't.
220
if ( m_runnerStop
221                         && ( ( t instanceof InterruptedException JavaDoc )
222                         || ( t instanceof SocketException JavaDoc )
223                         || ( t instanceof InterruptedIOException JavaDoc ) ) )
224                     {
225                         // This is expected, the service is being stopped.
226
}
227                     else
228                     {
229                         System.out.println(
230                             "WrapperActionServer: Unexpeced error." );
231                         t.printStackTrace();
232                         
233                         // Avoid tight thrashing
234
try
235                         {
236                             Thread.sleep( 5000 );
237                         }
238                         catch ( InterruptedException JavaDoc e )
239                         {
240                             // Ignore
241
}
242                     }
243                 }
244             }
245         }
246         finally
247         {
248             synchronized( this )
249             {
250                 m_runner = null;
251                 
252                 // Wake up the stop method if it is waiting for the runner to stop.
253
this.notify();
254             }
255         }
256     }
257     
258     /*---------------------------------------------------------------
259      * Methods
260      *-------------------------------------------------------------*/

261     /**
262      * Starts the runner thread.
263      *
264      * @throws IOException If the server socket is unable to bind to the
265      * specified port or there are any other problems
266      * opening a socket.
267      */

268     public void start()
269         throws IOException JavaDoc
270     {
271         // Create the server socket.
272
m_serverSocket = new ServerSocket JavaDoc( m_port, 5, m_bindAddr );
273         
274         m_runner = new Thread JavaDoc( this, "WrapperActionServer_runner" );
275         m_runner.setDaemon( true );
276         m_runner.start();
277     }
278     
279     /**
280      * Stops the runner thread, blocking until it has stopped.
281      */

282     public void stop()
283         throws Exception JavaDoc
284     {
285         Thread JavaDoc runner = m_runner;
286         m_runnerStop = true;
287         runner.interrupt();
288
289         // Close the server socket so it stops blocking for new connections.
290
ServerSocket JavaDoc serverSocket = m_serverSocket;
291         if ( serverSocket != null )
292         {
293             try
294             {
295                 serverSocket.close();
296             }
297             catch ( IOException JavaDoc e )
298             {
299                 // Ignore.
300
}
301         }
302         
303         synchronized( this )
304         {
305             while( m_runner != null )
306             {
307                 try
308                 {
309                     // Wait to be notified that the thread has exited.
310
this.wait();
311                 }
312                 catch ( InterruptedException JavaDoc e )
313                 {
314                     // Ignore
315
}
316             }
317         }
318     }
319     
320     /**
321      * Registers an action with the action server. The server will not accept
322      * any new connections until an action has returned, so keep that in mind
323      * when writing them. Also be aware than any uncaught exceptions will be
324      * dumped to the console if uncaught by the action. To avoid this, wrap
325      * the code in a <code>try { ... } catch (Throwable t) { ... }</code>
326      * block.
327      *
328      * @param command Command to be registered. Will override any exiting
329      * action already registered with the same command.
330      * @param action Action to be registered.
331      */

332     public void registerAction( byte command, Runnable JavaDoc action )
333     {
334         synchronized( m_actions )
335         {
336             m_actions.put( new Integer JavaDoc( command ), action );
337         }
338     }
339     
340     /**
341      * Unregisters an action with the given command. If no action exists with
342      * the specified command, the method will quietly ignore the call.
343      */

344     public void unregisterAction( byte command )
345     {
346         synchronized( m_actions )
347         {
348             m_actions.remove( new Integer JavaDoc( command ) );
349         }
350     }
351     
352     /**
353      * Enable or disable the shutdown command. Disabled by default.
354      *
355      * @param enable True to enable to action, false to disable it.
356      */

357     public void enableShutdownAction( boolean enable )
358     {
359         if ( enable )
360         {
361             registerAction( COMMAND_SHUTDOWN, new Runnable JavaDoc()
362                 {
363                     public void run()
364                     {
365                         WrapperManager.stop( 0 );
366                     }
367                 } );
368         }
369         else
370         {
371             unregisterAction( COMMAND_SHUTDOWN );
372         }
373     }
374     
375     /**
376      * Enable or disable the expected halt command. Disabled by default.
377      * This will shutdown the JVM, but will do so immediately without going
378      * through the clean shutdown process.
379      *
380      * @param enable True to enable to action, false to disable it.
381      */

382     public void enableHaltExpectedAction( boolean enable )
383     {
384         if ( enable )
385         {
386             registerAction( COMMAND_HALT_EXPECTED, new Runnable JavaDoc()
387                 {
388                     public void run()
389                     {
390                         WrapperManager.stopImmediate( 0 );
391                     }
392                 } );
393         }
394         else
395         {
396             unregisterAction( COMMAND_HALT_EXPECTED );
397         }
398     }
399     
400     /**
401      * Enable or disable the restart command. Disabled by default.
402      *
403      * @param enable True to enable to action, false to disable it.
404      */

405     public void enableRestartAction( boolean enable )
406     {
407         if ( enable )
408         {
409             registerAction( COMMAND_RESTART, new Runnable JavaDoc()
410                 {
411                     public void run()
412                     {
413                         WrapperManager.restart();
414                     }
415                 } );
416         }
417         else
418         {
419             unregisterAction( COMMAND_RESTART );
420         }
421     }
422     
423     /**
424      * Enable or disable the thread dump command. Disabled by default.
425      *
426      * @param enable True to enable to action, false to disable it.
427      */

428     public void enableThreadDumpAction( boolean enable )
429     {
430         if ( enable )
431         {
432             registerAction( COMMAND_DUMP, new Runnable JavaDoc()
433                 {
434                     public void run()
435                     {
436                         WrapperManager.requestThreadDump();
437                     }
438                 } );
439         }
440         else
441         {
442             unregisterAction( COMMAND_DUMP );
443         }
444     }
445     
446     /**
447      * Enable or disable the unexpected halt command. Disabled by default.
448      * If this command is executed, the Wrapper will think the JVM crashed
449      * and restart it.
450      *
451      * @param enable True to enable to action, false to disable it.
452      */

453     public void enableHaltUnexpectedAction( boolean enable )
454     {
455         if ( enable )
456         {
457             registerAction( COMMAND_HALT_UNEXPECTED, new Runnable JavaDoc()
458                 {
459                     public void run()
460                     {
461                         // Execute runtime.halt(0) using reflection so this class will
462
// compile on 1.2.x versions of Java.
463
Method JavaDoc haltMethod;
464                         try
465                         {
466                             haltMethod =
467                                 Runtime JavaDoc.class.getMethod( "halt", new Class JavaDoc[] { Integer.TYPE } );
468                         }
469                         catch ( NoSuchMethodException JavaDoc e )
470                         {
471                             System.out.println( "halt not supported by current JVM." );
472                             haltMethod = null;
473                         }
474                         
475                         if ( haltMethod != null )
476                         {
477                             Runtime JavaDoc runtime = Runtime.getRuntime();
478                             try
479                             {
480                                 haltMethod.invoke( runtime, new Object JavaDoc[] { new Integer JavaDoc( 0 ) } );
481                             }
482                             catch ( IllegalAccessException JavaDoc e )
483                             {
484                                 System.out.println(
485                                     "Unable to call runitme.halt: " + e.getMessage() );
486                             }
487                             catch ( InvocationTargetException JavaDoc e )
488                             {
489                                 System.out.println(
490                                     "Unable to call runitme.halt: " + e.getMessage() );
491                             }
492                         }
493                     }
494                 } );
495         }
496         else
497         {
498             unregisterAction( COMMAND_HALT_UNEXPECTED );
499         }
500     }
501     
502     /**
503      * Enable or disable the access violation command. Disabled by default.
504      * This command is useful for testing how an application handles the worst
505      * case situation where the JVM suddenly crashed. When this happens, the
506      * the JVM will simply die and there will be absolutely no chance for any
507      * shutdown or cleanup work to be done by the JVM.
508      *
509      * @param enable True to enable to action, false to disable it.
510      */

511     public void enableAccessViolationAction( boolean enable )
512     {
513         if ( enable )
514         {
515             registerAction( COMMAND_ACCESS_VIOLATION, new Runnable JavaDoc()
516                 {
517                     public void run()
518                     {
519                         WrapperManager.accessViolationNative();
520                     }
521                 } );
522         }
523         else
524         {
525             unregisterAction( COMMAND_ACCESS_VIOLATION );
526         }
527     }
528     
529     /**
530      * Enable or disable the appear hung command. Disabled by default.
531      * This command is useful for testing how an application handles the
532      * situation where the JVM stops responding to the Wrapper's ping
533      * requests. This can happen if the JVM hangs or some piece of code
534      * deadlocks. When this happens, the Wrapper will give up after the
535      * ping timeout has expired and kill the JVM process. The JVM will
536      * not have a chance to clean up and shudown gracefully.
537      *
538      * @param enable True to enable to action, false to disable it.
539      */

540     public void enableAppearHungAction( boolean enable )
541     {
542         if ( enable )
543         {
544             registerAction( COMMAND_APPEAR_HUNG, new Runnable JavaDoc()
545                 {
546                     public void run()
547                     {
548                         WrapperManager.appearHung();
549                     }
550                 } );
551         }
552         else
553         {
554             unregisterAction( COMMAND_APPEAR_HUNG );
555         }
556     }
557 }
558
559
Popular Tags