View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2015 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.start;
20  
21  import static org.eclipse.jetty.start.UsageException.ERR_BAD_GRAPH;
22  import static org.eclipse.jetty.start.UsageException.ERR_INVOKE_MAIN;
23  import static org.eclipse.jetty.start.UsageException.ERR_NOT_STOPPED;
24  import static org.eclipse.jetty.start.UsageException.ERR_UNKNOWN;
25  
26  import java.io.BufferedReader;
27  import java.io.File;
28  import java.io.IOException;
29  import java.io.InputStream;
30  import java.io.InputStreamReader;
31  import java.io.LineNumberReader;
32  import java.io.OutputStream;
33  import java.lang.reflect.InvocationTargetException;
34  import java.lang.reflect.Method;
35  import java.net.ConnectException;
36  import java.net.InetAddress;
37  import java.net.Socket;
38  import java.net.SocketTimeoutException;
39  import java.nio.file.Path;
40  import java.util.List;
41  import java.util.Locale;
42  
43  import org.eclipse.jetty.start.config.CommandLineConfigSource;
44  import org.eclipse.jetty.start.graph.GraphException;
45  import org.eclipse.jetty.start.graph.Selection;
46  
47  /**
48   * Main start class.
49   * <p>
50   * This class is intended to be the main class listed in the MANIFEST.MF of the start.jar archive. It allows the Jetty Application server to be started with the
51   * command "java -jar start.jar".
52   * <p>
53   * <b>Argument processing steps:</b>
54   * <ol>
55   * <li>Directory Location: jetty.home=[directory] (the jetty.home location)</li>
56   * <li>Directory Location: jetty.base=[directory] (the jetty.base location)</li>
57   * <li>Start Logging behavior: --debug (debugging enabled)</li>
58   * <li>Start Logging behavior: --start-log-file=logs/start.log (output start logs to logs/start.log location)</li>
59   * <li>Module Resolution</li>
60   * <li>Properties Resolution</li>
61   * <li>Present Optional Informational Options</li>
62   * <li>Normal Startup</li>
63   * </ol>
64   */
65  public class Main
66  {
67      private static final int EXIT_USAGE = 1;
68  
69      public static void main(String[] args)
70      {
71          try
72          {
73              Main main = new Main();
74              StartArgs startArgs = main.processCommandLine(args);
75              main.start(startArgs);
76          }
77          catch (UsageException e)
78          {
79              System.err.println(e.getMessage());
80              usageExit(e.getCause(),e.getExitCode());
81          }
82          catch (Throwable e)
83          {
84              usageExit(e,UsageException.ERR_UNKNOWN);
85          }
86      }
87  
88      static void usageExit(int exit)
89      {
90          usageExit(null,exit);
91      }
92  
93      static void usageExit(Throwable t, int exit)
94      {
95          if (t != null)
96          {
97              t.printStackTrace(System.err);
98          }
99          System.err.println();
100         System.err.println("Usage: java -jar start.jar [options] [properties] [configs]");
101         System.err.println("       java -jar start.jar --help  # for more information");
102         System.exit(exit);
103     }
104 
105     private BaseHome baseHome;
106     private StartArgs startupArgs;
107 
108     public Main() throws IOException
109     {
110     }
111 
112     private void copyInThread(final InputStream in, final OutputStream out)
113     {
114         new Thread(new Runnable()
115         {
116             @Override
117             public void run()
118             {
119                 try
120                 {
121                     byte[] buf = new byte[1024];
122                     int len = in.read(buf);
123                     while (len > 0)
124                     {
125                         out.write(buf,0,len);
126                         len = in.read(buf);
127                     }
128                 }
129                 catch (IOException e)
130                 {
131                     // e.printStackTrace();
132                 }
133             }
134 
135         }).start();
136     }
137 
138     private void dumpClasspathWithVersions(Classpath classpath)
139     {
140         StartLog.endStartLog();
141         System.out.println();
142         System.out.println("Jetty Server Classpath:");
143         System.out.println("-----------------------");
144         if (classpath.count() == 0)
145         {
146             System.out.println("No classpath entries and/or version information available show.");
147             return;
148         }
149 
150         System.out.println("Version Information on " + classpath.count() + " entr" + ((classpath.count() > 1)?"ies":"y") + " in the classpath.");
151         System.out.println("Note: order presented here is how they would appear on the classpath.");
152         System.out.println("      changes to the --module=name command line options will be reflected here.");
153 
154         int i = 0;
155         for (File element : classpath.getElements())
156         {
157             System.out.printf("%2d: %24s | %s\n",i++,getVersion(element),baseHome.toShortForm(element));
158         }
159     }
160 
161     public BaseHome getBaseHome()
162     {
163         return baseHome;
164     }
165 
166     private String getVersion(File element)
167     {
168         if (element.isDirectory())
169         {
170             return "(dir)";
171         }
172 
173         if (element.isFile())
174         {
175             String name = element.getName().toLowerCase(Locale.ENGLISH);
176             if (name.endsWith(".jar"))
177             {
178                 return JarVersion.getVersion(element);
179             }
180         }
181 
182         return "";
183     }
184 
185     public void invokeMain(ClassLoader classloader, StartArgs args) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException, IOException
186     {
187         Class<?> invoked_class = null;
188         String mainclass = args.getMainClassname();
189 
190         try
191         {
192             invoked_class = classloader.loadClass(mainclass);
193         }
194         catch (ClassNotFoundException e)
195         {
196             System.out.println("WARNING: Nothing to start, exiting ...");
197             StartLog.debug(e);
198             usageExit(ERR_INVOKE_MAIN);
199             return;
200         }
201 
202         StartLog.debug("%s - %s",invoked_class,invoked_class.getPackage().getImplementationVersion());
203 
204         CommandLineBuilder cmd = args.getMainArgs(baseHome,false);
205         String argArray[] = cmd.getArgs().toArray(new String[0]);
206         StartLog.debug("Command Line Args: %s",cmd.toString());
207 
208         Class<?>[] method_param_types = new Class[]
209         { argArray.getClass() };
210 
211         Method main = invoked_class.getDeclaredMethod("main",method_param_types);
212         Object[] method_params = new Object[] { argArray };
213         StartLog.endStartLog();
214         main.invoke(null,method_params);
215     }
216 
217     public void listConfig(StartArgs args)
218     {
219         StartLog.endStartLog();
220         
221         // Dump Jetty Home / Base
222         args.dumpEnvironment(baseHome);
223 
224         // Dump JVM Args
225         args.dumpJvmArgs();
226 
227         // Dump System Properties
228         args.dumpSystemProperties();
229 
230         // Dump Properties
231         args.dumpProperties();
232 
233         // Dump Classpath
234         dumpClasspathWithVersions(args.getClasspath());
235 
236         // Dump Resolved XMLs
237         args.dumpActiveXmls(baseHome);
238     }
239 
240     public void listModules(StartArgs args)
241     {
242         StartLog.endStartLog();
243         System.out.println();
244         System.out.println("Jetty All Available Modules:");
245         System.out.println("----------------------------");
246         args.getAllModules().dump();
247 
248         // Dump Enabled Modules
249         System.out.println();
250         System.out.println("Jetty Selected Module Ordering:");
251         System.out.println("-------------------------------");
252         Modules modules = args.getAllModules();
253         modules.dumpSelected();
254     }
255 
256     /**
257      * Convenience for <code>processCommandLine(cmdLine.toArray(new String[cmdLine.size()]))</code>
258      * @param cmdLine the command line
259      * @return the start args parsed from the command line
260      * @throws Exception if unable to process the command line
261      */
262     public StartArgs processCommandLine(List<String> cmdLine) throws Exception
263     {
264         return this.processCommandLine(cmdLine.toArray(new String[cmdLine.size()]));
265     }
266 
267     public StartArgs processCommandLine(String[] cmdLine) throws Exception
268     {
269         // Processing Order is important!
270         // ------------------------------------------------------------
271         // 1) Configuration Locations
272         CommandLineConfigSource cmdLineSource = new CommandLineConfigSource(cmdLine);
273         baseHome = new BaseHome(cmdLineSource);
274 
275         StartLog.debug("jetty.home=%s",baseHome.getHome());
276         StartLog.debug("jetty.base=%s",baseHome.getBase());
277 
278         // ------------------------------------------------------------
279         // 2) Parse everything provided.
280         // This would be the directory information +
281         // the various start inis
282         // and then the raw command line arguments
283         StartLog.debug("Parsing collected arguments");
284         StartArgs args = new StartArgs();
285         args.parse(baseHome.getConfigSources());
286 
287         try
288         {
289             // ------------------------------------------------------------
290             // 3) Module Registration
291             Modules modules = new Modules(baseHome,args);
292             StartLog.debug("Registering all modules");
293             modules.registerAll();
294 
295             // ------------------------------------------------------------
296             // 4) Active Module Resolution
297             for (String enabledModule : args.getEnabledModules())
298             {
299                 for (String source : args.getSources(enabledModule))
300                 {
301                     String shortForm = baseHome.toShortForm(source);
302                     modules.selectNode(enabledModule,new Selection(shortForm));
303                 }
304             }
305 
306             StartLog.debug("Building Module Graph");
307             modules.buildGraph();
308 
309             args.setAllModules(modules);
310             List<Module> activeModules = modules.getSelected();
311             
312             final Version START_VERSION = new Version(StartArgs.VERSION);
313             
314             for(Module enabled: activeModules)
315             {
316                 if(enabled.getVersion().isNewerThan(START_VERSION))
317                 {
318                     throw new UsageException(UsageException.ERR_BAD_GRAPH, "Module [" + enabled.getName() + "] specifies jetty version [" + enabled.getVersion()
319                             + "] which is newer than this version of jetty [" + START_VERSION + "]");
320                 }
321             }
322             
323             for(String name: args.getSkipFileValidationModules())
324             {
325                 Module module = modules.get(name);
326                 module.setSkipFilesValidation(true);
327             }
328 
329             // ------------------------------------------------------------
330             // 5) Lib & XML Expansion / Resolution
331             args.expandLibs(baseHome);
332             args.expandModules(baseHome,activeModules);
333 
334         }
335         catch (GraphException e)
336         {
337             throw new UsageException(ERR_BAD_GRAPH,e);
338         }
339 
340         // ------------------------------------------------------------
341         // 6) Resolve Extra XMLs
342         args.resolveExtraXmls(baseHome);
343         
344         // ------------------------------------------------------------
345         // 9) Resolve Property Files
346         args.resolvePropertyFiles(baseHome);
347 
348         return args;
349     }
350     
351     public void start(StartArgs args) throws IOException, InterruptedException
352     {
353         StartLog.debug("StartArgs: %s",args);
354 
355         // Get Desired Classpath based on user provided Active Options.
356         Classpath classpath = args.getClasspath();
357 
358         System.setProperty("java.class.path",classpath.toString());
359 
360         // Show the usage information and return
361         if (args.isHelp())
362         {
363             usage(true);
364         }
365 
366         // Show the version information and return
367         if (args.isListClasspath())
368         {
369             dumpClasspathWithVersions(classpath);
370         }
371 
372         // Show configuration
373         if (args.isListConfig())
374         {
375             listConfig(args);
376         }
377 
378         // Show modules
379         if (args.isListModules())
380         {
381             listModules(args);
382         }
383         
384         // Generate Module Graph File
385         if (args.getModuleGraphFilename() != null)
386         {
387             Path outputFile = baseHome.getBasePath(args.getModuleGraphFilename());
388             System.out.printf("Generating GraphViz Graph of Jetty Modules at %s%n",baseHome.toShortForm(outputFile));
389             ModuleGraphWriter writer = new ModuleGraphWriter();
390             writer.config(args.getProperties());
391             writer.write(args.getAllModules(),outputFile);
392         }
393 
394         // Show Command Line to execute Jetty
395         if (args.isDryRun())
396         {
397             CommandLineBuilder cmd = args.getMainArgs(baseHome,true);
398             System.out.println(cmd.toString(StartLog.isDebugEnabled()?" \\\n":" "));
399         }
400 
401         if (args.isStopCommand())
402         {
403             doStop(args);
404         }
405         
406         BaseBuilder baseBuilder = new BaseBuilder(baseHome,args);
407         if(baseBuilder.build())
408         {
409             // base directory changed.
410             StartLog.info("Base directory was modified");
411             return;
412         }
413         
414         // Informational command line, don't run jetty
415         if (!args.isRun())
416         {
417             return;
418         }
419         
420         // execute Jetty in another JVM
421         if (args.isExec())
422         {
423             CommandLineBuilder cmd = args.getMainArgs(baseHome,true);
424             cmd.debug();
425             ProcessBuilder pbuilder = new ProcessBuilder(cmd.getArgs());
426             StartLog.endStartLog();
427             final Process process = pbuilder.start();
428             Runtime.getRuntime().addShutdownHook(new Thread()
429             {
430                 @Override
431                 public void run()
432                 {
433                     StartLog.debug("Destroying " + process);
434                     process.destroy();
435                 }
436             });
437 
438             copyInThread(process.getErrorStream(),System.err);
439             copyInThread(process.getInputStream(),System.out);
440             copyInThread(System.in,process.getOutputStream());
441             process.waitFor();
442             System.exit(0); // exit JVM when child process ends.
443             return;
444         }
445 
446         if (args.hasJvmArgs() || args.hasSystemProperties())
447         {
448             System.err.println("WARNING: System properties and/or JVM args set.  Consider using --dry-run or --exec");
449         }
450 
451         ClassLoader cl = classpath.getClassLoader();
452         Thread.currentThread().setContextClassLoader(cl);
453 
454         // Invoke the Main Class
455         try
456         {
457             invokeMain(cl, args);
458         }
459         catch (Exception e)
460         {
461             usageExit(e,ERR_INVOKE_MAIN);
462         }
463     }
464 
465     private void doStop(StartArgs args)
466     {
467         String stopHost = args.getProperties().getString("STOP.HOST");
468         int stopPort = Integer.parseInt(args.getProperties().getString("STOP.PORT"));
469         String stopKey = args.getProperties().getString("STOP.KEY");
470 
471         if (args.getProperties().getString("STOP.WAIT") != null)
472         {
473             int stopWait = Integer.parseInt(args.getProperties().getString("STOP.WAIT"));
474 
475             stop(stopHost,stopPort,stopKey,stopWait);
476         }
477         else
478         {
479             stop(stopHost,stopPort,stopKey);
480         }
481     }
482 
483     /**
484      * Stop a running jetty instance.
485      * @param host the host
486      * @param port the port
487      * @param key the key
488      */
489     public void stop(String host, int port, String key)
490     {
491         stop(host,port,key,0);
492     }
493 
494     public void stop(String host, int port, String key, int timeout)
495     {
496         if (host==null || host.length()==0)
497             host="127.0.0.1";
498         
499         try
500         {
501             if (port <= 0)
502             {
503                 System.err.println("STOP.PORT system property must be specified");
504             }
505             if (key == null)
506             {
507                 key = "";
508                 System.err.println("STOP.KEY system property must be specified");
509                 System.err.println("Using empty key");
510             }
511 
512             try (Socket s = new Socket(InetAddress.getByName(host),port))
513             {
514                 if (timeout > 0)
515                 {
516                     s.setSoTimeout(timeout * 1000);
517                 }
518 
519                 try (OutputStream out = s.getOutputStream())
520                 {
521                     out.write((key + "\r\nstop\r\n").getBytes());
522                     out.flush();
523 
524                     if (timeout > 0)
525                     {
526                         System.err.printf("Waiting %,d seconds for jetty to stop%n",timeout);
527                         LineNumberReader lin = new LineNumberReader(new InputStreamReader(s.getInputStream()));
528                         String response;
529                         while ((response = lin.readLine()) != null)
530                         {
531                             StartLog.debug("Received \"%s\"",response);
532                             if ("Stopped".equals(response))
533                             {
534                                 StartLog.warn("Server reports itself as Stopped");
535                             }
536                         }
537                     }
538                 }
539             }
540         }
541         catch (SocketTimeoutException e)
542         {
543             System.err.println("Timed out waiting for stop confirmation");
544             System.exit(ERR_UNKNOWN);
545         }
546         catch (ConnectException e)
547         {
548             usageExit(e,ERR_NOT_STOPPED);
549         }
550         catch (Exception e)
551         {
552             usageExit(e,ERR_UNKNOWN);
553         }
554     }
555 
556     public void usage(boolean exit)
557     {
558         StartLog.endStartLog();
559         if(!printTextResource("org/eclipse/jetty/start/usage.txt"))
560         {
561             System.err.println("ERROR: detailed usage resource unavailable");
562         }
563         if (exit)
564         {
565             System.exit(EXIT_USAGE);
566         }
567     }
568     
569     public static boolean printTextResource(String resourceName)
570     {
571         boolean resourcePrinted = false;
572         try (InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(resourceName))
573         {
574             if (stream != null)
575             {
576                 try (InputStreamReader reader = new InputStreamReader(stream); BufferedReader buf = new BufferedReader(reader))
577                 {
578                     resourcePrinted = true;
579                     String line;
580                     while ((line = buf.readLine()) != null)
581                     {
582                         System.out.println(line);
583                     }
584                 }
585             }
586             else
587             {
588                 System.out.println("Unable to find resource: " + resourceName);
589             }
590         }
591         catch (IOException e)
592         {
593             StartLog.warn(e);
594         }
595 
596         return resourcePrinted;
597     }
598 
599     // ------------------------------------------------------------
600     // implement Apache commons daemon (jsvc) lifecycle methods (init, start, stop, destroy)
601     public void init(String[] args) throws Exception
602     {
603         try
604         {
605             startupArgs = processCommandLine(args);
606         }
607         catch (UsageException e)
608         {
609             System.err.println(e.getMessage());
610             usageExit(e.getCause(),e.getExitCode());
611         }
612         catch (Throwable e)
613         {
614             usageExit(e,UsageException.ERR_UNKNOWN);
615         }
616     }
617 
618     public void start() throws Exception
619     {
620         start(startupArgs);
621     }
622 
623     public void stop() throws Exception
624     {
625         doStop(startupArgs);
626     }
627 
628     public void destroy()
629     {
630     }
631 }