View Javadoc

1   // ========================================================================
2   // Copyright (c) 2003-2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses.
12  // ========================================================================
13  package org.eclipse.jetty.start;
14  
15  import java.io.BufferedReader;
16  import java.io.File;
17  import java.io.FileFilter;
18  import java.io.FileInputStream;
19  import java.io.FileNotFoundException;
20  import java.io.FileReader;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.InputStreamReader;
24  import java.io.OutputStream;
25  import java.io.PrintStream;
26  import java.io.Reader;
27  import java.lang.reflect.InvocationTargetException;
28  import java.lang.reflect.Method;
29  import java.net.ConnectException;
30  import java.net.InetAddress;
31  import java.net.Socket;
32  import java.security.Policy;
33  import java.util.ArrayList;
34  import java.util.Arrays;
35  import java.util.Collections;
36  import java.util.List;
37  import java.util.Set;
38  import java.util.TimeZone;
39  
40  import org.eclipse.jetty.start.log.RedirectedStreamLogger;
41  
42  /*-------------------------------------------*/
43  /**
44   * <p>
45   * Main start class. This class is intended to be the main class listed in the MANIFEST.MF of the start.jar archive. It
46   * allows an application to be started with the command "java -jar start.jar".
47   * </p>
48   * 
49   * <p>
50   * The behaviour of Main is controlled by the parsing of the {@link Config} "org/eclipse/start/start.config" file
51   * obtained as a resource or file. This can be overridden with the START system property.
52   * </p>
53   */
54  public class Main
55  {
56      private boolean _showUsage = false;
57      private boolean _dumpVersions = false;
58      private boolean _listOptions = false;
59      private boolean _dryRun = false;
60      private boolean _secure = false;
61      private boolean _fromDaemon = false;
62      private List<String> _activeOptions = new ArrayList<String>();
63      private Config _config = new Config();
64  
65      private String _jettyHome;
66  
67      public static void main(String[] args)
68      {
69          Main main = new Main();
70          try
71          {
72              main.parseCommandLine(args);
73          }
74          catch (Throwable t)
75          {
76              t.printStackTrace(System.err);
77          }
78      }
79  
80      public void parseCommandLine(String[] args)
81      {
82          try
83          {
84              List<String> arguments = new ArrayList<String>();
85              arguments.addAll(Arrays.asList(args)); // Add Arguments on Command Line
86              arguments.addAll(loadStartIni()); // Add Arguments from start.ini (if it exists)
87  
88              // The XML Configuration Files to initialize with
89              List<String> xmls = new ArrayList<String>();
90  
91              for (String arg : arguments)
92              {
93                  if ("--help".equals(arg))
94                  {
95                      _showUsage = true;
96                      continue;
97                  }
98  
99                  if ("--stop".equals(arg))
100                 {
101                     int port = Integer.parseInt(_config.getProperty("STOP.PORT","-1"));
102                     String key = _config.getProperty("STOP.KEY",null);
103                     stop(port,key);
104                     return;
105                 }
106 
107                 if ("--version".equals(arg) || "-v".equals(arg) || "--info".equals(arg))
108                 {
109                     _dumpVersions = true;
110                     continue;
111                 }
112 
113                 if ("--list-modes".equals(arg) || "--list-options".equals(arg))
114                 {
115                     _listOptions = true;
116                     continue;
117                 }
118 
119                 if ("--exec-print".equals(arg)||"--dry-run".equals(arg))
120                 {
121                     _dryRun = true;
122                     continue;
123                 }
124 
125                 // Special internal indicator that jetty was started by the jetty.sh Daemon
126                 if ("--fromDaemon".equals(arg))
127                 {
128                     _fromDaemon = true;
129                     PrintStream logger = new PrintStream(new RedirectedStreamLogger("daemon_yyyy_mm_dd.log",false,90,TimeZone.getTimeZone("GMT")));
130                     System.setOut(logger);
131                     System.setErr(logger);
132                     continue;
133                 }
134 
135                 if ("--secure".equals(arg))
136                 {
137                     _secure = true;
138                     continue;
139                 }
140 
141                 // Process property spec
142                 if (arg.indexOf('=') >= 0)
143                 {
144                     processProperty(arg);
145                     continue;
146                 }
147 
148                 // Anything else is considered an XML file.
149                 xmls.add(arg);
150             }
151 
152             start(xmls);
153         }
154         catch (Throwable t)
155         {
156             t.printStackTrace(System.err);
157             usage();
158         }
159     }
160 
161     private void processProperty(String arg)
162     {
163         String[] prop = arg.split("=",2);
164 
165         if (prop[0].startsWith("-D"))
166         {
167             // Process System Property
168             if (prop.length == 2)
169             {
170                 setSystemProperty(prop[0].substring(2),prop[1]);
171             }
172             else
173             {
174                 System.err.println("Unable to set value-less System Property: " + prop[0]);
175             }
176         }
177         else
178         {
179             // Process Startup Property
180             if (prop.length == 2)
181             {
182                 // Special case (the Config section id options)
183                 if ("OPTIONS".equals(prop[0]))
184                 {
185                     String ids[] = prop[1].split(",");
186                     for (String id : ids)
187                     {
188                         if (!_activeOptions.contains(id))
189                         {
190                             _activeOptions.add(id);
191                         }
192                     }
193                     _activeOptions.addAll(Arrays.asList(ids));
194                 }
195                 else
196                 {
197                     _config.setProperty(prop[0],prop[1]);
198                 }
199             }
200             else
201             {
202                 _config.setProperty(prop[0],null);
203             }
204         }
205     }
206 
207     /**
208      * If a start.ini is present in the CWD, then load it into the argument list.
209      */
210     private List<String> loadStartIni()
211     {
212         File startIniFile = new File("start.ini");
213         if (!startIniFile.exists())
214         {
215             // No start.ini found, skip load.
216             return Collections.emptyList();
217         }
218 
219         List<String> args = new ArrayList<String>();
220 
221         FileReader reader = null;
222         BufferedReader buf = null;
223         try
224         {
225             reader = new FileReader(startIniFile);
226             buf = new BufferedReader(reader);
227 
228             String arg;
229             while ((arg = buf.readLine()) != null)
230             {
231                 // Is this a Property?
232                 if (arg.indexOf('=') >= 0)
233                 {
234                     // A System Property?
235                     if (arg.startsWith("-D"))
236                     {
237                         String[] assign = arg.substring(2).split("=",2);
238 
239                         if (assign.length == 2)
240                         {
241                             System.setProperty(assign[0],assign[1]);
242                         }
243                         else
244                         {
245                             System.err.printf("Unable to set System Property '%s', no value provided%n",assign[0]);
246                         }
247                     }
248                     else
249                         // Nah, it's a normal property
250                     {
251                         String[] assign = arg.split("=",2);
252 
253                         if (assign.length == 2)
254                         {
255                             this._config.setProperty(assign[0],assign[1]);
256                         }
257                         else
258                         {
259                             this._config.setProperty(assign[0],null);
260                         }
261                     }
262                 }
263                 else
264                     // A normal argument
265                 {
266                     args.add(arg);
267                 }
268             }
269         }
270         catch (IOException e)
271         {
272             e.printStackTrace();
273         }
274         finally
275         {
276             close(buf);
277             close(reader);
278         }
279 
280         return args;
281     }
282 
283     private void setSystemProperty(String key, String value)
284     {
285         _config.setProperty(key,value);
286     }
287 
288     private void usage()
289     {
290         String usageResource = "org/eclipse/jetty/start/usage.txt";
291         InputStream usageStream = getClass().getClassLoader().getResourceAsStream(usageResource);
292 
293         if (usageStream == null)
294         {
295             System.err.println("Usage: java -jar start.jar [options] [properties] [configs]");
296             System.err.println("ERROR: detailed usage resource unavailable");
297             System.exit(1);
298         }
299 
300         BufferedReader buf = null;
301         try
302         {
303             buf = new BufferedReader(new InputStreamReader(usageStream));
304             String line;
305 
306             while ((line = buf.readLine()) != null)
307             {
308                 if (line.startsWith("@OPTIONS@"))
309                 {
310                     List<String> sortedOptions = new ArrayList<String>();
311                     sortedOptions.addAll(_config.getSectionIds());
312                     Collections.sort(sortedOptions);
313 
314                     System.err.println("      Available OPTIONS: ");
315 
316                     for (String option : sortedOptions)
317                     {
318                         System.err.println("         [" + option + "]");
319                     }
320                 }
321                 else if (line.startsWith("@CONFIGS@"))
322                 {
323                     System.err.println("    Configurations Available in ${jetty.home}/etc/: ");
324                     File etc = new File(System.getProperty("jetty.home","."),"etc");
325                     if (!etc.exists())
326                     {
327                         System.err.println("      Unable to find " + etc);
328                         continue;
329                     }
330 
331                     if (!etc.isDirectory())
332                     {
333                         System.err.println("      Unable list dir " + etc);
334                         continue;
335                     }
336 
337                     File configs[] = etc.listFiles(new FileFilter()
338                     {
339                         public boolean accept(File path)
340                         {
341                             if (!path.isFile())
342                             {
343                                 return false;
344                             }
345 
346                             String name = path.getName().toLowerCase();
347                             return (name.startsWith("jetty") && name.endsWith(".xml"));
348                         }
349                     });
350 
351                     List<File> configFiles = new ArrayList<File>();
352                     configFiles.addAll(Arrays.asList(configs));
353                     Collections.sort(configFiles);
354 
355                     for (File configFile : configFiles)
356                     {
357                         System.err.println("         etc/" + configFile.getName());
358                     }
359                 }
360                 else
361                 {
362                     System.err.println(line);
363                 }
364             }
365         }
366         catch (IOException e)
367         {
368             e.printStackTrace(System.err);
369         }
370         finally
371         {
372             if (buf != null)
373             {
374                 try
375                 {
376                     buf.close();
377                 }
378                 catch (IOException ignore)
379                 {
380                     /* ignore */
381                 }
382             }
383         }
384         System.exit(1);
385     }
386 
387     public void invokeMain(ClassLoader classloader, String classname, List<String> args) throws IllegalAccessException, InvocationTargetException,
388     NoSuchMethodException, ClassNotFoundException
389     {
390         Class<?> invoked_class = null;
391 
392         try
393         {
394             invoked_class = classloader.loadClass(classname);
395         }
396         catch (ClassNotFoundException e)
397         {
398             //ignored
399         }
400 
401         if (Config.isDebug() || invoked_class == null)
402         {
403             if (invoked_class == null)
404                 System.err.println("ClassNotFound: " + classname);
405             else
406                 System.err.println(classname + " " + invoked_class.getPackage().getImplementationVersion());
407 
408             if (invoked_class == null)
409             {
410                 usage();
411                 return;
412             }
413         }
414 
415         String argArray[] = args.toArray(new String[0]);
416 
417         Class<?>[] method_param_types = new Class[]
418                                                   { argArray.getClass() };
419 
420         Method main = invoked_class.getDeclaredMethod("main",method_param_types);
421         Object[] method_params = new Object[]
422                                             { argArray };
423 
424         main.invoke(null,method_params);
425     }
426 
427     /* ------------------------------------------------------------ */
428     public static void close(Reader reader)
429     {
430         if (reader == null)
431         {
432             return;
433         }
434         try
435         {
436             reader.close();
437         }
438         catch (IOException e)
439         {
440             e.printStackTrace();
441         }
442     }
443 
444     /* ------------------------------------------------------------ */
445     public static void close(InputStream stream)
446     {
447         if (stream == null)
448         {
449             return;
450         }
451         try
452         {
453             stream.close();
454         }
455         catch (IOException e)
456         {
457             e.printStackTrace();
458         }
459     }
460 
461     /* ------------------------------------------------------------ */
462     public void start(List<String> xmls) throws FileNotFoundException
463     {
464         // Setup Start / Stop Monitoring
465         startMonitor();
466 
467         // Default options (if not specified)
468         if (_activeOptions.isEmpty())
469         {
470             _activeOptions.add("default");
471             _activeOptions.add("*");
472         }
473 
474         // Add mandatory options for secure mode
475         if (_secure)
476         {
477             addMandatoryOption("secure");
478             addMandatoryOption("security");
479         }
480 
481         // Default XMLs (if not specified)
482         if (xmls.isEmpty())
483         {
484             // Do not rely on _jettyHome yet, as initConfig(xmls) defines _jettyHome, and
485             // resolveXmlConfigs(xmls) normalizes the xmls based off it.
486             if (_fromDaemon)
487             {
488                 xmls.add("etc/jetty-logging.xml");
489             }
490             xmls.add("etc/jetty.xml");
491         }
492 
493         // Initialize the Config (start.config)
494         initConfig(xmls);
495 
496         // Normalize the XML config options passed on the command line.
497         xmls = resolveXmlConfigs(xmls);
498 
499         // Get Desired Classpath based on user provided Active Options.
500         Classpath classpath = _config.getCombinedClasspath(_activeOptions);
501 
502         System.setProperty("java.class.path",classpath.toString());
503         ClassLoader cl = classpath.getClassLoader();
504         if (Config.isDebug())
505         {
506             System.err.println("java.class.path=" + System.getProperty("java.class.path"));
507             System.err.println("jetty.home=" + System.getProperty("jetty.home"));
508             System.err.println("java.home=" + System.getProperty("java.home"));
509             System.err.println("java.io.tmpdir=" + System.getProperty("java.io.tmpdir"));
510             System.err.println("java.class.path=" + classpath);
511             System.err.println("classloader=" + cl);
512             System.err.println("classloader.parent=" + cl.getParent());
513         }
514 
515         // Show the usage information and return
516         if (_showUsage)
517         {
518             usage();
519             return;
520         }
521 
522         // Show the version information and return
523         if (_dumpVersions)
524         {
525             showClasspathWithVersions(classpath);
526             return;
527         }
528 
529         // Show all options with version information
530         if (_listOptions)
531         {
532             showAllOptionsWithVersions(classpath);
533             return;
534         }
535 
536         // Show Command Line to execute Jetty
537         if (_dryRun)
538         {
539             showDryRun(classpath,xmls);
540             return;
541         }
542 
543         // Set current context class loader to what is selected.
544         Thread.currentThread().setContextClassLoader(cl);
545 
546         // Initialize the Security
547         initSecurity(cl);
548 
549         // Invoke the Main Class
550         try
551         {
552             // Get main class as defined in start.config
553             String classname = _config.getMainClassname();
554 
555             // Check for override of start class (via "jetty.server" property)
556             String mainClass = System.getProperty("jetty.server");
557             if (mainClass != null)
558                 classname = mainClass;
559 
560             // Check for override of start class (via "main.class" property)
561             mainClass = System.getProperty("main.class");
562             if (mainClass != null)
563                 classname = mainClass;
564 
565             Config.debug("main.class=" + classname);
566 
567             invokeMain(cl,classname,xmls);
568         }
569         catch (Exception e)
570         {
571             e.printStackTrace();
572         }
573     }
574 
575     private String resolveXmlConfig(String xmlFilename) throws FileNotFoundException
576     {
577         File xml = new File(xmlFilename);
578         if (xml.exists() && xml.isFile())
579         {
580             return xml.getAbsolutePath();
581         }
582 
583         xml = new File(_jettyHome,fixPath(xmlFilename));
584         if (xml.exists() && xml.isFile())
585         {
586             return xml.getAbsolutePath();
587         }
588 
589         xml = new File(_jettyHome,fixPath("etc/" + xmlFilename));
590         if (xml.exists() && xml.isFile())
591         {
592             return xml.getAbsolutePath();
593         }
594 
595         throw new FileNotFoundException("Unable to find XML Config: " + xmlFilename);
596     }
597 
598     private void addMandatoryOption(String id)
599     {
600         if (!_activeOptions.contains(id))
601         {
602             _activeOptions.add(id);
603         }
604     }
605 
606     private void showDryRun(Classpath classpath, List<String> xmls)
607     {
608         StringBuffer cmd = new StringBuffer();
609 
610         cmd.append(findJavaBin());
611         cmd.append(" -cp ").append(classpath.toString());
612         cmd.append(" -Djetty.home=").append(_jettyHome);
613         cmd.append(" ").append(_config.getMainClassname());
614         for (String xml : xmls)
615         {
616             cmd.append(" ").append(xml);
617         }
618 
619         System.out.println(cmd);
620     }
621 
622     private String findJavaBin()
623     {
624         File javaHome = new File(System.getProperty("java.home"));
625         if (!javaHome.exists())
626         {
627             return null;
628         }
629 
630         File javabin = findExecutable(javaHome,"bin/java");
631         if (javabin != null)
632         {
633             return javabin.getAbsolutePath();
634         }
635 
636         javabin = findExecutable(javaHome,"bin/java.exe");
637         if (javabin != null)
638         {
639             return javabin.getAbsolutePath();
640         }
641 
642         return "java";
643     }
644 
645     private File findExecutable(File root, String path)
646     {
647         String npath = path.replace('/',File.separatorChar);
648         File exe = new File(root,npath);
649         if (!exe.exists())
650         {
651             return null;
652         }
653         return exe;
654     }
655 
656     private void showAllOptionsWithVersions(Classpath classpath)
657     {
658         Set<String> sectionIds = _config.getSectionIds();
659 
660         StringBuffer msg = new StringBuffer();
661         msg.append("There ");
662         if (sectionIds.size() > 1)
663         {
664             msg.append("are ");
665         }
666         else
667         {
668             msg.append("is ");
669         }
670         msg.append(String.valueOf(sectionIds.size()));
671         msg.append(" OPTION");
672         if (sectionIds.size() > 1)
673         {
674             msg.append("s");
675         }
676         msg.append(" available to use.");
677         System.out.println(msg);
678         System.out.println("Each option is listed along with associated available classpath entries,  in the order that they would appear from that mode.");
679         System.out.println("Note: If using multiple options (eg: 'Server,servlet,webapp,jms,jmx') "
680                 + "then overlapping entries will not be repeated in the eventual classpath.");
681         System.out.println();
682         System.out.printf("${jetty.home} = %s%n",_jettyHome);
683         System.out.println();
684 
685         for (String sectionId : sectionIds)
686         {
687             if (Config.DEFAULT_SECTION.equals(sectionId))
688             {
689                 System.out.println("GLOBAL option (Prepended Entries)");
690             }
691             else if ("*".equals(sectionId))
692             {
693                 System.out.println("GLOBAL option (Appended Entries) (*)");
694             }
695             else
696             {
697                 System.out.printf("Option [%s]",sectionId);
698                 if (Character.isUpperCase(sectionId.charAt(0)))
699                 {
700                     System.out.print(" (Aggregate)");
701                 }
702                 System.out.println();
703             }
704             System.out.println("-------------------------------------------------------------");
705 
706             Classpath sectionCP = _config.getSectionClasspath(sectionId);
707 
708             if (sectionCP.isEmpty())
709             {
710                 System.out.println("Empty option, no classpath entries active.");
711                 System.out.println();
712                 continue;
713             }
714 
715             int i = 0;
716             for (File element : sectionCP.getElements())
717             {
718                 String elementPath = element.getAbsolutePath();
719                 if (elementPath.startsWith(_jettyHome))
720                 {
721                     elementPath = "${jetty.home}" + elementPath.substring(_jettyHome.length());
722                 }
723                 System.out.printf("%2d: %20s | %s\n",i++,getVersion(element),elementPath);
724             }
725 
726             System.out.println();
727         }
728     }
729 
730     private void showClasspathWithVersions(Classpath classpath)
731     {
732         // Iterate through active classpath, and fetch Implementation Version from each entry (if present)
733         // to dump to end user.
734 
735         System.out.println("Active Options: " + _activeOptions);
736 
737         if (classpath.count() == 0)
738         {
739             System.out.println("No version information available show.");
740             return;
741         }
742 
743         System.out.println("Version Information on " + classpath.count() + " entr" + ((classpath.count() > 1)?"ies":"y") + " in the classpath.");
744         System.out.println("Note: order presented here is how they would appear on the classpath.");
745         System.out.println("      changes to the OPTIONS=[option,option,...] command line option will be reflected here.");
746 
747         int i = 0;
748         for (File element : classpath.getElements())
749         {
750             String elementPath = element.getAbsolutePath();
751             if (elementPath.startsWith(_jettyHome))
752             {
753                 elementPath = "${jetty.home}" + elementPath.substring(_jettyHome.length());
754             }
755             System.out.printf("%2d: %20s | %s\n",i++,getVersion(element),elementPath);
756         }
757     }
758 
759     private String fixPath(String path)
760     {
761         return path.replace('/',File.separatorChar);
762     }
763 
764     private String getVersion(File element)
765     {
766         if (element.isDirectory())
767         {
768             return "(dir)";
769         }
770 
771         if (element.isFile())
772         {
773             String name = element.getName().toLowerCase();
774             if (name.endsWith(".jar"))
775             {
776                 return JarVersion.getVersion(element);
777             }
778 
779             if (name.endsWith(".zip"))
780             {
781                 return getZipVersion(element);
782             }
783         }
784 
785         return "";
786     }
787 
788     private String getZipVersion(File element)
789     {
790         // TODO - find version in zip file.  Look for META-INF/MANIFEST.MF ?
791         return "";
792     }
793 
794     private void initSecurity(ClassLoader cl)
795     {
796         // Init the Security Policies
797         try
798         {
799             if (_secure)
800             {
801                 Policy.setPolicy(_config.getPolicyInstance(cl));
802                 System.setSecurityManager(new SecurityManager());
803             }
804             else
805             {
806                 Policy policy = Policy.getPolicy();
807                 if (policy != null)
808                     policy.refresh();
809             }
810         }
811         catch (Exception e)
812         {
813             e.printStackTrace();
814         }
815     }
816 
817     private List<String> resolveXmlConfigs(List<String> xmls) throws FileNotFoundException
818     {
819         List<String> ret = new ArrayList<String>();
820         for (String xml : xmls)
821         {
822             ret.add(resolveXmlConfig(xml));
823         }
824 
825         return ret;
826     }
827 
828     private void initConfig(List<String> xmls)
829     {
830         InputStream cfgstream = null;
831         try
832         {
833             _config.setArgCount(xmls.size());
834 
835             // What start.config should we use?
836             String cfgName = _config.getProperty("START","org/eclipse/jetty/start/start.config");
837             Config.debug("config=" + cfgName);
838 
839             // Look up config as resource first.
840             cfgstream = getClass().getClassLoader().getResourceAsStream(cfgName);
841 
842             // resource not found, try filesystem next
843             if (cfgstream == null)
844                 cfgstream = new FileInputStream(cfgName);
845 
846             // parse the config
847             _config.parse(cfgstream);
848 
849             _jettyHome = _config.getProperty("jetty.home");
850             if (_jettyHome != null)
851             {
852                 _jettyHome = new File(_jettyHome).getCanonicalPath();
853                 System.setProperty("jetty.home",_jettyHome);
854             }
855         }
856         catch (Exception e)
857         {
858             e.printStackTrace();
859             System.exit(1);
860         }
861         finally
862         {
863             close(cfgstream);
864         }
865     }
866 
867     private void startMonitor()
868     {
869         int port = Integer.parseInt(_config.getProperty("STOP.PORT","-1"));
870         String key = _config.getProperty("STOP.KEY",null);
871 
872         Monitor.monitor(port,key);
873     }
874 
875     /**
876      * Stop a running jetty instance.
877      */
878     public void stop(int port, String key)
879     {
880         int _port = port;
881         String _key = key;
882 
883         try
884         {
885             if (_port <= 0)
886                 System.err.println("STOP.PORT system property must be specified");
887             if (_key == null)
888             {
889                 _key = "";
890                 System.err.println("STOP.KEY system property must be specified");
891                 System.err.println("Using empty key");
892             }
893 
894             Socket s = new Socket(InetAddress.getByName("127.0.0.1"),_port);
895             OutputStream out = s.getOutputStream();
896             out.write((_key + "\r\nstop\r\n").getBytes());
897             out.flush();
898             s.close();
899         }
900         catch (ConnectException e)
901         {
902             System.err.println("ERROR: Not running!");
903         }
904         catch (Exception e)
905         {
906             e.printStackTrace();
907         }
908     }
909 }