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.start;
20  
21  import static org.eclipse.jetty.start.UsageException.ERR_INVOKE_MAIN;
22  import static org.eclipse.jetty.start.UsageException.ERR_NOT_STOPPED;
23  import static org.eclipse.jetty.start.UsageException.ERR_UNKNOWN;
24  
25  import java.io.BufferedReader;
26  import java.io.File;
27  import java.io.FileOutputStream;
28  import java.io.FileWriter;
29  import java.io.IOException;
30  import java.io.InputStream;
31  import java.io.InputStreamReader;
32  import java.io.LineNumberReader;
33  import java.io.OutputStream;
34  import java.io.PrintWriter;
35  import java.lang.reflect.InvocationTargetException;
36  import java.lang.reflect.Method;
37  import java.net.ConnectException;
38  import java.net.InetAddress;
39  import java.net.Socket;
40  import java.net.SocketTimeoutException;
41  import java.net.URL;
42  import java.util.ArrayList;
43  import java.util.Collection;
44  import java.util.Collections;
45  import java.util.List;
46  import java.util.Locale;
47  import java.util.regex.Matcher;
48  import java.util.regex.Pattern;
49  
50  /**
51   * Main start class.
52   * <p>
53   * 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
54   * command "java -jar start.jar".
55   * <p>
56   * Argument processing steps:
57   * <ol>
58   * <li>Directory Locations:
59   * <ul>
60   * <li>jetty.home=[directory] (the jetty.home location)</li>
61   * <li>jetty.base=[directory] (the jetty.base location)</li>
62   * </ul>
63   * </li>
64   * <li>Start Logging behavior:
65   * <ul>
66   * <li>--debug (debugging enabled)</li>
67   * <li>--start-log-file=logs/start.log (output start logs to logs/start.log location)</li>
68   * </ul>
69   * </li>
70   * <li>Module Resolution</li>
71   * <li>Properties Resolution</li>
72   * <li>Present Optional Informational Options</li>
73   * <li>Normal Startup</li>
74   * </li>
75   * </ol>
76   */
77  public class Main
78  {
79      private static final int EXIT_USAGE = 1;
80  
81      public static String join(Collection<?> objs, String delim)
82      {
83          StringBuilder str = new StringBuilder();
84          boolean needDelim = false;
85          for (Object obj : objs)
86          {
87              if (needDelim)
88              {
89                  str.append(delim);
90              }
91              str.append(obj);
92              needDelim = true;
93          }
94          return str.toString();
95      }
96  
97      public static void main(String[] args)
98      {
99          try
100         {
101             Main main = new Main();
102             StartArgs startArgs = main.processCommandLine(args);
103             main.start(startArgs);
104         }
105         catch (UsageException e)
106         {
107             System.err.println(e.getMessage());
108             usageExit(e.getCause(),e.getExitCode());
109         }
110         catch (Throwable e)
111         {
112             usageExit(e,UsageException.ERR_UNKNOWN);
113         }
114     }
115 
116     static void usageExit(int exit)
117     {
118         usageExit(null,exit);
119     }
120 
121     static void usageExit(Throwable t, int exit)
122     {
123         if (t != null)
124         {
125             t.printStackTrace(System.err);
126         }
127         System.err.println();
128         System.err.println("Usage: java -jar start.jar [options] [properties] [configs]");
129         System.err.println("       java -jar start.jar --help  # for more information");
130         System.exit(exit);
131     }
132 
133     private final BaseHome baseHome;
134 
135     Main() throws IOException
136     {
137         baseHome = new BaseHome();
138     }
139 
140     private void copyInThread(final InputStream in, final OutputStream out)
141     {
142         new Thread(new Runnable()
143         {
144             @Override
145             public void run()
146             {
147                 try
148                 {
149                     byte[] buf = new byte[1024];
150                     int len = in.read(buf);
151                     while (len > 0)
152                     {
153                         out.write(buf,0,len);
154                         len = in.read(buf);
155                     }
156                 }
157                 catch (IOException e)
158                 {
159                     // e.printStackTrace();
160                 }
161             }
162 
163         }).start();
164     }
165 
166     private void initFile(FileArg arg)
167     {
168         try
169         {
170             File file = baseHome.getBaseFile(arg.location);
171 
172             StartLog.debug("Module file %s %s",file.getAbsolutePath(),(file.exists()?"[Exists!]":""));
173             if (file.exists())
174             {
175                 return;
176             }
177 
178             if (arg.uri!=null)
179             {
180                 URL url = new URL(arg.uri);
181 
182                 System.err.println("DOWNLOAD: " + url + " to " + arg.location);
183 
184                 FS.ensureDirectoryExists(file.getParentFile());
185 
186                 byte[] buf = new byte[8192];
187                 try (InputStream in = url.openStream(); OutputStream out = new FileOutputStream(file);)
188                 {
189                     while (true)
190                     {
191                         int len = in.read(buf);
192 
193                         if (len > 0)
194                         {
195                             out.write(buf,0,len);
196                         }
197                         if (len < 0)
198                         {
199                             break;
200                         }
201                     }
202                 }
203             }
204             else if (arg.location.endsWith("/"))
205             {
206                 System.err.println("MKDIR: " + baseHome.toShortForm(file));
207                 file.mkdirs();
208             }
209             else
210                 StartLog.warn("MISSING: required file "+ baseHome.toShortForm(file));
211             
212         }
213         catch (Exception e)
214         {
215             StartLog.warn("ERROR: processing %s%n%s",arg,e);
216             StartLog.warn(e);
217             usageExit(EXIT_USAGE);
218         }
219     }
220 
221     private void dumpClasspathWithVersions(Classpath classpath)
222     {
223         System.out.println();
224         System.out.println("Jetty Server Classpath:");
225         System.out.println("-----------------------");
226         if (classpath.count() == 0)
227         {
228             System.out.println("No classpath entries and/or version information available show.");
229             return;
230         }
231 
232         System.out.println("Version Information on " + classpath.count() + " entr" + ((classpath.count() > 1)?"ies":"y") + " in the classpath.");
233         System.out.println("Note: order presented here is how they would appear on the classpath.");
234         System.out.println("      changes to the --module=name command line options will be reflected here.");
235 
236         int i = 0;
237         for (File element : classpath.getElements())
238         {
239             System.out.printf("%2d: %24s | %s\n",i++,getVersion(element),baseHome.toShortForm(element));
240         }
241     }
242 
243     public BaseHome getBaseHome()
244     {
245         return baseHome;
246     }
247 
248     private String getVersion(File element)
249     {
250         if (element.isDirectory())
251         {
252             return "(dir)";
253         }
254 
255         if (element.isFile())
256         {
257             String name = element.getName().toLowerCase(Locale.ENGLISH);
258             if (name.endsWith(".jar"))
259             {
260                 return JarVersion.getVersion(element);
261             }
262         }
263 
264         return "";
265     }
266 
267     public void invokeMain(StartArgs args) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException, IOException
268     {
269         Class<?> invoked_class = null;
270         ClassLoader classloader = args.getClasspath().getClassLoader();
271         String mainclass = args.getMainClassname();
272 
273         try
274         {
275             invoked_class = classloader.loadClass(mainclass);
276         }
277         catch (ClassNotFoundException e)
278         {
279             System.out.println("WARNING: Nothing to start, exiting ...");
280             StartLog.debug(e);
281             usageExit(ERR_INVOKE_MAIN);
282             return;
283         }
284 
285         StartLog.debug("%s - %s",invoked_class,invoked_class.getPackage().getImplementationVersion());
286 
287         CommandLineBuilder cmd = args.getMainArgs(baseHome,false);
288         String argArray[] = cmd.getArgs().toArray(new String[0]);
289         StartLog.debug("Command Line Args: %s",cmd.toString());
290 
291         Class<?>[] method_param_types = new Class[]
292         { argArray.getClass() };
293 
294         Method main = invoked_class.getDeclaredMethod("main",method_param_types);
295         Object[] method_params = new Object[]
296         { argArray };
297         main.invoke(null,method_params);
298     }
299 
300     public void listConfig(StartArgs args)
301     {
302         // Dump Jetty Home / Base
303         args.dumpEnvironment();
304 
305         // Dump JVM Args
306         args.dumpJvmArgs();
307 
308         // Dump System Properties
309         args.dumpSystemProperties();
310 
311         // Dump Properties
312         args.dumpProperties();
313 
314         // Dump Classpath
315         dumpClasspathWithVersions(args.getClasspath());
316 
317         // Dump Resolved XMLs
318         args.dumpActiveXmls(baseHome);
319     }
320 
321     private void listModules(StartArgs args)
322     {
323         System.out.println();
324         System.out.println("Jetty All Available Modules:");
325         System.out.println("----------------------------");
326         args.getAllModules().dump();
327 
328         // Dump Enabled Modules
329         System.out.println();
330         System.out.println("Jetty Active Module Tree:");
331         System.out.println("-------------------------");
332         Modules modules = args.getAllModules();
333         modules.dumpEnabledTree();
334     }
335 
336     private void moduleIni(StartArgs args, String name, boolean topLevel, boolean appendStartIni) throws IOException
337     {
338         // Find the start.d relative to the base directory only.
339         File start_d = baseHome.getBaseFile("start.d");
340 
341         // Is this a module?
342         Modules modules = args.getAllModules();
343         Module module = modules.get(name);
344         if (module == null)
345         {
346             StartLog.warn("ERROR: No known module for %s",name);
347             return;
348         }
349 
350         // Find any named ini file and check it follows the convention
351         File start_ini = baseHome.getBaseFile("start.ini");
352         String short_start_ini = baseHome.toShortForm(start_ini);
353         File ini = new File(start_d,name + ".ini");
354         String short_ini = baseHome.toShortForm(ini);
355         StartIni module_ini = null;
356         if (ini.exists())
357         {
358             module_ini = new StartIni(ini);
359             if (module_ini.getLineMatches(Pattern.compile("--module=(.*, *)*" + name)).size() == 0)
360             {
361                 StartLog.warn("ERROR: %s is not enabled in %s!",name,short_ini);
362                 return;
363             }
364         }
365 
366         boolean transitive = module.isEnabled() && (module.getSources().size() == 0);
367         boolean has_ini_lines = module.getInitialise().size() > 0;
368 
369         // If it is not enabled or is transitive with ini template lines or toplevel and doesn't exist
370         if (!module.isEnabled() || (transitive && has_ini_lines) || (topLevel && !ini.exists() && !appendStartIni))
371         {
372             String source = null;
373             PrintWriter out = null;
374             try
375             {
376                 if (appendStartIni)
377                 {
378                     if ((!start_ini.exists() && !start_ini.createNewFile()) || !start_ini.canWrite())
379                     {
380                         StartLog.warn("ERROR: Bad %s! ",start_ini);
381                         return;
382                     }
383                     source = short_start_ini;
384                     StartLog.warn("%-15s initialised in %s (appended)",name,source);
385                     out = new PrintWriter(new FileWriter(start_ini,true));
386                 }
387                 else
388                 {
389                     // Create the directory if needed
390                     if (!start_d.exists())
391                     {
392                         start_d.mkdirs();
393                     }
394                     if (!start_d.isDirectory() || !start_d.canWrite())
395                     {
396                         StartLog.warn("ERROR: Bad start.d %s! ",start_d);
397                         return;
398                     }
399                     // Create a new ini file for it
400                     if (!ini.createNewFile())
401                     {
402                         StartLog.warn("ERROR: %s cannot be initialised in %s! ",name,short_ini);
403                         return;
404                     }
405                     source = short_ini;
406                     StartLog.warn("%-15s initialised in %s (created)",name,source);
407                     out = new PrintWriter(ini);
408                 }
409 
410                 if (appendStartIni)
411                 {
412                     out.println();
413                 }
414                 out.println("#");
415                 out.println("# Initialize module " + name);
416                 out.println("#");
417                 Pattern p = Pattern.compile("--module=([^,]+)(,([^,]+))*");
418 
419                 out.println("--module=" + name);
420                 args.parse("--module=" + name,source);
421                 modules.enable(name,Collections.singletonList(source));
422                 for (String line : module.getInitialise())
423                 {
424                     out.println(line);
425                     args.parse(line,source);
426                     Matcher m = p.matcher(line);
427                     if (m.matches())
428                     {
429                         for (int i = 1; i <= m.groupCount(); i++)
430                         {
431                             String n = m.group(i);
432                             if (n == null)
433                             {
434                                 continue;
435                             }
436                             n = n.trim();
437                             if ((n.length() == 0) || n.startsWith(","))
438                             {
439                                 continue;
440                             }
441 
442                             modules.enable(n,Collections.singletonList(source));
443                         }
444                     }
445                 }
446             }
447             finally
448             {
449                 if (out != null)
450                 {
451                     out.close();
452                 }
453             }
454         }
455         else if (ini.exists())
456         {
457             StartLog.info("%-15s initialised in %s",name,short_ini);
458         }
459 
460         // Also list other places this module is enabled
461         for (String source : module.getSources())
462         {
463             if (!short_ini.equals(source))
464             {
465                 StartLog.warn("%-15s enabled in     %s",name,baseHome.toShortForm(source));
466             }
467         }
468 
469         // Do downloads now
470         for (String file : module.getFiles())
471         {
472             initFile(new FileArg(file));
473         }
474 
475         // Process dependencies from top level only
476         if (topLevel)
477         {
478             List<Module> parents = new ArrayList<>();
479             for (String parent : modules.resolveParentModulesOf(name))
480             {
481                 if (!name.equals(parent))
482                 {
483                     Module m = modules.get(parent);
484                     m.setEnabled(true);
485                     parents.add(m);
486                 }
487             }
488             Collections.sort(parents,Collections.reverseOrder(new Module.DepthComparator()));
489             for (Module m : parents)
490             {
491                 moduleIni(args,m.getName(),false,appendStartIni);
492             }
493         }
494     }
495 
496     /**
497      * Convenience for <code>processCommandLine(cmdLine.toArray(new String[cmdLine.size()]))</code>
498      */
499     public StartArgs processCommandLine(List<String> cmdLine) throws Exception
500     {
501         return this.processCommandLine(cmdLine.toArray(new String[cmdLine.size()]));
502     }
503 
504     public StartArgs processCommandLine(String[] cmdLine) throws Exception
505     {
506         StartArgs args = new StartArgs(cmdLine);
507 
508         // Processing Order is important!
509         // ------------------------------------------------------------
510         // 1) Directory Locations
511 
512         // Set Home and Base at the start, as all other paths encountered
513         // will be based off of them.
514         baseHome.initialize(args);
515 
516         // ------------------------------------------------------------
517         // 2) Start Logging
518         StartLog.getInstance().initialize(baseHome,args);
519 
520         StartLog.debug("jetty.home=%s",baseHome.getHome());
521         StartLog.debug("jetty.base=%s",baseHome.getBase());
522         args.addSystemProperty("jetty.home",baseHome.getHome());
523         args.addSystemProperty("jetty.base",baseHome.getBase());
524 
525         // ------------------------------------------------------------
526         // 3) Load Inis
527         File start_ini = baseHome.getBaseFile("start.ini");
528         if (FS.canReadFile(start_ini))
529         {
530             StartLog.debug("Reading ${jetty.base}/start.ini - %s",start_ini);
531             args.parse(baseHome,new StartIni(start_ini));
532         }
533 
534         File start_d = baseHome.getBaseFile("start.d");
535         if (FS.canReadDirectory(start_d))
536         {
537             List<File> files = new ArrayList<>();
538             for (File file : start_d.listFiles(new FS.IniFilter()))
539             {
540                 files.add(file);
541             }
542 
543             Collections.sort(files,new NaturalSort.Files());
544             for (File file : files)
545             {
546                 StartLog.debug("Reading ${jetty.base}/start.d/%s - %s",file.getName(),file);
547                 args.parse(baseHome,new StartIni(file));
548             }
549         }
550 
551         // 4) Parse everything provided.
552         // This would be the directory information +
553         // the various start inis
554         // and then the raw command line arguments
555         StartLog.debug("Parsing collected arguments");
556         args.parseCommandLine();
557 
558         // 5) Module Registration
559         Modules modules = new Modules();
560         StartLog.debug("Registering all modules");
561         modules.registerAll(baseHome);
562 
563         // 6) Active Module Resolution
564         for (String enabledModule : args.getEnabledModules())
565         {
566             List<String> sources = args.getSources(enabledModule);
567             modules.enable(enabledModule,sources);
568         }
569 
570         StartLog.debug("Building Module Graph");
571         modules.buildGraph();
572 
573         args.setAllModules(modules);
574         List<Module> activeModules = modules.resolveEnabled();
575 
576         // 7) Lib & XML Expansion / Resolution
577         args.expandModules(baseHome,activeModules);
578 
579         // 8) Resolve Extra XMLs
580         args.resolveExtraXmls(baseHome);
581 
582         return args;
583     }
584 
585     public void start(StartArgs args) throws IOException, InterruptedException
586     {
587         StartLog.debug("StartArgs: %s",args);
588 
589         // Get Desired Classpath based on user provided Active Options.
590         Classpath classpath = args.getClasspath();
591 
592         System.setProperty("java.class.path",classpath.toString());
593 
594         // Show the usage information and return
595         if (args.isHelp())
596         {
597             usage(true);
598         }
599 
600         // Show the version information and return
601         if (args.isListClasspath())
602         {
603             dumpClasspathWithVersions(classpath);
604         }
605 
606         // Show configuration
607         if (args.isListConfig())
608         {
609             listConfig(args);
610         }
611 
612         // Show modules
613         if (args.isListModules())
614         {
615             listModules(args);
616         }
617         
618         // Generate Module Graph File
619         if (args.getModuleGraphFilename() != null)
620         {
621             File outputFile = baseHome.getBaseFile(args.getModuleGraphFilename());
622             System.out.printf("Generating GraphViz Graph of Jetty Modules at %s%n",baseHome.toShortForm(outputFile));
623             ModuleGraphWriter writer = new ModuleGraphWriter();
624             writer.config(args.getProperties());
625             writer.write(args.getAllModules(),outputFile);
626         }
627 
628         // Show Command Line to execute Jetty
629         if (args.isDryRun())
630         {
631             CommandLineBuilder cmd = args.getMainArgs(baseHome,true);
632             System.out.println(cmd.toString());
633         }
634 
635         if (args.isStopCommand())
636         {
637             int stopPort = Integer.parseInt(args.getProperties().getProperty("STOP.PORT"));
638             String stopKey = args.getProperties().getProperty("STOP.KEY");
639 
640             if (args.getProperties().getProperty("STOP.WAIT") != null)
641             {
642                 int stopWait = Integer.parseInt(args.getProperties().getProperty("STOP.PORT"));
643 
644                 stop(stopPort,stopKey,stopWait);
645             }
646             else
647             {
648                 stop(stopPort,stopKey);
649             }
650         }
651 
652         // Initialize
653         for (String module : args.getModuleStartIni())
654         {
655             moduleIni(args,module,true,true);
656         }
657 
658         // Initialize
659         for (String module : args.getModuleStartdIni())
660         {
661             moduleIni(args,module,true,false);
662         }
663 
664         // Informational command line, don't run jetty
665         if (!args.isRun())
666         {
667             return;
668         }
669 
670         // execute Jetty in another JVM
671         if (args.isExec())
672         {
673             CommandLineBuilder cmd = args.getMainArgs(baseHome,true);
674             ProcessBuilder pbuilder = new ProcessBuilder(cmd.getArgs());
675             final Process process = pbuilder.start();
676             Runtime.getRuntime().addShutdownHook(new Thread()
677             {
678                 @Override
679                 public void run()
680                 {
681                     StartLog.debug("Destroying " + process);
682                     process.destroy();
683                 }
684             });
685 
686             copyInThread(process.getErrorStream(),System.err);
687             copyInThread(process.getInputStream(),System.out);
688             copyInThread(System.in,process.getOutputStream());
689             process.waitFor();
690             System.exit(0); // exit JVM when child process ends.
691             return;
692         }
693 
694         if (args.hasJvmArgs() || args.hasSystemProperties())
695         {
696             System.err.println("WARNING: System properties and/or JVM args set.  Consider using --dry-run or --exec");
697         }
698 
699         // Set current context class loader to what is selected.
700         ClassLoader cl = classpath.getClassLoader();
701         Thread.currentThread().setContextClassLoader(cl);
702 
703         // Invoke the Main Class
704         try
705         {
706             invokeMain(args);
707         }
708         catch (Exception e)
709         {
710             usageExit(e,ERR_INVOKE_MAIN);
711         }
712     }
713 
714     /**
715      * Stop a running jetty instance.
716      */
717     public void stop(int port, String key)
718     {
719         stop(port,key,0);
720     }
721 
722     public void stop(int port, String key, int timeout)
723     {
724         int _port = port;
725         String _key = key;
726 
727         try
728         {
729             if (_port <= 0)
730             {
731                 System.err.println("STOP.PORT system property must be specified");
732             }
733             if (_key == null)
734             {
735                 _key = "";
736                 System.err.println("STOP.KEY system property must be specified");
737                 System.err.println("Using empty key");
738             }
739 
740             try (Socket s = new Socket(InetAddress.getByName("127.0.0.1"),_port))
741             {
742                 if (timeout > 0)
743                 {
744                     s.setSoTimeout(timeout * 1000);
745                 }
746 
747                 try (OutputStream out = s.getOutputStream())
748                 {
749                     out.write((_key + "\r\nstop\r\n").getBytes());
750                     out.flush();
751 
752                     if (timeout > 0)
753                     {
754                         System.err.printf("Waiting %,d seconds for jetty to stop%n",timeout);
755                         LineNumberReader lin = new LineNumberReader(new InputStreamReader(s.getInputStream()));
756                         String response;
757                         while ((response = lin.readLine()) != null)
758                         {
759                             StartLog.debug("Received \"%s\"",response);
760                             if ("Stopped".equals(response))
761                             {
762                                 StartLog.warn("Server reports itself as Stopped");
763                             }
764                         }
765                     }
766                 }
767             }
768         }
769         catch (SocketTimeoutException e)
770         {
771             System.err.println("Timed out waiting for stop confirmation");
772             System.exit(ERR_UNKNOWN);
773         }
774         catch (ConnectException e)
775         {
776             usageExit(e,ERR_NOT_STOPPED);
777         }
778         catch (Exception e)
779         {
780             usageExit(e,ERR_UNKNOWN);
781         }
782     }
783 
784     public void usage(boolean exit)
785     {
786         String usageResource = "org/eclipse/jetty/start/usage.txt";
787         boolean usagePresented = false;
788         try (InputStream usageStream = getClass().getClassLoader().getResourceAsStream(usageResource))
789         {
790             if (usageStream != null)
791             {
792                 try (InputStreamReader reader = new InputStreamReader(usageStream); BufferedReader buf = new BufferedReader(reader))
793                 {
794                     usagePresented = true;
795                     String line;
796                     while ((line = buf.readLine()) != null)
797                     {
798                         System.out.println(line);
799                     }
800                 }
801             }
802             else
803             {
804                 System.out.println("No usage.txt ??");
805             }
806         }
807         catch (IOException e)
808         {
809             StartLog.warn(e);
810         }
811         if (!usagePresented)
812         {
813             System.err.println("ERROR: detailed usage resource unavailable");
814         }
815         if (exit)
816         {
817             System.exit(EXIT_USAGE);
818         }
819     }
820 }