KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > martiansoftware > nailgun > NGServer


1 /*
2
3   Copyright 2004, Martian Software, Inc.
4
5   Licensed under the Apache License, Version 2.0 (the "License");
6   you may not use this file except in compliance with the License.
7   You may obtain a copy of the License at
8
9   http://www.apache.org/licenses/LICENSE-2.0
10
11   Unless required by applicable law or agreed to in writing, software
12   distributed under the License is distributed on an "AS IS" BASIS,
13   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   See the License for the specific language governing permissions and
15   limitations under the License.
16
17 */

18
19 package com.martiansoftware.nailgun;
20 import java.io.InputStream JavaDoc;
21 import java.io.PrintStream JavaDoc;
22 import java.lang.reflect.Method JavaDoc;
23 import java.net.InetAddress JavaDoc;
24 import java.net.ServerSocket JavaDoc;
25 import java.net.Socket JavaDoc;
26 import java.net.UnknownHostException JavaDoc;
27 import java.util.Iterator JavaDoc;
28 import java.util.Map JavaDoc;
29
30 import com.martiansoftware.nailgun.builtins.DefaultNail;
31
32 /**
33  * <p>Listens for new connections from NailGun clients and launches
34  * NGSession threads to process them.</p>
35  *
36  * <p>This class can be run as a standalone server or can be embedded
37  * within larger applications as a means of providing command-line
38  * interaction with the application.</p>
39  *
40  * @author <a HREF="http://www.martiansoftware.com/contact.html">Marty Lamb</a>
41  */

42 public class NGServer implements Runnable JavaDoc {
43
44     /**
45      * The address on which to listen, or null to listen on all
46      * local addresses
47      */

48     private InetAddress JavaDoc addr = null;
49     
50     /**
51      * The port on which to listen, or zero to select a port automatically
52      */

53     private int port = 0;
54     
55     /**
56      * The socket doing the listening
57      */

58     private ServerSocket JavaDoc serversocket;
59     
60     /**
61      * True if this NGServer has received instructions to shut down
62      */

63     private boolean shutdown = false;
64     
65     /**
66      * True if this NGServer has been started and is accepting connections
67      */

68     private boolean running = false;
69     
70     /**
71      * This NGServer's AliasManager, which maps aliases to classes
72      */

73     private AliasManager aliasManager;
74     
75     /**
76      * If true, fully-qualified classnames are valid commands
77      */

78     private boolean allowNailsByClassName = true;
79     
80     /**
81      * The default class to use if an invalid alias or classname is
82      * specified by the client.
83      */

84     private Class JavaDoc defaultNailClass = null;
85     
86     /**
87      * A pool of NGSessions ready to handle client connections
88      */

89     private NGSessionPool sessionPool = null;
90     
91     /**
92      * <code>System.out</code> at the time of the NGServer's creation
93      */

94     public final PrintStream JavaDoc out = System.out;
95
96     /**
97      * <code>System.err</code> at the time of the NGServer's creation
98      */

99     public final PrintStream JavaDoc err = System.err;
100     
101     /**
102      * <code>System.in</code> at the time of the NGServer's creation
103      */

104     public final InputStream JavaDoc in = System.in;
105     
106     /**
107      * a collection of all classes executed by this server so far
108      */

109     private Map JavaDoc allNailStats = null;
110     
111     /**
112      * Remember the security manager we start with so we can restore it later
113      */

114     private SecurityManager JavaDoc originalSecurityManager = null;
115     
116     /**
117      * Creates a new NGServer that will listen at the specified address and
118      * on the specified port.
119      * This does <b>not</b> cause the server to start listening. To do
120      * so, create a new <code>Thread</code> wrapping this <code>NGServer</code>
121      * and start it.
122      * @param addr the address at which to listen, or <code>null</code> to bind
123      * to all local addresses
124      * @param port the port on which to listen.
125      */

126     public NGServer(InetAddress JavaDoc addr, int port) {
127         init(addr, port);
128     }
129     
130     /**
131      * Creates a new NGServer that will listen on the default port
132      * (defined in <code>NGConstants.DEFAULT_PORT</code>).
133      * This does <b>not</b> cause the server to start listening. To do
134      * so, create a new <code>Thread</code> wrapping this <code>NGServer</code>
135      * and start it.
136      */

137     public NGServer() {
138         init(null, NGConstants.DEFAULT_PORT);
139     }
140     
141     /**
142      * Sets up the NGServer internals
143      * @param addr the InetAddress to bind to
144      * @param port the port on which to listen
145      */

146     private void init(InetAddress JavaDoc addr, int port) {
147         this.addr = addr;
148         this.port = port;
149         
150         this.aliasManager = new AliasManager();
151         allNailStats = new java.util.HashMap JavaDoc();
152         // allow a maximum of 10 idle threads. probably too high a number
153
// and definitely should be configurable in the future
154
sessionPool = new NGSessionPool(this, 10);
155     }
156
157     /**
158      * Sets a flag that determines whether Nails can be executed by class name.
159      * If this is false, Nails can only be run via aliases (and you should
160      * probably remove ng-alias from the AliasManager).
161      *
162      * @param allowNailsByClassName true iff Nail lookups by classname are allowed
163      */

164     public void setAllowNailsByClassName(boolean allowNailsByClassName) {
165         this.allowNailsByClassName = allowNailsByClassName;
166     }
167     
168     /**
169      * Returns a flag that indicates whether Nail lookups by classname
170      * are allowed. If this is false, Nails can only be run via aliases.
171      * @return a flag that indicates whether Nail lookups by classname
172      * are allowed.
173      */

174     public boolean allowsNailsByClassName() {
175         return (allowNailsByClassName);
176     }
177     
178     /**
179      * Sets the default class to use for the Nail if no Nails can
180      * be found via alias or classname. (may be <code>null</code>,
181      * in which case NailGun will use its own default)
182      * @param defaultNailClass the default class to use for the Nail
183      * if no Nails can be found via alias or classname.
184      * (may be <code>null</code>, in which case NailGun will use
185      * its own default)
186      */

187     public void setDefaultNailClass(Class JavaDoc defaultNailClass) {
188         this.defaultNailClass = defaultNailClass;
189     }
190     
191     /**
192      * Returns the default class that will be used if no Nails
193      * can be found via alias or classname.
194      * @return the default class that will be used if no Nails
195      * can be found via alias or classname.
196      */

197     public Class JavaDoc getDefaultNailClass() {
198         return ((defaultNailClass == null) ? DefaultNail.class : defaultNailClass) ;
199     }
200     
201     /**
202      * Returns the current NailStats object for the specified class, creating
203      * a new one if necessary
204      * @param nailClass the class for which we're gathering stats
205      * @return a NailStats object for the specified class
206      */

207     private NailStats getOrCreateStatsFor(Class JavaDoc nailClass) {
208         NailStats result = null;
209         synchronized(allNailStats) {
210             result = (NailStats) allNailStats.get(nailClass);
211             if (result == null) {
212                 result = new NailStats(nailClass);
213                 allNailStats.put(nailClass, result);
214             }
215         }
216         return (result);
217     }
218     
219     /**
220      * Provides a means for an NGSession to register the starting of
221      * a nail execution with the server.
222      *
223      * @param nailClass the nail class that was launched
224      */

225     void nailStarted(Class JavaDoc nailClass) {
226         NailStats stats = getOrCreateStatsFor(nailClass);
227         stats.nailStarted();
228     }
229     
230     /**
231      * Provides a means for an NGSession to register the completion of
232      * a nails execution with the server.
233      *
234      * @param nailClass the nail class that finished
235      */

236     void nailFinished(Class JavaDoc nailClass) {
237         NailStats stats = (NailStats) allNailStats.get(nailClass);
238         stats.nailFinished();
239     }
240     
241     /**
242      * Returns a snapshot of this NGServer's nail statistics. The result is a <code>java.util.Map</code>,
243      * keyed by class name, with <a HREF="NailStats.html">NailStats</a> objects as values.
244      *
245      * @return a snapshot of this NGServer's nail statistics.
246      */

247     public Map JavaDoc getNailStats() {
248         Map JavaDoc result = new java.util.TreeMap JavaDoc();
249         synchronized(allNailStats) {
250             for (Iterator JavaDoc i = allNailStats.keySet().iterator(); i.hasNext();) {
251                 Class JavaDoc nailclass = (Class JavaDoc) i.next();
252                 result.put(nailclass.getName(), ((NailStats) allNailStats.get(nailclass)).clone());
253             }
254         }
255         return (result);
256     }
257     
258     /**
259      * Returns the AliasManager in use by this NGServer.
260      * @return the AliasManager in use by this NGServer.
261      */

262     public AliasManager getAliasManager() {
263         return (aliasManager);
264     }
265
266     /**
267      * <p>Shuts down the server. The server will stop listening
268      * and its thread will finish. Any running nails will be allowed
269      * to finish.</p>
270      *
271      * <p>Any nails that provide a
272      * <pre><code>public static void nailShutdown(NGServer)</code></pre>
273      * method will have this method called with this NGServer as its sole
274      * parameter.</p>
275      *
276      * @param exitVM if true, this method will also exit the JVM after
277      * calling nailShutdown() on any nails. This may prevent currently
278      * running nails from exiting gracefully, but may be necessary in order
279      * to perform some tasks, such as shutting down any AWT or Swing threads
280      * implicitly launched by your nails.
281      */

282     public void shutdown(boolean exitVM) {
283         synchronized(this) {
284             if (shutdown) return;
285             shutdown = true;
286         }
287         
288         try {
289             serversocket.close();
290         } catch (Throwable JavaDoc toDiscard) {}
291         
292         sessionPool.shutdown();
293         
294         Class JavaDoc[] argTypes = new Class JavaDoc[1];
295         argTypes[0] = NGServer.class;
296         Object JavaDoc[] argValues = new Object JavaDoc[1];
297         argValues[0] = this;
298         
299         // make sure that all aliased classes have associated nailstats
300
// so they can be shut down.
301
for (Iterator JavaDoc i = getAliasManager().getAliases().iterator(); i.hasNext();) {
302             Alias alias = (Alias) i.next();
303             getOrCreateStatsFor(alias.getAliasedClass());
304         }
305         
306         synchronized(allNailStats) {
307             for (Iterator JavaDoc i = allNailStats.values().iterator(); i.hasNext();) {
308                 NailStats ns = (NailStats) i.next();
309                 Class JavaDoc nailClass = ns.getNailClass();
310                 
311                 // yes, I know this is lazy, relying upon the exception
312
// to handle the case of no nailShutdown method.
313
try {
314                     Method JavaDoc nailShutdown = nailClass.getMethod("nailShutdown", argTypes);
315                     nailShutdown.invoke(null, argValues);
316                 } catch (Throwable JavaDoc toDiscard) {}
317             }
318         }
319         
320         // restore system streams
321
System.setIn(in);
322         System.setOut(out);
323         System.setErr(err);
324         
325         System.setSecurityManager(originalSecurityManager);
326         
327         if (exitVM) {
328             System.exit(0);
329         }
330     }
331     
332     /**
333      * Returns true iff the server is currently running.
334      * @return true iff the server is currently running.
335      */

336     public boolean isRunning() {
337         return (running);
338     }
339     
340     /**
341      * Returns the port on which this server is (or will be) listening.
342      * @return the port on which this server is (or will be) listening.
343      */

344     public int getPort() {
345         return ((serversocket == null) ? port : serversocket.getLocalPort());
346     }
347     
348     /**
349      * Listens for new connections and launches NGSession threads
350      * to process them.
351      */

352     public void run() {
353         running = true;
354         NGSession sessionOnDeck = null;
355         
356         originalSecurityManager = System.getSecurityManager();
357         System.setSecurityManager(
358                 new NGSecurityManager(
359                         originalSecurityManager));
360   
361
362         synchronized(System.in) {
363             if (!(System.in instanceof ThreadLocalInputStream)) {
364                 System.setIn(new ThreadLocalInputStream(in));
365                 System.setOut(new ThreadLocalPrintStream(out));
366                 System.setErr(new ThreadLocalPrintStream(err));
367             }
368         }
369         
370         try {
371             if (addr == null) {
372                 serversocket = new ServerSocket JavaDoc(port);
373             } else {
374                 serversocket = new ServerSocket JavaDoc(port, 0, addr);
375             }
376             
377             while (!shutdown) {
378                 sessionOnDeck = sessionPool.take();
379                 Socket JavaDoc socket = serversocket.accept();
380                 sessionOnDeck.run(socket);
381             }
382
383         } catch (Throwable JavaDoc t) {
384             // if shutdown is called while the accept() method is blocking,
385
// an exception will be thrown that we don't care about. filter
386
// those out.
387
if (!shutdown) {
388                 t.printStackTrace();
389             }
390         }
391         if (sessionOnDeck != null) {
392             sessionOnDeck.shutdown();
393         }
394         running = false;
395     }
396     
397     private static void usage() {
398         System.err.println("Usage: java com.martiansoftware.nailgun.NGServer");
399         System.err.println(" or: java com.martiansoftware.nailgun.NGServer port");
400         System.err.println(" or: java com.martiansoftware.nailgun.NGServer IPAddress");
401         System.err.println(" or: java com.martiansoftware.nailgun.NGServer IPAddress:port");
402     }
403     
404     /**
405      * Creates and starts a new <code>NGServer</code>. A single optional
406      * argument is valid, specifying the port on which this <code>NGServer</code>
407      * should listen. If omitted, <code>NGServer.DEFAULT_PORT</code> will be used.
408      * @param args a single optional argument specifying the port on which to listen.
409      * @throws NumberFormatException if a non-numeric port is specified
410      */

411     public static void main(String JavaDoc[] args) throws NumberFormatException JavaDoc, UnknownHostException JavaDoc {
412
413         if (args.length > 1) {
414             usage();
415             return;
416         }
417
418         // null server address means bind to everything local
419
InetAddress JavaDoc serverAddress = null;
420         int port = NGConstants.DEFAULT_PORT;
421         
422         // parse the sole command line parameter, which
423
// may be an inetaddress to bind to, a port number,
424
// or an inetaddress followed by a port, separated
425
// by a colon
426
if (args.length != 0) {
427             String JavaDoc[] argParts = args[0].split(":");
428             String JavaDoc addrPart = null;
429             String JavaDoc portPart = null;
430             if (argParts.length == 2) {
431                 addrPart = argParts[0];
432                 portPart = argParts[1];
433             } else if (argParts[0].indexOf('.') >= 0) {
434                 addrPart = argParts[0];
435             } else {
436                 portPart = argParts[0];
437             }
438             if (addrPart != null) {
439                 serverAddress = InetAddress.getByName(addrPart);
440             }
441             if (portPart != null) {
442                 port = Integer.parseInt(portPart);
443             }
444         }
445
446         NGServer server = new NGServer(serverAddress, port);
447         Thread JavaDoc t = new Thread JavaDoc(server);
448         t.setName("NGServer(" + serverAddress + ", " + port + ")");
449         t.start();
450
451         Runtime.getRuntime().addShutdownHook(new NGServerShutdowner(server));
452         
453         // if the port is 0, it will be automatically determined.
454
// add this little wait so the ServerSocket can fully
455
// initialize and we can see what port it chose.
456
int runningPort = server.getPort();
457         while (runningPort == 0) {
458             try { Thread.sleep(50); } catch (Throwable JavaDoc toIgnore) {}
459             runningPort = server.getPort();
460         }
461         
462         System.out.println("NGServer started on "
463                             + ((serverAddress == null)
464                                 ? "all interfaces"
465                                 : serverAddress.getHostAddress())
466                             + ", port "
467                             + runningPort
468                             + ".");
469     }
470
471     /**
472      * A shutdown hook that will cleanly bring down the NGServer if it
473      * is interrupted.
474      *
475      * @author <a HREF="http://www.martiansoftware.com/contact.html">Marty Lamb</a>
476      */

477     private static class NGServerShutdowner extends Thread JavaDoc {
478         private NGServer server = null;
479         
480         NGServerShutdowner(NGServer server) {
481             this.server = server;
482         }
483         
484         
485         public void run() {
486             
487             int count = 0;
488             server.shutdown(false);
489             
490             // give the server up to five seconds to stop. is that enough?
491
// remember that the shutdown will call nailShutdown in any
492
// nails as well
493
while (server.isRunning() && (count < 50)) {
494
495                 try {Thread.sleep(100);} catch(InterruptedException JavaDoc e) {}
496                 ++count;
497             }
498             
499             if (server.isRunning()) {
500                 System.err.println("Unable to cleanly shutdown server. Exiting JVM Anyway.");
501             } else {
502                 System.out.println("NGServer shut down.");
503             }
504         }
505     }
506 }
507
Popular Tags