Coverage details for com.martiansoftware.nailgun.NGServer

LineHitsSource
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;
21 import java.io.PrintStream;
22 import java.lang.reflect.Method;
23 import java.net.InetAddress;
24 import java.net.ServerSocket;
25 import java.net.Socket;
26 import java.net.UnknownHostException;
27 import java.util.Iterator;
28 import java.util.Map;
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 {
43  
44     /**
45      * The address on which to listen, or null to listen on all
46      * local addresses
47      */
481    private InetAddress addr = null;
49     
50     /**
51      * The port on which to listen, or zero to select a port automatically
52      */
531    private int port = 0;
54     
55     /**
56      * The socket doing the listening
57      */
58     private ServerSocket serversocket;
59     
60     /**
61      * True if this NGServer has received instructions to shut down
62      */
631    private boolean shutdown = false;
64     
65     /**
66      * True if this NGServer has been started and is accepting connections
67      */
681    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      */
781    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      */
841    private Class defaultNailClass = null;
85     
86     /**
87      * A pool of NGSessions ready to handle client connections
88      */
891    private NGSessionPool sessionPool = null;
90     
91     /**
92      * <code>System.out</code> at the time of the NGServer's creation
93      */
941    public final PrintStream out = System.out;
95  
96     /**
97      * <code>System.err</code> at the time of the NGServer's creation
98      */
991    public final PrintStream err = System.err;
100     
101     /**
102      * <code>System.in</code> at the time of the NGServer's creation
103      */
1041    public final InputStream in = System.in;
105     
106     /**
107      * a collection of all classes executed by this server so far
108      */
1091    private Map allNailStats = null;
110     
111     /**
112      * Remember the security manager we start with so we can restore it later
113      */
1141    private SecurityManager 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      */
1260    public NGServer(InetAddress addr, int port) {
1270        init(addr, port);
1280    }
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      */
1371    public NGServer() {
1381        init(null, NGConstants.DEFAULT_PORT);
1391    }
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 addr, int port) {
1471        this.addr = addr;
1481        this.port = port;
149         
1501        this.aliasManager = new AliasManager();
1511        allNailStats = new java.util.HashMap();
152         // allow a maximum of 10 idle threads. probably too high a number
153         // and definitely should be configurable in the future
1541        sessionPool = new NGSessionPool(this, 10);
1551    }
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) {
1650        this.allowNailsByClassName = allowNailsByClassName;
1660    }
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() {
1750        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 defaultNailClass) {
1880        this.defaultNailClass = defaultNailClass;
1890    }
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 getDefaultNailClass() {
1980        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 nailClass) {
2080        NailStats result = null;
2090        synchronized(allNailStats) {
2100            result = (NailStats) allNailStats.get(nailClass);
2110            if (result == null) {
2120                result = new NailStats(nailClass);
2130                allNailStats.put(nailClass, result);
214             }
2150        }
2160        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 nailClass) {
2260        NailStats stats = getOrCreateStatsFor(nailClass);
2270        stats.nailStarted();
2280    }
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 nailClass) {
2370        NailStats stats = (NailStats) allNailStats.get(nailClass);
2380        stats.nailFinished();
2390    }
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 getNailStats() {
2480        Map result = new java.util.TreeMap();
2490        synchronized(allNailStats) {
2500            for (Iterator i = allNailStats.keySet().iterator(); i.hasNext();) {
2510                Class nailclass = (Class) i.next();
2520                result.put(nailclass.getName(), ((NailStats) allNailStats.get(nailclass)).clone());
253             }
2540        }
2550        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() {
2630        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) {
2830        synchronized(this) {
2840            if (shutdown) return;
2850            shutdown = true;
2860        }
287         
288         try {
2890            serversocket.close();
2900        } catch (Throwable toDiscard) {}
291         
2920        sessionPool.shutdown();
293         
2940        Class[] argTypes = new Class[1];
2950        argTypes[0] = NGServer.class;
2960        Object[] argValues = new Object[1];
2970        argValues[0] = this;
298         
299         // make sure that all aliased classes have associated nailstats
300         // so they can be shut down.
3010        for (Iterator i = getAliasManager().getAliases().iterator(); i.hasNext();) {
3020            Alias alias = (Alias) i.next();
3030            getOrCreateStatsFor(alias.getAliasedClass());
304         }
305         
3060        synchronized(allNailStats) {
3070            for (Iterator i = allNailStats.values().iterator(); i.hasNext();) {
3080                NailStats ns = (NailStats) i.next();
3090                Class 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 {
3140                    Method nailShutdown = nailClass.getMethod("nailShutdown", argTypes);
3150                    nailShutdown.invoke(null, argValues);
3160                } catch (Throwable toDiscard) {}
317             }
3180        }
319         
320         // restore system streams
3210        System.setIn(in);
3220        System.setOut(out);
3230        System.setErr(err);
324         
3250        System.setSecurityManager(originalSecurityManager);
326         
3270        if (exitVM) {
3280            System.exit(0);
329         }
3300    }
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() {
3370        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() {
3450        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() {
3530        running = true;
3540        NGSession sessionOnDeck = null;
355         
3560        originalSecurityManager = System.getSecurityManager();
3570        System.setSecurityManager(
358                 new NGSecurityManager(
359                         originalSecurityManager));
360   
361  
3620        synchronized(System.in) {
3630            if (!(System.in instanceof ThreadLocalInputStream)) {
3640                System.setIn(new ThreadLocalInputStream(in));
3650                System.setOut(new ThreadLocalPrintStream(out));
3660                System.setErr(new ThreadLocalPrintStream(err));
367             }
3680        }
369         
370         try {
3710            if (addr == null) {
3720                serversocket = new ServerSocket(port);
373             } else {
3740                serversocket = new ServerSocket(port, 0, addr);
375             }
376             
3770            while (!shutdown) {
3780                sessionOnDeck = sessionPool.take();
3790                Socket socket = serversocket.accept();
3800                sessionOnDeck.run(socket);
381             }
382  
3830        } catch (Throwable 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.
3870            if (!shutdown) {
3880                t.printStackTrace();
389             }
3900        }
3910        if (sessionOnDeck != null) {
3920            sessionOnDeck.shutdown();
393         }
3940        running = false;
3950    }
396     
397     private static void usage() {
3980        System.err.println("Usage: java com.martiansoftware.nailgun.NGServer");
3990        System.err.println(" or: java com.martiansoftware.nailgun.NGServer port");
4000        System.err.println(" or: java com.martiansoftware.nailgun.NGServer IPAddress");
4010        System.err.println(" or: java com.martiansoftware.nailgun.NGServer IPAddress:port");
4020    }
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[] args) throws NumberFormatException, UnknownHostException {
412  
4130        if (args.length > 1) {
4140            usage();
4150            return;
416         }
417  
418         // null server address means bind to everything local
4190        InetAddress serverAddress = null;
4200        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
4260        if (args.length != 0) {
4270            String[] argParts = args[0].split(":");
4280            String addrPart = null;
4290            String portPart = null;
4300            if (argParts.length == 2) {
4310                addrPart = argParts[0];
4320                portPart = argParts[1];
4330            } else if (argParts[0].indexOf('.') >= 0) {
4340                addrPart = argParts[0];
435             } else {
4360                portPart = argParts[0];
437             }
4380            if (addrPart != null) {
4390                serverAddress = InetAddress.getByName(addrPart);
440             }
4410            if (portPart != null) {
4420                port = Integer.parseInt(portPart);
443             }
444         }
445  
4460        NGServer server = new NGServer(serverAddress, port);
4470        Thread t = new Thread(server);
4480        t.setName("NGServer(" + serverAddress + ", " + port + ")");
4490        t.start();
450  
4510        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.
4560        int runningPort = server.getPort();
4570        while (runningPort == 0) {
4580            try { Thread.sleep(50); } catch (Throwable toIgnore) {}
4590            runningPort = server.getPort();
460         }
461         
4620        System.out.println("NGServer started on "
463                             + ((serverAddress == null)
464                                 ? "all interfaces"
465                                 : serverAddress.getHostAddress())
466                             + ", port "
467                             + runningPort
468                             + ".");
4690    }
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 {
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 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 }

this report was generated by version 1.0.5 of jcoverage.
visit www.jcoverage.com for updates.

copyright © 2003, jcoverage ltd. all rights reserved.
Java is a trademark of Sun Microsystems, Inc. in the United States and other countries.