View Javadoc

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