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                 try(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         
139         public void addPath (Resource path)
140         {
141             if (path == null || !path.exists())
142                 throw new IllegalStateException ("No such path: "+path);
143             _classpath.add(path.getURL());
144         }
145         
146         
147         public URL[] asArray ()
148         {
149             return _classpath.toArray(new URL[_classpath.size()]);
150         }
151     }
152     
153     
154     
155 
156     /**
157      * 
158      */
159     public Runner()
160     {
161 
162     }
163 
164 
165     /**
166      * Generate helpful usage message and exit
167      * 
168      * @param error
169      */
170     public void usage(String error)
171     {
172         if (error!=null)
173             System.err.println("ERROR: "+error);
174         System.err.println("Usage: java [-Djetty.home=dir] -jar jetty-runner.jar [--help|--version] [ server opts] [[ context opts] context ...] ");
175         System.err.println("Server opts:");
176         System.err.println(" --version                           - display version and exit");
177         System.err.println(" --log file                          - request log filename (with optional 'yyyy_mm_dd' wildcard");
178         System.err.println(" --out file                          - info/warn/debug log filename (with optional 'yyyy_mm_dd' wildcard");
179         System.err.println(" --host name|ip                      - interface to listen on (default is all interfaces)");
180         System.err.println(" --port n                            - port to listen on (default 8080)");
181         System.err.println(" --stop-port n                       - port to listen for stop command");
182         System.err.println(" --stop-key n                        - security string for stop command (required if --stop-port is present)");
183         System.err.println(" [--jar file]*n                      - each tuple specifies an extra jar to be added to the classloader");
184         System.err.println(" [--lib dir]*n                       - each tuple specifies an extra directory of jars to be added to the classloader");
185         System.err.println(" [--classes dir]*n                   - each tuple specifies an extra directory of classes to be added to the classloader");
186         System.err.println(" --stats [unsecure|realm.properties] - enable stats gathering servlet context");
187         System.err.println(" [--config file]*n                   - each tuple specifies the name of a jetty xml config file to apply (in the order defined)");
188         System.err.println("Context opts:");
189         System.err.println(" [[--path /path] context]*n          - WAR file, web app dir or context xml file, optionally with a context path");                     
190         System.exit(1);
191     }
192 
193     
194     /**
195      * Generate version message and exit
196      */
197     public void version ()
198     {
199         System.err.println("org.eclipse.jetty.runner.Runner: "+Server.getVersion());
200         System.exit(1);
201     }
202     
203     
204     
205     /**
206      * Configure a jetty instance and deploy the webapps presented as args
207      * 
208      * @param args
209      * @throws Exception
210      */
211     public void configure(String[] args) throws Exception
212     {
213         // handle classpath bits first so we can initialize the log mechanism.
214         for (int i=0;i<args.length;i++)
215         {
216             if ("--lib".equals(args[i]))
217             {
218                 try(Resource lib = Resource.newResource(args[++i]);)
219                 {
220                     if (!lib.exists() || !lib.isDirectory())
221                         usage("No such lib directory "+lib);
222                     _classpath.addJars(lib);
223                 }
224             }
225             else if ("--jar".equals(args[i]))
226             {
227                 try(Resource jar = Resource.newResource(args[++i]);)
228                 {
229                     if (!jar.exists() || jar.isDirectory())
230                         usage("No such jar "+jar);
231                     _classpath.addPath(jar);
232                 }
233             }
234             else if ("--classes".equals(args[i]))
235             {
236                 try(Resource classes = Resource.newResource(args[++i]);)
237                 {
238                     if (!classes.exists() || !classes.isDirectory())
239                         usage("No such classes directory "+classes);
240                     _classpath.addPath(classes);
241                 }
242             }
243             else if (args[i].startsWith("--"))
244                 i++;
245         }
246 
247         initClassLoader();
248 
249         LOG.info("Runner");
250         LOG.debug("Runner classpath {}",_classpath);
251 
252         String contextPath = __defaultContextPath;
253         boolean contextPathSet = false;
254         int port = __defaultPort;
255         String host = null;
256         int stopPort = 0;
257         String stopKey = null;
258 
259         boolean runnerServerInitialized = false;
260 
261         for (int i=0;i<args.length;i++)
262         {
263             if ("--port".equals(args[i]))
264                 port=Integer.parseInt(args[++i]);
265             else if ("--host".equals(args[i]))
266                 host=args[++i];
267             else if ("--stop-port".equals(args[i]))
268                 stopPort=Integer.parseInt(args[++i]);
269             else if ("--stop-key".equals(args[i]))
270                 stopKey=args[++i];
271             else if ("--log".equals(args[i]))
272                 _logFile=args[++i];
273             else if ("--out".equals(args[i]))
274             {
275                 String outFile=args[++i];
276                 PrintStream out = new PrintStream(new RolloverFileOutputStream(outFile,true,-1));
277                 LOG.info("Redirecting stderr/stdout to "+outFile);
278                 System.setErr(out);
279                 System.setOut(out);
280             }
281             else if ("--path".equals(args[i]))
282             {
283                 contextPath=args[++i];
284                 contextPathSet=true;
285             }
286             else if ("--config".equals(args[i]))
287             {
288                 if (_configFiles == null)
289                     _configFiles = new ArrayList<String>();
290                 _configFiles.add(args[++i]);
291             }
292             else if ("--lib".equals(args[i]))
293             {
294                 ++i;//skip
295             }
296             else if ("--jar".equals(args[i]))
297             {
298                 ++i; //skip
299             }
300             else if ("--classes".equals(args[i]))
301             {
302                 ++i;//skip
303             }
304             else if ("--stats".equals( args[i]))
305             {
306                 _enableStats = true;
307                 _statsPropFile = args[++i];
308                 _statsPropFile = ("unsecure".equalsIgnoreCase(_statsPropFile)?null:_statsPropFile);
309             }
310             else // process contexts
311             {
312                 if (!runnerServerInitialized) // log handlers not registered, server maybe not created, etc
313                 {
314                     if (_server == null) // server not initialized yet
315                     {
316                         // build the server
317                         _server = new Server();
318                     }
319 
320                     //apply jetty config files if there are any
321                     if (_configFiles != null)
322                     {
323                         for (String cfg:_configFiles)
324                         {
325                             try (Resource resource=Resource.newResource(cfg))
326                             {
327                                 XmlConfiguration xmlConfiguration = new XmlConfiguration(resource.getURL());
328                                 xmlConfiguration.configure(_server);
329                             }
330                         }
331                     }
332 
333                     //check that everything got configured, and if not, make the handlers
334                     HandlerCollection handlers = (HandlerCollection) _server.getChildHandlerByClass(HandlerCollection.class);
335                     if (handlers == null)
336                     {
337                         handlers = new HandlerCollection();
338                         _server.setHandler(handlers);
339                     }
340                     
341                     //check if contexts already configured
342                     _contexts = (ContextHandlerCollection) handlers.getChildHandlerByClass(ContextHandlerCollection.class);
343                     if (_contexts == null)
344                     {
345                         _contexts = new ContextHandlerCollection();
346                         prependHandler(_contexts, handlers);
347                     }
348                     
349                   
350                     if (_enableStats)
351                     {
352                         //if no stats handler already configured
353                         if (handlers.getChildHandlerByClass(StatisticsHandler.class) == null)
354                         {
355                             StatisticsHandler statsHandler = new StatisticsHandler();
356                             
357                             
358                             Handler oldHandler = _server.getHandler();
359                             statsHandler.setHandler(oldHandler);
360                             _server.setHandler(statsHandler);
361                          
362                             
363                             ServletContextHandler statsContext = new ServletContextHandler(_contexts, "/stats");
364                             statsContext.addServlet(new ServletHolder(new StatisticsServlet()), "/");
365                             statsContext.setSessionHandler(new SessionHandler());
366                             if (_statsPropFile != null)
367                             {
368                                 HashLoginService loginService = new HashLoginService("StatsRealm", _statsPropFile);
369                                 Constraint constraint = new Constraint();
370                                 constraint.setName("Admin Only");
371                                 constraint.setRoles(new String[]{"admin"});
372                                 constraint.setAuthenticate(true);
373 
374                                 ConstraintMapping cm = new ConstraintMapping();
375                                 cm.setConstraint(constraint);
376                                 cm.setPathSpec("/*");
377 
378                                 ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
379                                 securityHandler.setLoginService(loginService);
380                                 securityHandler.setConstraintMappings(Collections.singletonList(cm));
381                                 securityHandler.setAuthenticator(new BasicAuthenticator());
382                                 statsContext.setSecurityHandler(securityHandler);
383                             }
384                         }
385                     }
386                    
387                     //ensure a DefaultHandler is present
388                     if (handlers.getChildHandlerByClass(DefaultHandler.class) == null)
389                     {
390                         handlers.addHandler(new DefaultHandler());
391                     }
392                   
393                     //ensure a log handler is present
394                     _logHandler = (RequestLogHandler)handlers.getChildHandlerByClass( RequestLogHandler.class );
395                     if ( _logHandler == null )
396                     {
397                         _logHandler = new RequestLogHandler();
398                         handlers.addHandler( _logHandler );
399                     }
400                     
401 
402                     //check a connector is configured to listen on
403                     Connector[] connectors = _server.getConnectors();
404                     if (connectors == null || connectors.length == 0)
405                     {
406                         ServerConnector connector = new ServerConnector(_server);
407                         connector.setPort(port);
408                         if (host != null)
409                         connector.setHost(host);
410                         _server.addConnector(connector);
411                         if (_enableStats)
412                             connector.addBean(new ConnectorStatistics());
413                     }
414                     else
415                     {
416                         if (_enableStats)
417                         {
418                             for (int j=0; j<connectors.length; j++)
419                             {
420                                 ((AbstractConnector)connectors[j]).addBean(new ConnectorStatistics());
421                             }
422                         }
423                     }
424 
425                     runnerServerInitialized = true;
426                 }
427 
428                 // Create a context
429                 try(Resource ctx = Resource.newResource(args[i]);)
430                 {
431                     if (!ctx.exists())
432                         usage("Context '"+ctx+"' does not exist");
433 
434                     if (contextPathSet && !(contextPath.startsWith("/")))
435                         contextPath = "/"+contextPath;
436 
437                     // Configure the context
438                     if (!ctx.isDirectory() && ctx.toString().toLowerCase().endsWith(".xml"))
439                     {
440                         // It is a context config file
441                         XmlConfiguration xmlConfiguration=new XmlConfiguration(ctx.getURL());
442                         xmlConfiguration.getIdMap().put("Server",_server);
443                         ContextHandler handler=(ContextHandler)xmlConfiguration.configure();
444                         if (contextPathSet)
445                             handler.setContextPath(contextPath);
446                         _contexts.addHandler(handler);                   
447                         handler.setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern", __containerIncludeJarPattern); 
448                     }
449                     else
450                     {
451                         // assume it is a WAR file
452                         WebAppContext webapp = new WebAppContext(_contexts,ctx.toString(),contextPath);
453                         webapp.setConfigurationClasses(__plusConfigurationClasses);
454                         webapp.setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",
455                             __containerIncludeJarPattern);
456                     }
457                 }
458                 //reset
459                 contextPathSet = false;
460                 contextPath = __defaultContextPath;
461             }
462         }
463 
464         if (_server==null)
465             usage("No Contexts defined");
466         _server.setStopAtShutdown(true);
467 
468         switch ((stopPort > 0 ? 1 : 0) + (stopKey != null ? 2 : 0))
469         {
470             case 1:
471                 usage("Must specify --stop-key when --stop-port is specified");
472                 break;
473                 
474             case 2:
475                 usage("Must specify --stop-port when --stop-key is specified");
476                 break;
477                 
478             case 3:
479                 ShutdownMonitor monitor = ShutdownMonitor.getInstance();
480                 monitor.setPort(stopPort);
481                 monitor.setKey(stopKey);
482                 monitor.setExitVm(true);
483                 break;
484         }
485 
486         if (_logFile!=null)
487         {
488             NCSARequestLog requestLog = new NCSARequestLog(_logFile);
489             requestLog.setExtended(false);
490             _logHandler.setRequestLog(requestLog);
491         }
492     }
493     
494     
495     /**
496      * @param handler
497      * @param handlers
498      */
499     protected void prependHandler (Handler handler, HandlerCollection handlers)
500     {
501         if (handler == null || handlers == null)
502             return;
503         
504        Handler[] existing = handlers.getChildHandlers();
505        Handler[] children = new Handler[existing.length + 1];
506        children[0] = handler;
507        System.arraycopy(existing, 0, children, 1, existing.length);
508        handlers.setHandlers(children);
509     }
510 
511     
512     
513 
514     /**
515      * @throws Exception
516      */
517     public void run() throws Exception
518     {
519         _server.start();
520         _server.join();
521     }
522 
523 
524     /**
525      * Establish a classloader with custom paths (if any)
526      */
527     protected void initClassLoader()
528     {
529         URL[] paths = _classpath.asArray();
530              
531         if (_classLoader==null && paths !=null && paths.length > 0)
532         {
533             ClassLoader context=Thread.currentThread().getContextClassLoader();
534 
535             if (context==null)
536                 _classLoader=new URLClassLoader(paths);
537             else
538                 _classLoader=new URLClassLoader(paths, context);
539 
540             Thread.currentThread().setContextClassLoader(_classLoader);
541         }
542     }
543 
544 
545 
546 
547     /**
548      * @param args
549      */
550     public static void main(String[] args)
551     {
552         Runner runner = new Runner();
553 
554         try
555         {
556             if (args.length>0&&args[0].equalsIgnoreCase("--help"))
557             {
558                 runner.usage(null);
559             }
560             else if (args.length>0&&args[0].equalsIgnoreCase("--version"))
561             {
562                 runner.version();
563             }
564             else
565             {
566                 runner.configure(args);
567                 runner.run();
568             }
569         }
570         catch (Exception e)
571         {
572             e.printStackTrace();
573             runner.usage(null);
574         }
575     }
576 }