View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.runner;
20  
21  import java.io.IOException;
22  import java.io.PrintStream;
23  import java.net.MalformedURLException;
24  import java.net.URL;
25  import java.net.URLClassLoader;
26  import java.util.ArrayList;
27  import java.util.Collections;
28  import java.util.List;
29  
30  import org.eclipse.jetty.security.ConstraintMapping;
31  import org.eclipse.jetty.security.ConstraintSecurityHandler;
32  import org.eclipse.jetty.security.HashLoginService;
33  import org.eclipse.jetty.security.authentication.BasicAuthenticator;
34  import org.eclipse.jetty.server.AbstractConnector;
35  import org.eclipse.jetty.server.Connector;
36  import org.eclipse.jetty.server.ConnectorStatistics;
37  import org.eclipse.jetty.server.Handler;
38  import org.eclipse.jetty.server.NCSARequestLog;
39  import org.eclipse.jetty.server.Server;
40  import org.eclipse.jetty.server.ServerConnector;
41  import org.eclipse.jetty.server.ShutdownMonitor;
42  import org.eclipse.jetty.server.handler.ContextHandler;
43  import org.eclipse.jetty.server.handler.ContextHandlerCollection;
44  import org.eclipse.jetty.server.handler.DefaultHandler;
45  import org.eclipse.jetty.server.handler.HandlerCollection;
46  import org.eclipse.jetty.server.handler.RequestLogHandler;
47  import org.eclipse.jetty.server.handler.StatisticsHandler;
48  import org.eclipse.jetty.server.session.SessionHandler;
49  import org.eclipse.jetty.servlet.ServletContextHandler;
50  import org.eclipse.jetty.servlet.ServletHolder;
51  import org.eclipse.jetty.servlet.StatisticsServlet;
52  import org.eclipse.jetty.util.RolloverFileOutputStream;
53  import org.eclipse.jetty.util.log.Log;
54  import org.eclipse.jetty.util.log.Logger;
55  import org.eclipse.jetty.util.resource.Resource;
56  import org.eclipse.jetty.util.security.Constraint;
57  import org.eclipse.jetty.webapp.WebAppContext;
58  import org.eclipse.jetty.xml.XmlConfiguration;
59  
60  
61  
62  /**
63   * Runner
64   *
65   * Combine jetty classes into a single executable jar and run webapps based on the args to it.
66   * 
67   */
68  public class Runner
69  {
70      private static final Logger LOG = Log.getLogger(Runner.class);
71  
72      public static final String[] __plusConfigurationClasses = new String[] {
73              org.eclipse.jetty.webapp.WebInfConfiguration.class.getCanonicalName(),
74              org.eclipse.jetty.webapp.WebXmlConfiguration.class.getCanonicalName(),
75              org.eclipse.jetty.webapp.MetaInfConfiguration.class.getCanonicalName(),
76              org.eclipse.jetty.webapp.FragmentConfiguration.class.getCanonicalName(),
77              org.eclipse.jetty.plus.webapp.EnvConfiguration.class.getCanonicalName(),
78              org.eclipse.jetty.plus.webapp.PlusConfiguration.class.getCanonicalName(),
79              org.eclipse.jetty.annotations.AnnotationConfiguration.class.getCanonicalName(),
80              org.eclipse.jetty.webapp.JettyWebXmlConfiguration.class.getCanonicalName()
81              };
82      public static final String __containerIncludeJarPattern =  ".*/jetty-runner-[^/]*\\.jar$";
83      public static final String __defaultContextPath = "/";
84      public static final int __defaultPort = 8080;
85  
86      protected Server _server;
87      protected URLClassLoader _classLoader;
88      protected Classpath _classpath = new Classpath();
89      protected ContextHandlerCollection _contexts;
90      protected RequestLogHandler _logHandler;
91      protected String _logFile;
92      protected ArrayList<String> _configFiles;
93      protected boolean _enableStats=false;
94      protected String _statsPropFile;
95  
96  
97      
98      /**
99       * Classpath
100      *
101      *
102      */
103     public class Classpath 
104     {
105         private  List<URL> _classpath = new ArrayList<URL>();
106         
107         public void addJars (Resource lib) throws MalformedURLException, IOException
108         {
109             if (lib == null || !lib.exists())
110                 throw new IllegalStateException ("No such lib: "+lib);
111             
112             String[] list = lib.list();
113             if (list==null)
114                 return;
115 
116             for (String path : list)
117             {
118                 if (".".equals(path) || "..".equals(path))
119                     continue;
120 
121                 Resource item = lib.addPath(path);
122 
123                 if (item.isDirectory())
124                     addJars(item);
125                 else
126                 {
127                     if (path.toLowerCase().endsWith(".jar") ||
128                         path.toLowerCase().endsWith(".zip"))
129                     {
130                         URL url = item.getURL();
131                         _classpath.add(url);
132                     }
133                 }
134             }
135         }
136         
137         
138         public void addPath (Resource path)
139         {
140             if (path == null || !path.exists())
141                 throw new IllegalStateException ("No such path: "+path);
142             _classpath.add(path.getURL());
143         }
144         
145         
146         public URL[] asArray ()
147         {
148             return _classpath.toArray(new URL[_classpath.size()]);
149         }
150     }
151     
152     
153     
154 
155     /**
156      * 
157      */
158     public Runner()
159     {
160 
161     }
162 
163 
164     /**
165      * Generate helpful usage message and exit
166      * 
167      * @param error
168      */
169     public void usage(String error)
170     {
171         if (error!=null)
172             System.err.println("ERROR: "+error);
173         System.err.println("Usage: java [-Djetty.home=dir] -jar jetty-runner.jar [--help|--version] [ server opts] [[ context opts] context ...] ");
174         System.err.println("Server opts:");
175         System.err.println(" --version                           - display version and exit");
176         System.err.println(" --log file                          - request log filename (with optional 'yyyy_mm_dd' wildcard");
177         System.err.println(" --out file                          - info/warn/debug log filename (with optional 'yyyy_mm_dd' wildcard");
178         System.err.println(" --host name|ip                      - interface to listen on (default is all interfaces)");
179         System.err.println(" --port n                            - port to listen on (default 8080)");
180         System.err.println(" --stop-port n                       - port to listen for stop command");
181         System.err.println(" --stop-key n                        - security string for stop command (required if --stop-port is present)");
182         System.err.println(" [--jar file]*n                      - each tuple specifies an extra jar to be added to the classloader");
183         System.err.println(" [--lib dir]*n                       - each tuple specifies an extra directory of jars to be added to the classloader");
184         System.err.println(" [--classes dir]*n                   - each tuple specifies an extra directory of classes to be added to the classloader");
185         System.err.println(" --stats [unsecure|realm.properties] - enable stats gathering servlet context");
186         System.err.println(" [--config file]*n                   - each tuple specifies the name of a jetty xml config file to apply (in the order defined)");
187         System.err.println("Context opts:");
188         System.err.println(" [[--path /path] context]*n          - WAR file, web app dir or context xml file, optionally with a context path");                     
189         System.exit(1);
190     }
191 
192     
193     /**
194      * Generate version message and exit
195      */
196     public void version ()
197     {
198         System.err.println("org.eclipse.jetty.runner.Runner: "+Server.getVersion());
199         System.exit(1);
200     }
201     
202     
203     
204     /**
205      * Configure a jetty instance and deploy the webapps presented as args
206      * 
207      * @param args
208      * @throws Exception
209      */
210     public void configure(String[] args) throws Exception
211     {
212         // handle classpath bits first so we can initialize the log mechanism.
213         for (int i=0;i<args.length;i++)
214         {
215             if ("--lib".equals(args[i]))
216             {
217                 Resource lib = Resource.newResource(args[++i]);
218                 if (!lib.exists() || !lib.isDirectory())
219                     usage("No such lib directory "+lib);
220                 _classpath.addJars(lib);
221             }
222             else if ("--jar".equals(args[i]))
223             {
224                 Resource jar = Resource.newResource(args[++i]);
225                 if (!jar.exists() || jar.isDirectory())
226                     usage("No such jar "+jar);
227                 _classpath.addPath(jar);
228             }
229             else if ("--classes".equals(args[i]))
230             {
231                 Resource classes = Resource.newResource(args[++i]);
232                 if (!classes.exists() || !classes.isDirectory())
233                     usage("No such classes directory "+classes);
234                 _classpath.addPath(classes);
235             }
236             else if (args[i].startsWith("--"))
237                 i++;
238         }
239 
240         initClassLoader();
241 
242         LOG.info("Runner");
243         LOG.debug("Runner classpath {}",_classpath);
244 
245         String contextPath = __defaultContextPath;
246         boolean contextPathSet = false;
247         int port = __defaultPort;
248         String host = null;
249         int stopPort = 0;
250         String stopKey = null;
251 
252         boolean runnerServerInitialized = false;
253 
254         for (int i=0;i<args.length;i++)
255         {
256             if ("--port".equals(args[i]))
257                 port=Integer.parseInt(args[++i]);
258             else if ("--host".equals(args[i]))
259                 host=args[++i];
260             else if ("--stop-port".equals(args[i]))
261                 stopPort=Integer.parseInt(args[++i]);
262             else if ("--stop-key".equals(args[i]))
263                 stopKey=args[++i];
264             else if ("--log".equals(args[i]))
265                 _logFile=args[++i];
266             else if ("--out".equals(args[i]))
267             {
268                 String outFile=args[++i];
269                 PrintStream out = new PrintStream(new RolloverFileOutputStream(outFile,true,-1));
270                 LOG.info("Redirecting stderr/stdout to "+outFile);
271                 System.setErr(out);
272                 System.setOut(out);
273             }
274             else if ("--path".equals(args[i]))
275             {
276                 contextPath=args[++i];
277                 contextPathSet=true;
278             }
279             else if ("--config".equals(args[i]))
280             {
281                 if (_configFiles == null)
282                     _configFiles = new ArrayList<String>();
283                 _configFiles.add(args[++i]);
284             }
285             else if ("--lib".equals(args[i]))
286             {
287                 ++i;//skip
288             }
289             else if ("--jar".equals(args[i]))
290             {
291                 ++i; //skip
292             }
293             else if ("--classes".equals(args[i]))
294             {
295                 ++i;//skip
296             }
297             else if ("--stats".equals( args[i]))
298             {
299                 _enableStats = true;
300                 _statsPropFile = args[++i];
301                 _statsPropFile = ("unsecure".equalsIgnoreCase(_statsPropFile)?null:_statsPropFile);
302             }
303             else // process contexts
304             {
305                 if (!runnerServerInitialized) // log handlers not registered, server maybe not created, etc
306                 {
307                     if (_server == null) // server not initialized yet
308                     {
309                         // build the server
310                         _server = new Server();
311                     }
312 
313                     //apply jetty config files if there are any
314                     if (_configFiles != null)
315                     {
316                         for (String cfg:_configFiles)
317                         {
318                             XmlConfiguration xmlConfiguration = new XmlConfiguration(Resource.newResource(cfg).getURL());
319                             xmlConfiguration.configure(_server);
320                         }
321                     }
322 
323                     //check that everything got configured, and if not, make the handlers
324                     HandlerCollection handlers = (HandlerCollection) _server.getChildHandlerByClass(HandlerCollection.class);
325                     if (handlers == null)
326                     {
327                         handlers = new HandlerCollection();
328                         _server.setHandler(handlers);
329                     }
330                     
331                     //check if contexts already configured
332                     _contexts = (ContextHandlerCollection) handlers.getChildHandlerByClass(ContextHandlerCollection.class);
333                     if (_contexts == null)
334                     {
335                         _contexts = new ContextHandlerCollection();
336                         prependHandler(_contexts, handlers);
337                     }
338                     
339                   
340                     if (_enableStats)
341                     {
342                         //if no stats handler already configured
343                         if (handlers.getChildHandlerByClass(StatisticsHandler.class) == null)
344                         {
345                             StatisticsHandler statsHandler = new StatisticsHandler();
346                             
347                             
348                             Handler oldHandler = _server.getHandler();
349                             statsHandler.setHandler(oldHandler);
350                             _server.setHandler(statsHandler);
351                          
352                             
353                             ServletContextHandler statsContext = new ServletContextHandler(_contexts, "/stats");
354                             statsContext.addServlet(new ServletHolder(new StatisticsServlet()), "/");
355                             statsContext.setSessionHandler(new SessionHandler());
356                             if (_statsPropFile != null)
357                             {
358                                 HashLoginService loginService = new HashLoginService("StatsRealm", _statsPropFile);
359                                 Constraint constraint = new Constraint();
360                                 constraint.setName("Admin Only");
361                                 constraint.setRoles(new String[]{"admin"});
362                                 constraint.setAuthenticate(true);
363 
364                                 ConstraintMapping cm = new ConstraintMapping();
365                                 cm.setConstraint(constraint);
366                                 cm.setPathSpec("/*");
367 
368                                 ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
369                                 securityHandler.setLoginService(loginService);
370                                 securityHandler.setConstraintMappings(Collections.singletonList(cm));
371                                 securityHandler.setAuthenticator(new BasicAuthenticator());
372                                 statsContext.setSecurityHandler(securityHandler);
373                             }
374                         }
375                     }
376                    
377                     //ensure a DefaultHandler is present
378                     if (handlers.getChildHandlerByClass(DefaultHandler.class) == null)
379                     {
380                         handlers.addHandler(new DefaultHandler());
381                     }
382                   
383                     //ensure a log handler is present
384                     _logHandler = (RequestLogHandler)handlers.getChildHandlerByClass( RequestLogHandler.class );
385                     if ( _logHandler == null )
386                     {
387                         _logHandler = new RequestLogHandler();
388                         handlers.addHandler( _logHandler );
389                     }
390                     
391 
392                     //check a connector is configured to listen on
393                     Connector[] connectors = _server.getConnectors();
394                     if (connectors == null || connectors.length == 0)
395                     {
396                         ServerConnector connector = new ServerConnector(_server);
397                         connector.setPort(port);
398                         if (host != null)
399                         connector.setHost(host);
400                         _server.addConnector(connector);
401                         if (_enableStats)
402                             connector.addBean(new ConnectorStatistics());
403                     }
404                     else
405                     {
406                         if (_enableStats)
407                         {
408                             for (int j=0; j<connectors.length; j++)
409                             {
410                                 ((AbstractConnector)connectors[j]).addBean(new ConnectorStatistics());
411                             }
412                         }
413                     }
414 
415                     runnerServerInitialized = true;
416                 }
417 
418                 // Create a context
419                 Resource ctx = Resource.newResource(args[i]);
420                 if (!ctx.exists())
421                     usage("Context '"+ctx+"' does not exist");
422                 
423                 if (contextPathSet && !(contextPath.startsWith("/")))
424                     contextPath = "/"+contextPath;
425 
426                 // Configure the context
427                 if (!ctx.isDirectory() && ctx.toString().toLowerCase().endsWith(".xml"))
428                 {
429                     // It is a context config file
430                     XmlConfiguration xmlConfiguration=new XmlConfiguration(ctx.getURL());
431                     xmlConfiguration.getIdMap().put("Server",_server);
432                     ContextHandler handler=(ContextHandler)xmlConfiguration.configure();
433                     if (contextPathSet)
434                         handler.setContextPath(contextPath);
435                     _contexts.addHandler(handler);                   
436                     handler.setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern", __containerIncludeJarPattern); 
437                 }
438                 else
439                 {
440                     // assume it is a WAR file
441                     WebAppContext webapp = new WebAppContext(_contexts,ctx.toString(),contextPath);
442                     webapp.setConfigurationClasses(__plusConfigurationClasses);
443                     webapp.setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",
444                                         __containerIncludeJarPattern);
445                 }
446                 
447                 //reset
448                 contextPathSet = false;
449                 contextPath = __defaultContextPath;
450             }
451         }
452 
453         if (_server==null)
454             usage("No Contexts defined");
455         _server.setStopAtShutdown(true);
456 
457         switch ((stopPort > 0 ? 1 : 0) + (stopKey != null ? 2 : 0))
458         {
459             case 1:
460                 usage("Must specify --stop-key when --stop-port is specified");
461                 break;
462                 
463             case 2:
464                 usage("Must specify --stop-port when --stop-key is specified");
465                 break;
466                 
467             case 3:
468                 ShutdownMonitor monitor = ShutdownMonitor.getInstance();
469                 monitor.setPort(stopPort);
470                 monitor.setKey(stopKey);
471                 monitor.setExitVm(true);
472                 break;
473         }
474 
475         if (_logFile!=null)
476         {
477             NCSARequestLog requestLog = new NCSARequestLog(_logFile);
478             requestLog.setExtended(false);
479             _logHandler.setRequestLog(requestLog);
480         }
481     }
482     
483     
484     /**
485      * @param handler
486      * @param handlers
487      */
488     protected void prependHandler (Handler handler, HandlerCollection handlers)
489     {
490         if (handler == null || handlers == null)
491             return;
492         
493        Handler[] existing = handlers.getChildHandlers();
494        Handler[] children = new Handler[existing.length + 1];
495        children[0] = handler;
496        System.arraycopy(existing, 0, children, 1, existing.length);
497        handlers.setHandlers(children);
498     }
499 
500     
501     
502 
503     /**
504      * @throws Exception
505      */
506     public void run() throws Exception
507     {
508         _server.start();
509         _server.join();
510     }
511 
512 
513     /**
514      * Establish a classloader with custom paths (if any)
515      */
516     protected void initClassLoader()
517     {
518         URL[] paths = _classpath.asArray();
519              
520         if (_classLoader==null && paths !=null && paths.length > 0)
521         {
522             ClassLoader context=Thread.currentThread().getContextClassLoader();
523 
524             if (context==null)
525                 _classLoader=new URLClassLoader(paths);
526             else
527                 _classLoader=new URLClassLoader(paths, context);
528 
529             Thread.currentThread().setContextClassLoader(_classLoader);
530         }
531     }
532 
533 
534 
535 
536     /**
537      * @param args
538      */
539     public static void main(String[] args)
540     {
541         Runner runner = new Runner();
542 
543         try
544         {
545             if (args.length>0&&args[0].equalsIgnoreCase("--help"))
546             {
547                 runner.usage(null);
548             }
549             else if (args.length>0&&args[0].equalsIgnoreCase("--version"))
550             {
551                 runner.version();
552             }
553             else
554             {
555                 runner.configure(args);
556                 runner.run();
557             }
558         }
559         catch (Exception e)
560         {
561             e.printStackTrace();
562             runner.usage(null);
563         }
564     }
565 }