View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 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.URL;
24  import java.net.URLClassLoader;
25  import java.util.ArrayList;
26  import java.util.Collections;
27  import java.util.List;
28  import java.util.Locale;
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   * Runner
63   *
64   * Combine jetty classes into a single executable jar and run webapps based on the args to it.
65   *
66   */
67  public class Runner
68  {
69      private static final Logger LOG = Log.getLogger(Runner.class);
70  
71      public static final String[] __plusConfigurationClasses = new String[] {
72              org.eclipse.jetty.webapp.WebInfConfiguration.class.getCanonicalName(),
73              org.eclipse.jetty.webapp.WebXmlConfiguration.class.getCanonicalName(),
74              org.eclipse.jetty.webapp.MetaInfConfiguration.class.getCanonicalName(),
75              org.eclipse.jetty.webapp.FragmentConfiguration.class.getCanonicalName(),
76              org.eclipse.jetty.plus.webapp.EnvConfiguration.class.getCanonicalName(),
77              org.eclipse.jetty.plus.webapp.PlusConfiguration.class.getCanonicalName(),
78              org.eclipse.jetty.annotations.AnnotationConfiguration.class.getCanonicalName(),
79              org.eclipse.jetty.webapp.JettyWebXmlConfiguration.class.getCanonicalName()
80              };
81      public static final String __containerIncludeJarPattern =  ".*/jetty-runner-[^/]*\\.jar$";
82      public static final String __defaultContextPath = "/";
83      public static final int __defaultPort = 8080;
84  
85      protected Server _server;
86      protected URLClassLoader _classLoader;
87      protected Classpath _classpath = new Classpath();
88      protected ContextHandlerCollection _contexts;
89      protected RequestLogHandler _logHandler;
90      protected String _logFile;
91      protected ArrayList<String> _configFiles;
92      protected boolean _enableStats=false;
93      protected String _statsPropFile;
94  
95  
96  
97      /**
98       * Classpath
99       *
100      *
101      */
102     public class Classpath
103     {
104         private  List<URL> _classpath = new ArrayList<>();
105 
106         public void addJars (Resource lib) throws IOException
107         {
108             if (lib == null || !lib.exists())
109                 throw new IllegalStateException ("No such lib: "+lib);
110 
111             String[] list = lib.list();
112             if (list==null)
113                 return;
114 
115             for (String path : list)
116             {
117                 if (".".equals(path) || "..".equals(path))
118                     continue;
119 
120                 try(Resource item = lib.addPath(path))
121                 {
122                     if (item.isDirectory())
123                         addJars(item);
124                     else
125                     {
126                         String lowerCasePath = path.toLowerCase(Locale.ENGLISH);
127                         if (lowerCasePath.endsWith(".jar") ||
128                             lowerCasePath.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             switch (args[i])
264             {
265                 case "--port":
266                     port = Integer.parseInt(args[++i]);
267                     break;
268                 case "--host":
269                     host = args[++i];
270                     break;
271                 case "--stop-port":
272                     stopPort = Integer.parseInt(args[++i]);
273                     break;
274                 case "--stop-key":
275                     stopKey = args[++i];
276                     break;
277                 case "--log":
278                     _logFile = args[++i];
279                     break;
280                 case "--out":
281                     String outFile = args[++i];
282                     PrintStream out = new PrintStream(new RolloverFileOutputStream(outFile, true, -1));
283                     LOG.info("Redirecting stderr/stdout to " + outFile);
284                     System.setErr(out);
285                     System.setOut(out);
286                     break;
287                 case "--path":
288                     contextPath = args[++i];
289                     contextPathSet = true;
290                     break;
291                 case "--config":
292                     if (_configFiles == null)
293                         _configFiles = new ArrayList<>();
294                     _configFiles.add(args[++i]);
295                     break;
296                 case "--lib":
297                     ++i;//skip
298 
299                     break;
300                 case "--jar":
301                     ++i; //skip
302 
303                     break;
304                 case "--classes":
305                     ++i;//skip
306 
307                     break;
308                 case "--stats":
309                     _enableStats = true;
310                     _statsPropFile = args[++i];
311                     _statsPropFile = ("unsecure".equalsIgnoreCase(_statsPropFile) ? null : _statsPropFile);
312                     break;
313                 default:
314 // process contexts
315 
316                     if (!runnerServerInitialized) // log handlers not registered, server maybe not created, etc
317                     {
318                         if (_server == null) // server not initialized yet
319                         {
320                             // build the server
321                             _server = new Server();
322                         }
323 
324                         //apply jetty config files if there are any
325                         if (_configFiles != null)
326                         {
327                             for (String cfg : _configFiles)
328                             {
329                                 try (Resource resource = Resource.newResource(cfg)) {
330                                     XmlConfiguration xmlConfiguration = new XmlConfiguration(resource.getURL());
331                                     xmlConfiguration.configure(_server);
332                                 }
333                             }
334                         }
335 
336                         //check that everything got configured, and if not, make the handlers
337                         HandlerCollection handlers = (HandlerCollection) _server.getChildHandlerByClass(HandlerCollection.class);
338                         if (handlers == null)
339                         {
340                             handlers = new HandlerCollection();
341                             _server.setHandler(handlers);
342                         }
343 
344                         //check if contexts already configured
345                         _contexts = (ContextHandlerCollection) handlers.getChildHandlerByClass(ContextHandlerCollection.class);
346                         if (_contexts == null)
347                         {
348                             _contexts = new ContextHandlerCollection();
349                             prependHandler(_contexts, handlers);
350                         }
351 
352 
353                         if (_enableStats)
354                         {
355                             //if no stats handler already configured
356                             if (handlers.getChildHandlerByClass(StatisticsHandler.class) == null) {
357                                 StatisticsHandler statsHandler = new StatisticsHandler();
358 
359 
360                                 Handler oldHandler = _server.getHandler();
361                                 statsHandler.setHandler(oldHandler);
362                                 _server.setHandler(statsHandler);
363 
364 
365                                 ServletContextHandler statsContext = new ServletContextHandler(_contexts, "/stats");
366                                 statsContext.addServlet(new ServletHolder(new StatisticsServlet()), "/");
367                                 statsContext.setSessionHandler(new SessionHandler());
368                                 if (_statsPropFile != null)
369                                 {
370                                     HashLoginService loginService = new HashLoginService("StatsRealm", _statsPropFile);
371                                     Constraint constraint = new Constraint();
372                                     constraint.setName("Admin Only");
373                                     constraint.setRoles(new String[]{"admin"});
374                                     constraint.setAuthenticate(true);
375 
376                                     ConstraintMapping cm = new ConstraintMapping();
377                                     cm.setConstraint(constraint);
378                                     cm.setPathSpec("/*");
379 
380                                     ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
381                                     securityHandler.setLoginService(loginService);
382                                     securityHandler.setConstraintMappings(Collections.singletonList(cm));
383                                     securityHandler.setAuthenticator(new BasicAuthenticator());
384                                     statsContext.setSecurityHandler(securityHandler);
385                                 }
386                             }
387                         }
388 
389                         //ensure a DefaultHandler is present
390                         if (handlers.getChildHandlerByClass(DefaultHandler.class) == null)
391                         {
392                             handlers.addHandler(new DefaultHandler());
393                         }
394 
395                         //ensure a log handler is present
396                         _logHandler = (RequestLogHandler) handlers.getChildHandlerByClass(RequestLogHandler.class);
397                         if (_logHandler == null)
398                         {
399                             _logHandler = new RequestLogHandler();
400                             handlers.addHandler(_logHandler);
401                         }
402 
403 
404                         //check a connector is configured to listen on
405                         Connector[] connectors = _server.getConnectors();
406                         if (connectors == null || connectors.length == 0)
407                         {
408                             ServerConnector connector = new ServerConnector(_server);
409                             connector.setPort(port);
410                             if (host != null)
411                                 connector.setHost(host);
412                             _server.addConnector(connector);
413                             if (_enableStats)
414                                 connector.addBean(new ConnectorStatistics());
415                         }
416                         else
417                         {
418                             if (_enableStats)
419                             {
420                                 for (Connector connector : connectors)
421                                 {
422                                     ((AbstractConnector) connector).addBean(new ConnectorStatistics());
423                                 }
424                             }
425                         }
426 
427                         runnerServerInitialized = true;
428                     }
429 
430                     // Create a context
431                     try (Resource ctx = Resource.newResource(args[i]))
432                     {
433                         if (!ctx.exists())
434                             usage("Context '" + ctx + "' does not exist");
435 
436                         if (contextPathSet && !(contextPath.startsWith("/")))
437                             contextPath = "/" + contextPath;
438 
439                         // Configure the context
440                         if (!ctx.isDirectory() && ctx.toString().toLowerCase(Locale.ENGLISH).endsWith(".xml"))
441                         {
442                             // It is a context config file
443                             XmlConfiguration xmlConfiguration = new XmlConfiguration(ctx.getURL());
444                             xmlConfiguration.getIdMap().put("Server", _server);
445                             ContextHandler handler = (ContextHandler) xmlConfiguration.configure();
446                             if (contextPathSet)
447                                 handler.setContextPath(contextPath);
448                             _contexts.addHandler(handler);
449                             handler.setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern", __containerIncludeJarPattern);
450                         }
451                         else
452                         {
453                             // assume it is a WAR file
454                             WebAppContext webapp = new WebAppContext(_contexts, ctx.toString(), contextPath);
455                             webapp.setConfigurationClasses(__plusConfigurationClasses);
456                             webapp.setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",
457                                     __containerIncludeJarPattern);
458                         }
459                     }
460                     //reset
461                     contextPathSet = false;
462                     contextPath = __defaultContextPath;
463                     break;
464             }
465         }
466 
467         if (_server==null)
468             usage("No Contexts defined");
469         _server.setStopAtShutdown(true);
470 
471         switch ((stopPort > 0 ? 1 : 0) + (stopKey != null ? 2 : 0))
472         {
473             case 1:
474                 usage("Must specify --stop-key when --stop-port is specified");
475                 break;
476 
477             case 2:
478                 usage("Must specify --stop-port when --stop-key is specified");
479                 break;
480 
481             case 3:
482                 ShutdownMonitor monitor = ShutdownMonitor.getInstance();
483                 monitor.setPort(stopPort);
484                 monitor.setKey(stopKey);
485                 monitor.setExitVm(true);
486                 break;
487         }
488 
489         if (_logFile!=null)
490         {
491             NCSARequestLog requestLog = new NCSARequestLog(_logFile);
492             requestLog.setExtended(false);
493             _logHandler.setRequestLog(requestLog);
494         }
495     }
496 
497 
498     /**
499      * @param handler
500      * @param handlers
501      */
502     protected void prependHandler (Handler handler, HandlerCollection handlers)
503     {
504         if (handler == null || handlers == null)
505             return;
506 
507        Handler[] existing = handlers.getChildHandlers();
508        Handler[] children = new Handler[existing.length + 1];
509        children[0] = handler;
510        System.arraycopy(existing, 0, children, 1, existing.length);
511        handlers.setHandlers(children);
512     }
513 
514 
515 
516 
517     /**
518      * @throws Exception
519      */
520     public void run() throws Exception
521     {
522         _server.start();
523         _server.join();
524     }
525 
526 
527     /**
528      * Establish a classloader with custom paths (if any)
529      */
530     protected void initClassLoader()
531     {
532         URL[] paths = _classpath.asArray();
533 
534         if (_classLoader==null && paths !=null && paths.length > 0)
535         {
536             ClassLoader context=Thread.currentThread().getContextClassLoader();
537 
538             if (context==null)
539                 _classLoader=new URLClassLoader(paths);
540             else
541                 _classLoader=new URLClassLoader(paths, context);
542 
543             Thread.currentThread().setContextClassLoader(_classLoader);
544         }
545     }
546 
547 
548 
549 
550     /**
551      * @param args
552      */
553     public static void main(String[] args)
554     {
555         Runner runner = new Runner();
556 
557         try
558         {
559             if (args.length>0&&args[0].equalsIgnoreCase("--help"))
560             {
561                 runner.usage(null);
562             }
563             else if (args.length>0&&args[0].equalsIgnoreCase("--version"))
564             {
565                 runner.version();
566             }
567             else
568             {
569                 runner.configure(args);
570                 runner.run();
571             }
572         }
573         catch (Exception e)
574         {
575             e.printStackTrace();
576             runner.usage(null);
577         }
578     }
579 }