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 java.io.BufferedReader;
22  import java.io.Closeable;
23  import java.io.File;
24  import java.io.FileFilter;
25  import java.io.FileInputStream;
26  import java.io.FileNotFoundException;
27  import java.io.FileOutputStream;
28  import java.io.FileReader;
29  import java.io.FilenameFilter;
30  import java.io.IOException;
31  import java.io.InputStream;
32  import java.io.InputStreamReader;
33  import java.io.LineNumberReader;
34  import java.io.OutputStream;
35  import java.io.PrintStream;
36  import java.lang.reflect.InvocationTargetException;
37  import java.lang.reflect.Method;
38  import java.net.ConnectException;
39  import java.net.InetAddress;
40  import java.net.Socket;
41  import java.net.SocketTimeoutException;
42  import java.net.URL;
43  import java.text.SimpleDateFormat;
44  import java.util.ArrayList;
45  import java.util.Arrays;
46  import java.util.Collections;
47  import java.util.Date;
48  import java.util.HashSet;
49  import java.util.List;
50  import java.util.Locale;
51  import java.util.Properties;
52  import java.util.Set;
53  
54  import javax.naming.OperationNotSupportedException;
55  
56  /*-------------------------------------------*/
57  /**
58   * <p>
59   * Main start class. This class is intended to be the main class listed in the MANIFEST.MF of the start.jar archive. It
60   * allows an application to be started with the command "java -jar start.jar".
61   * </p>
62   * 
63   * <p>
64   * The behaviour of Main is controlled by the parsing of the {@link Config} "org/eclipse/start/start.config" file
65   * obtained as a resource or file.
66   * </p>
67   */
68  public class Main
69  {
70      private static final String START_LOG_FILENAME = "start.log";
71      private static final SimpleDateFormat START_LOG_ROLLOVER_DATEFORMAT = new SimpleDateFormat("yyyy_MM_dd-HHmmSSSSS.'" + START_LOG_FILENAME + "'");
72  
73      private static final int EXIT_USAGE = 1;
74      private static final int ERR_LOGGING = -1;
75      private static final int ERR_INVOKE_MAIN = -2;
76      private static final int ERR_NOT_STOPPED = -4;
77      private static final int ERR_UNKNOWN = -5;
78      private boolean _showUsage = false;
79      private boolean _dumpVersions = false;
80      private boolean _listConfig = false;
81      private boolean _listOptions = false;
82      private boolean _dryRun = false;
83      private boolean _exec = false;
84      private final Config _config = new Config();
85      private final Set<String> _sysProps = new HashSet<String>();
86      private final List<String> _jvmArgs = new ArrayList<String>();
87      private String _startConfig = null;
88  
89      private String _jettyHome;
90  
91      public static void main(String[] args)
92      {
93          try
94          {
95              Main main = new Main();
96              List<String> arguments = main.expandCommandLine(args);
97              List<String> xmls = main.processCommandLine(arguments);
98              if (xmls != null)
99                  main.start(xmls);
100         }
101         catch (Throwable e)
102         {
103             usageExit(e,ERR_UNKNOWN);
104         }
105     }
106 
107     Main() throws IOException
108     {
109         _jettyHome = System.getProperty("jetty.home",".");
110         _jettyHome = new File(_jettyHome).getCanonicalPath();
111     }
112 
113     public List<String> expandCommandLine(String[] args) throws Exception
114     {
115         List<String> arguments = new ArrayList<String>();
116 
117         // add the command line args and look for start.ini args
118         boolean ini = false;
119         for (String arg : args)
120         {
121             if (arg.startsWith("--ini=") || arg.equals("--ini"))
122             {
123                 ini = true;
124                 if (arg.length() > 6)
125                 {
126                     arguments.addAll(loadStartIni(new File(arg.substring(6))));
127                 }
128             }
129             else if (arg.startsWith("--config="))
130             {
131                 _startConfig = arg.substring(9);
132             }
133             else
134             {
135                 arguments.add(arg);
136             }
137         }
138 
139         // if no non-option inis, add the start.ini and start.d
140         if (!ini)
141         {
142             arguments.addAll(0,parseStartIniFiles());
143         }
144 
145         return arguments;
146     }
147 
148     List<String> parseStartIniFiles()
149     {
150         List<String> ini_args = new ArrayList<String>();
151         File start_ini = new File(_jettyHome,"start.ini");
152         if (start_ini.exists())
153             ini_args.addAll(loadStartIni(start_ini));
154            
155         return ini_args;
156     }
157 
158     public List<String> processCommandLine(List<String> arguments) throws Exception
159     {
160         // The XML Configuration Files to initialize with
161         List<String> xmls = new ArrayList<String>();
162 
163         // Process the arguments
164         int startup = 0;
165         for (String arg : arguments)
166         {
167             if ("--help".equals(arg) || "-?".equals(arg))
168             {
169                 _showUsage = true;
170                 continue;
171             }
172 
173             if ("--stop".equals(arg))
174             {
175                 int port = Integer.parseInt(Config.getProperty("STOP.PORT","-1"));
176                 String key = Config.getProperty("STOP.KEY",null);
177                 int timeout = Integer.parseInt(Config.getProperty("STOP.WAIT","0"));
178                 stop(port,key,timeout);
179                 return null;
180             }
181             
182             if (arg.startsWith("--download="))
183             {
184                 download(arg);
185                 continue;
186             }
187 
188             if ("--version".equals(arg) || "-v".equals(arg) || "--info".equals(arg))
189             {
190                 _dumpVersions = true;
191                 continue;
192             }
193 
194             if ("--list-modes".equals(arg) || "--list-options".equals(arg))
195             {
196                 _listOptions = true;
197                 continue;
198             }
199 
200             if ("--list-config".equals(arg))
201             {
202                 _listConfig = true;
203                 continue;
204             }
205 
206             if ("--exec-print".equals(arg) || "--dry-run".equals(arg))
207             {
208                 _dryRun = true;
209                 continue;
210             }
211 
212             if ("--exec".equals(arg))
213             {
214                 _exec = true;
215                 continue;
216             }
217 
218             // Special internal indicator that jetty was started by the jetty.sh Daemon
219             if ("--daemon".equals(arg))
220             {
221                 File startDir = new File(System.getProperty("jetty.logs","logs"));
222                 if (!startDir.exists() || !startDir.canWrite())
223                     startDir = new File(".");
224 
225                 File startLog = new File(startDir,START_LOG_ROLLOVER_DATEFORMAT.format(new Date()));
226 
227                 if (!startLog.exists() && !startLog.createNewFile())
228                 {
229                     // Output about error is lost in majority of cases.
230                     System.err.println("Unable to create: " + startLog.getAbsolutePath());
231                     // Toss a unique exit code indicating this failure.
232                     usageExit(ERR_LOGGING);
233                 }
234 
235                 if (!startLog.canWrite())
236                 {
237                     // Output about error is lost in majority of cases.
238                     System.err.println("Unable to write to: " + startLog.getAbsolutePath());
239                     // Toss a unique exit code indicating this failure.
240                     usageExit(ERR_LOGGING);
241                 }
242                 PrintStream logger = new PrintStream(new FileOutputStream(startLog,false));
243                 System.setOut(logger);
244                 System.setErr(logger);
245                 System.out.println("Establishing " + START_LOG_FILENAME + " on " + new Date());
246                 continue;
247             }
248 
249             if (arg.startsWith("-D"))
250             {
251                 String[] assign = arg.substring(2).split("=",2);
252                 _sysProps.add(assign[0]);
253                 switch (assign.length)
254                 {
255                     case 2:
256                         System.setProperty(assign[0],assign[1]);
257                         break;
258                     case 1:
259                         System.setProperty(assign[0],"");
260                         break;
261                     default:
262                         break;
263                 }
264                 continue;
265             }
266 
267             if (arg.startsWith("-"))
268             {
269                 _jvmArgs.add(arg);
270                 continue;
271             }
272 
273             // Is this a Property?
274             if (arg.indexOf('=') >= 0)
275             {
276                 String[] assign = arg.split("=",2);
277 
278                 switch (assign.length)
279                 {
280                     case 2:
281                         if ("OPTIONS".equals(assign[0]))
282                         {
283                             String opts[] = assign[1].split(",");
284                             for (String opt : opts)
285                                 _config.addActiveOption(opt.trim());
286                         }
287                         else
288                         {
289                             this._config.setProperty(assign[0],assign[1]);
290                         }
291                         break;
292                     case 1:
293                         this._config.setProperty(assign[0],null);
294                         break;
295                     default:
296                         break;
297                 }
298 
299                 continue;
300             }
301 
302             // Anything else is considered an XML file.
303             if (xmls.contains(arg))
304             {
305                 System.out.println("WARN: Argument '" + arg + "' specified multiple times. Check start.ini?");
306                 System.out.println("Use \"java -jar start.jar --help\" for more information.");
307             }
308             xmls.add(arg);
309         }
310 
311         return xmls;
312     }
313 
314     private void download(String arg)
315     {
316         try
317         {
318             String[] split = arg.split(":",3);
319             if (split.length!=3 || "http".equalsIgnoreCase(split[0]) || !split[1].startsWith("//"))
320                 throw new IllegalArgumentException("Not --download=<http uri>:<location>");
321             
322             String location=split[2];
323             if (File.separatorChar!='/')
324                 location.replaceAll("/",File.separator);
325             File file = new File(location);
326             
327             if (Config.isDebug())
328                 System.err.println("Download to "+file.getAbsolutePath()+(file.exists()?" Exists!":""));
329             if (file.exists())
330                 return;
331             
332             URL url = new URL(split[0].substring(11)+":"+split[1]);
333 
334             System.err.println("DOWNLOAD: "+url+" to "+location);
335 
336             if (!file.getParentFile().exists())
337                 file.getParentFile().mkdirs();
338 
339             byte[] buf=new byte[8192];
340             try (InputStream in = url.openStream(); OutputStream out = new FileOutputStream(file);)
341             {
342                 while(true)
343                 {
344                     int len = in.read(buf);
345 
346                     if (len>0)
347                         out.write(buf,0,len);
348                     if (len<0)
349                         break;
350                 }
351             }
352         }
353         catch(Exception e)
354         {
355             System.err.println("ERROR: processing "+arg+"\n"+e);
356             e.printStackTrace();
357             usageExit(EXIT_USAGE);
358         }
359     }
360 
361     private void usage()
362     {
363         String usageResource = "org/eclipse/jetty/start/usage.txt";
364         InputStream usageStream = getClass().getClassLoader().getResourceAsStream(usageResource);
365 
366         if (usageStream == null)
367         {
368             System.err.println("ERROR: detailed usage resource unavailable");
369             usageExit(EXIT_USAGE);
370         }
371 
372         BufferedReader buf = null;
373         try
374         {
375             buf = new BufferedReader(new InputStreamReader(usageStream));
376             String line;
377 
378             while ((line = buf.readLine()) != null)
379             {
380                 if (line.endsWith("@") && line.indexOf('@') != line.lastIndexOf('@'))
381                 {
382                     String indent = line.substring(0,line.indexOf("@"));
383                     String info = line.substring(line.indexOf('@'),line.lastIndexOf('@'));
384 
385                     if (info.equals("@OPTIONS"))
386                     {
387                         List<String> sortedOptions = new ArrayList<String>();
388                         sortedOptions.addAll(_config.getSectionIds());
389                         Collections.sort(sortedOptions);
390 
391                         for (String option : sortedOptions)
392                         {
393                             if ("*".equals(option) || option.trim().length() == 0)
394                                 continue;
395                             System.out.print(indent);
396                             System.out.println(option);
397                         }
398                     }
399                     else if (info.equals("@CONFIGS"))
400                     {
401                         File etc = new File(System.getProperty("jetty.home","."),"etc");
402                         if (!etc.exists() || !etc.isDirectory())
403                         {
404                             System.out.print(indent);
405                             System.out.println("Unable to find/list " + etc);
406                             continue;
407                         }
408 
409                         File configs[] = etc.listFiles(new FileFilter()
410                         {
411                             public boolean accept(File path)
412                             {
413                                 if (!path.isFile())
414                                 {
415                                     return false;
416                                 }
417 
418                                 String name = path.getName().toLowerCase(Locale.ENGLISH);
419                                 return (name.startsWith("jetty") && name.endsWith(".xml"));
420                             }
421                         });
422 
423                         List<File> configFiles = new ArrayList<File>();
424                         configFiles.addAll(Arrays.asList(configs));
425                         Collections.sort(configFiles);
426 
427                         for (File configFile : configFiles)
428                         {
429                             System.out.print(indent);
430                             System.out.print("etc/");
431                             System.out.println(configFile.getName());
432                         }
433                     }
434                     else if (info.equals("@STARTINI"))
435                     {
436                         List<String> ini = parseStartIniFiles();
437                         if (ini != null && ini.size() > 0)
438                         {
439                             for (String a : ini)
440                             {
441                                 System.out.print(indent);
442                                 System.out.println(a);
443                             }
444                         }
445                         else
446                         {
447                             System.out.print(indent);
448                             System.out.println("none");
449                         }
450                     }
451                 }
452                 else
453                 {
454                     System.out.println(line);
455                 }
456             }
457         }
458         catch (IOException e)
459         {
460             usageExit(e,EXIT_USAGE);
461         }
462         finally
463         {
464             close(buf);
465         }
466         System.exit(EXIT_USAGE);
467     }
468 
469     public void invokeMain(ClassLoader classloader, String classname, List<String> args) throws IllegalAccessException, InvocationTargetException,
470             NoSuchMethodException, ClassNotFoundException
471     {
472         Class<?> invoked_class = null;
473 
474         try
475         {
476             invoked_class = classloader.loadClass(classname);
477         }
478         catch (ClassNotFoundException e)
479         {
480             e.printStackTrace();
481         }
482 
483         if (Config.isDebug() || invoked_class == null)
484         {
485             if (invoked_class == null)
486             {
487                 System.err.println("ClassNotFound: " + classname);
488             }
489             else
490             {
491                 System.err.println(classname + " " + invoked_class.getPackage().getImplementationVersion());
492             }
493 
494             if (invoked_class == null)
495             {
496                 usageExit(ERR_INVOKE_MAIN);
497                 return;
498             }
499         }
500 
501         String argArray[] = args.toArray(new String[0]);
502 
503         Class<?>[] method_param_types = new Class[]
504         { argArray.getClass() };
505 
506         Method main = invoked_class.getDeclaredMethod("main",method_param_types);
507         Object[] method_params = new Object[]
508         { argArray };
509         main.invoke(null,method_params);
510     }
511 
512     /* ------------------------------------------------------------ */
513     public static void close(Closeable c)
514     {
515         if (c == null)
516         {
517             return;
518         }
519         try
520         {
521             c.close();
522         }
523         catch (IOException e)
524         {
525             e.printStackTrace(System.err);
526         }
527     }
528 
529     /* ------------------------------------------------------------ */
530     public void start(List<String> xmls) throws IOException, InterruptedException
531     {
532         // Load potential Config (start.config)
533         List<String> configuredXmls = loadConfig(xmls);
534 
535         // No XML defined in start.config or command line. Can't execute.
536         if (configuredXmls.isEmpty())
537         {
538             throw new FileNotFoundException("No XML configuration files specified in start.config or command line.");
539         }
540 
541         // Normalize the XML config options passed on the command line.
542         configuredXmls = resolveXmlConfigs(configuredXmls);
543 
544         // Get Desired Classpath based on user provided Active Options.
545         Classpath classpath = _config.getActiveClasspath();
546 
547         System.setProperty("java.class.path",classpath.toString());
548         ClassLoader cl = classpath.getClassLoader();
549         if (Config.isDebug())
550         {
551             System.err.println("java.class.path=" + System.getProperty("java.class.path"));
552             System.err.println("jetty.home=" + System.getProperty("jetty.home"));
553             System.err.println("java.home=" + System.getProperty("java.home"));
554             System.err.println("java.io.tmpdir=" + System.getProperty("java.io.tmpdir"));
555             System.err.println("java.class.path=" + classpath);
556             System.err.println("classloader=" + cl);
557             System.err.println("classloader.parent=" + cl.getParent());
558             System.err.println("properties=" + Config.getProperties());
559         }
560 
561         // Show the usage information and return
562         if (_showUsage)
563         {
564             usage();
565             return;
566         }
567 
568         // Show the version information and return
569         if (_dumpVersions)
570         {
571             showClasspathWithVersions(classpath);
572             return;
573         }
574 
575         // Show all options with version information
576         if (_listOptions)
577         {
578             showAllOptionsWithVersions();
579             return;
580         }
581 
582         if (_listConfig)
583         {
584             listConfig();
585             return;
586         }
587 
588         // Show Command Line to execute Jetty
589         if (_dryRun)
590         {
591             CommandLineBuilder cmd = buildCommandLine(classpath,configuredXmls);
592             System.out.println(cmd.toString());
593             return;
594         }
595 
596         // execute Jetty in another JVM
597         if (_exec)
598         {
599             CommandLineBuilder cmd = buildCommandLine(classpath,configuredXmls);
600 
601             ProcessBuilder pbuilder = new ProcessBuilder(cmd.getArgs());
602             final Process process = pbuilder.start();
603             Runtime.getRuntime().addShutdownHook(new Thread()
604             {
605                 @Override
606                 public void run()
607                 {
608                     Config.debug("Destroying " + process);
609                     process.destroy();
610                 }
611             });
612 
613             copyInThread(process.getErrorStream(),System.err);
614             copyInThread(process.getInputStream(),System.out);
615             copyInThread(System.in,process.getOutputStream());
616             process.waitFor();
617             System.exit(0); // exit JVM when child process ends.
618             return;
619         }
620 
621         if (_jvmArgs.size() > 0 || _sysProps.size() > 0)
622         {
623             System.err.println("WARNING: System properties and/or JVM args set.  Consider using --dry-run or --exec");
624         }
625 
626         // Set current context class loader to what is selected.
627         Thread.currentThread().setContextClassLoader(cl);
628 
629         // Invoke the Main Class
630         try
631         {
632             // Get main class as defined in start.config
633             String classname = _config.getMainClassname();
634 
635             // Check for override of start class (via "jetty.server" property)
636             String mainClass = System.getProperty("jetty.server");
637             if (mainClass != null)
638             {
639                 classname = mainClass;
640             }
641 
642             // Check for override of start class (via "main.class" property)
643             mainClass = System.getProperty("main.class");
644             if (mainClass != null)
645             {
646                 classname = mainClass;
647             }
648 
649             Config.debug("main.class=" + classname);
650 
651             invokeMain(cl,classname,configuredXmls);
652         }
653         catch (Exception e)
654         {
655             usageExit(e,ERR_INVOKE_MAIN);
656         }
657     }
658 
659     private void copyInThread(final InputStream in, final OutputStream out)
660     {
661         new Thread(new Runnable()
662         {
663             public void run()
664             {
665                 try
666                 {
667                     byte[] buf = new byte[1024];
668                     int len = in.read(buf);
669                     while (len > 0)
670                     {
671                         out.write(buf,0,len);
672                         len = in.read(buf);
673                     }
674                 }
675                 catch (IOException e)
676                 {
677                     // e.printStackTrace();
678                 }
679             }
680 
681         }).start();
682     }
683 
684     private String resolveXmlConfig(String xmlFilename) throws FileNotFoundException
685     {
686         if (!xmlFilename.toLowerCase(Locale.ENGLISH).endsWith(".xml"))
687         {
688             // Nothing to resolve.
689             return xmlFilename;
690         }
691 
692         File xml = new File(xmlFilename);
693         if (xml.exists() && xml.isFile())
694         {
695             return xml.getAbsolutePath();
696         }
697 
698         xml = new File(_jettyHome,fixPath(xmlFilename));
699         if (xml.exists() && xml.isFile())
700         {
701             return xml.getAbsolutePath();
702         }
703 
704         xml = new File(_jettyHome,fixPath("etc/" + xmlFilename));
705         if (xml.exists() && xml.isFile())
706         {
707             return xml.getAbsolutePath();
708         }
709 
710         throw new FileNotFoundException("Unable to find XML Config: " + xmlFilename);
711     }
712 
713     CommandLineBuilder buildCommandLine(Classpath classpath, List<String> xmls) throws IOException
714     {
715         CommandLineBuilder cmd = new CommandLineBuilder(findJavaBin());
716 
717         for (String x : _jvmArgs)
718         {
719             cmd.addArg(x);
720         }
721         cmd.addRawArg("-Djetty.home=" + _jettyHome);
722 
723         // Special Stop/Shutdown properties
724         ensureSystemPropertySet("STOP.PORT");
725         ensureSystemPropertySet("STOP.KEY");
726 
727         // System Properties
728         for (String p : _sysProps)
729         {
730             String v = System.getProperty(p);
731             cmd.addEqualsArg("-D" + p,v);
732         }
733 
734         cmd.addArg("-cp");
735         cmd.addRawArg(classpath.toString());
736         cmd.addRawArg(_config.getMainClassname());
737 
738         // Check if we need to pass properties as a file
739         Properties properties = Config.getProperties();
740         if (properties.size() > 0)
741         {
742             File prop_file = File.createTempFile("start",".properties");
743             if (!_dryRun)
744                 prop_file.deleteOnExit();
745             try (OutputStream out = new FileOutputStream(prop_file))
746             {
747                 properties.store(out,"start.jar properties");
748             }
749             cmd.addArg(prop_file.getAbsolutePath());
750         }
751 
752         for (String xml : xmls)
753         {
754             cmd.addRawArg(xml);
755         }
756         return cmd;
757     }
758 
759     /**
760      * Ensure that the System Properties are set (if defined as a System property, or start.config property, or
761      * start.ini property)
762      * 
763      * @param key
764      *            the key to be sure of
765      */
766     private void ensureSystemPropertySet(String key)
767     {
768         if (_sysProps.contains(key))
769         {
770             return; // done
771         }
772 
773         Properties props = Config.getProperties();
774         if (props.containsKey(key))
775         {
776             String val = props.getProperty(key,null);
777             if (val == null)
778             {
779                 return; // no value to set
780             }
781             // setup system property
782             _sysProps.add(key);
783             System.setProperty(key,val);
784         }
785     }
786 
787     private String findJavaBin()
788     {
789         File javaHome = new File(System.getProperty("java.home"));
790         if (!javaHome.exists())
791         {
792             return null;
793         }
794 
795         File javabin = findExecutable(javaHome,"bin/java");
796         if (javabin != null)
797         {
798             return javabin.getAbsolutePath();
799         }
800 
801         javabin = findExecutable(javaHome,"bin/java.exe");
802         if (javabin != null)
803         {
804             return javabin.getAbsolutePath();
805         }
806 
807         return "java";
808     }
809 
810     private File findExecutable(File root, String path)
811     {
812         String npath = path.replace('/',File.separatorChar);
813         File exe = new File(root,npath);
814         if (!exe.exists())
815         {
816             return null;
817         }
818         return exe;
819     }
820 
821     private void showAllOptionsWithVersions()
822     {
823         Set<String> sectionIds = _config.getSectionIds();
824 
825         StringBuffer msg = new StringBuffer();
826         msg.append("There ");
827         if (sectionIds.size() > 1)
828         {
829             msg.append("are ");
830         }
831         else
832         {
833             msg.append("is ");
834         }
835         msg.append(String.valueOf(sectionIds.size()));
836         msg.append(" OPTION");
837         if (sectionIds.size() > 1)
838         {
839             msg.append("s");
840         }
841         msg.append(" available to use.");
842         System.out.println(msg);
843         System.out.println("Each option is listed along with associated available classpath entries,  in the order that they would appear from that mode.");
844         System.out.println("Note: If using multiple options (eg: 'Server,servlet,webapp,jms,jmx') "
845                 + "then overlapping entries will not be repeated in the eventual classpath.");
846         System.out.println();
847         System.out.printf("${jetty.home} = %s%n",_jettyHome);
848         System.out.println();
849 
850         for (String sectionId : sectionIds)
851         {
852             if (Config.DEFAULT_SECTION.equals(sectionId))
853             {
854                 System.out.println("GLOBAL option (Prepended Entries)");
855             }
856             else if ("*".equals(sectionId))
857             {
858                 System.out.println("GLOBAL option (Appended Entries) (*)");
859             }
860             else
861             {
862                 System.out.printf("Option [%s]",sectionId);
863                 if (Character.isUpperCase(sectionId.charAt(0)))
864                 {
865                     System.out.print(" (Aggregate)");
866                 }
867                 System.out.println();
868             }
869             System.out.println("-------------------------------------------------------------");
870 
871             Classpath sectionCP = _config.getSectionClasspath(sectionId);
872 
873             if (sectionCP.isEmpty())
874             {
875                 System.out.println("Empty option, no classpath entries active.");
876                 System.out.println();
877                 continue;
878             }
879 
880             int i = 0;
881             for (File element : sectionCP.getElements())
882             {
883                 String elementPath = element.getAbsolutePath();
884                 if (elementPath.startsWith(_jettyHome))
885                 {
886                     elementPath = "${jetty.home}" + elementPath.substring(_jettyHome.length());
887                 }
888                 System.out.printf("%2d: %20s | %s\n",i++,getVersion(element),elementPath);
889             }
890 
891             System.out.println();
892         }
893     }
894 
895     private void showClasspathWithVersions(Classpath classpath)
896     {
897         // Iterate through active classpath, and fetch Implementation Version from each entry (if present)
898         // to dump to end user.
899 
900         System.out.println("Active Options: " + _config.getActiveOptions());
901 
902         if (classpath.count() == 0)
903         {
904             System.out.println("No version information available show.");
905             return;
906         }
907 
908         System.out.println("Version Information on " + classpath.count() + " entr" + ((classpath.count() > 1)?"ies":"y") + " in the classpath.");
909         System.out.println("Note: order presented here is how they would appear on the classpath.");
910         System.out.println("      changes to the OPTIONS=[option,option,...] command line option will be reflected here.");
911 
912         int i = 0;
913         for (File element : classpath.getElements())
914         {
915             String elementPath = element.getAbsolutePath();
916             if (elementPath.startsWith(_jettyHome))
917             {
918                 elementPath = "${jetty.home}" + elementPath.substring(_jettyHome.length());
919             }
920             System.out.printf("%2d: %20s | %s\n",i++,getVersion(element),elementPath);
921         }
922     }
923 
924     private String fixPath(String path)
925     {
926         return path.replace('/',File.separatorChar);
927     }
928 
929     private String getVersion(File element)
930     {
931         if (element.isDirectory())
932         {
933             return "(dir)";
934         }
935 
936         if (element.isFile())
937         {
938             String name = element.getName().toLowerCase(Locale.ENGLISH);
939             if (name.endsWith(".jar"))
940             {
941                 return JarVersion.getVersion(element);
942             }
943         }
944 
945         return "";
946     }
947 
948     private List<String> resolveXmlConfigs(List<String> xmls) throws FileNotFoundException
949     {
950         List<String> ret = new ArrayList<String>();
951         for (String xml : xmls)
952         {
953             ret.add(resolveXmlConfig(xml));
954         }
955 
956         return ret;
957     }
958 
959     private void listConfig()
960     {
961         InputStream cfgstream = null;
962         try
963         {
964             cfgstream = getConfigStream();
965             byte[] buf = new byte[4096];
966 
967             int len = 0;
968 
969             while (len >= 0)
970             {
971                 len = cfgstream.read(buf);
972                 if (len > 0)
973                     System.out.write(buf,0,len);
974             }
975         }
976         catch (Exception e)
977         {
978             usageExit(e,ERR_UNKNOWN);
979         }
980         finally
981         {
982             close(cfgstream);
983         }
984     }
985 
986     /**
987      * Load Configuration.
988      * 
989      * No specific configuration is real until a {@link Config#getCombinedClasspath(java.util.Collection)} is used to
990      * execute the {@link Class} specified by {@link Config#getMainClassname()} is executed.
991      * 
992      * @param xmls
993      *            the command line specified xml configuration options.
994      * @return the list of xml configurations arriving via command line and start.config choices.
995      */
996     private List<String> loadConfig(List<String> xmls)
997     {
998         InputStream cfgstream = null;
999         try
1000         {
1001             // Pass in xmls.size into Config so that conditions based on "nargs" work.
1002             _config.setArgCount(xmls.size());
1003 
1004             cfgstream = getConfigStream();
1005 
1006             // parse the config
1007             _config.parse(cfgstream);
1008 
1009             _jettyHome = Config.getProperty("jetty.home",_jettyHome);
1010             if (_jettyHome != null)
1011             {
1012                 _jettyHome = new File(_jettyHome).getCanonicalPath();
1013                 System.setProperty("jetty.home",_jettyHome);
1014             }
1015 
1016             // Collect the configured xml configurations.
1017             List<String> ret = new ArrayList<String>();
1018             ret.addAll(xmls); // add command line provided xmls first.
1019             for (String xmlconfig : _config.getXmlConfigs())
1020             {
1021                 // add xmlconfigs arriving via start.config
1022                 if (!ret.contains(xmlconfig))
1023                 {
1024                     ret.add(xmlconfig);
1025                 }
1026             }
1027 
1028             return ret;
1029         }
1030         catch (Exception e)
1031         {
1032             usageExit(e,ERR_UNKNOWN);
1033             return null; // never executed (just here to satisfy javac compiler)
1034         }
1035         finally
1036         {
1037             close(cfgstream);
1038         }
1039     }
1040 
1041     private InputStream getConfigStream() throws FileNotFoundException
1042     {
1043         String config = _startConfig;
1044         if (config == null || config.length() == 0)
1045         {
1046             config = System.getProperty("START","org/eclipse/jetty/start/start.config");
1047         }
1048 
1049         Config.debug("config=" + config);
1050 
1051         // Look up config as resource first.
1052         InputStream cfgstream = getClass().getClassLoader().getResourceAsStream(config);
1053 
1054         // resource not found, try filesystem next
1055         if (cfgstream == null)
1056         {
1057             cfgstream = new FileInputStream(config);
1058         }
1059 
1060         return cfgstream;
1061     }
1062 
1063     /**
1064      * Stop a running jetty instance.
1065      */
1066     public void stop(int port, String key)
1067     {
1068         stop(port,key,0);
1069     }
1070 
1071     public void stop(int port, String key, int timeout)
1072     {
1073         int _port = port;
1074         String _key = key;
1075 
1076         try
1077         {
1078             if (_port <= 0)
1079             {
1080                 System.err.println("STOP.PORT system property must be specified");
1081             }
1082             if (_key == null)
1083             {
1084                 _key = "";
1085                 System.err.println("STOP.KEY system property must be specified");
1086                 System.err.println("Using empty key");
1087             }
1088 
1089             Socket s = new Socket(InetAddress.getByName("127.0.0.1"),_port);
1090             if (timeout > 0)
1091                 s.setSoTimeout(timeout * 1000);
1092             try
1093             {
1094                 OutputStream out = s.getOutputStream();
1095                 out.write((_key + "\r\nstop\r\n").getBytes());
1096                 out.flush();
1097 
1098                 if (timeout > 0)
1099                 {
1100                     System.err.printf("Waiting %,d seconds for jetty to stop%n",timeout);
1101                     LineNumberReader lin = new LineNumberReader(new InputStreamReader(s.getInputStream()));
1102                     String response;
1103                     while ((response = lin.readLine()) != null)
1104                     {
1105                         Config.debug("Received \"" + response + "\"");
1106                         if ("Stopped".equals(response))
1107                             System.err.println("Server reports itself as Stopped");
1108                     }
1109                 }
1110             }
1111             finally
1112             {
1113                 s.close();
1114             }
1115         }
1116         catch (SocketTimeoutException e)
1117         {
1118             System.err.println("Timed out waiting for stop confirmation");
1119             System.exit(ERR_UNKNOWN);
1120         }
1121         catch (ConnectException e)
1122         {
1123             usageExit(e,ERR_NOT_STOPPED);
1124         }
1125         catch (Exception e)
1126         {
1127             usageExit(e,ERR_UNKNOWN);
1128         }
1129     }
1130 
1131     static void usageExit(Throwable t, int exit)
1132     {
1133         t.printStackTrace(System.err);
1134         System.err.println();
1135         System.err.println("Usage: java -jar start.jar [options] [properties] [configs]");
1136         System.err.println("       java -jar start.jar --help  # for more information");
1137         System.exit(exit);
1138     }
1139 
1140     static void usageExit(int exit)
1141     {
1142         System.err.println();
1143         System.err.println("Usage: java -jar start.jar [options] [properties] [configs]");
1144         System.err.println("       java -jar start.jar --help  # for more information");
1145         System.exit(exit);
1146     }
1147 
1148     /**
1149      * Convert a start.ini format file into an argument list.
1150      */
1151     List<String> loadStartIni(File ini)
1152     {
1153         if (!ini.exists())
1154         {
1155             System.err.println("Warning - can't find ini file: " + ini);
1156             // No start.ini found, skip load.
1157             return Collections.emptyList();
1158         }
1159 
1160         List<String> args = new ArrayList<String>();
1161 
1162         FileReader reader = null;
1163         BufferedReader buf = null;
1164         try
1165         {
1166             reader = new FileReader(ini);
1167             buf = new BufferedReader(reader);
1168 
1169             String arg;
1170             while ((arg = buf.readLine()) != null)
1171             {
1172                 arg = arg.trim();
1173                 if (arg.length() == 0 || arg.startsWith("#"))
1174                 {
1175                     continue;
1176                 }
1177                 
1178                 if (arg.endsWith("/"))
1179                 {
1180                     try
1181                     {
1182                         File start_d = new File(arg);
1183                         if (!start_d.exists() || !start_d.isDirectory())
1184                             start_d = new File(_jettyHome,arg);
1185                         
1186                         if (start_d.isDirectory())
1187                         {
1188                             File[] inis = start_d.listFiles(new FilenameFilter()
1189                             {
1190                                 @Override
1191                                 public boolean accept(File dir, String name)
1192                                 {
1193                                     return name.toLowerCase(Locale.ENGLISH).endsWith(".ini");
1194                                 }
1195                             });
1196                             Arrays.sort(inis);
1197                             
1198                             for (File i : inis)
1199                                 args.addAll(loadStartIni(i));
1200      
1201                             continue;
1202                         }
1203                     }
1204                     catch(Exception e)
1205                     {
1206                         e.printStackTrace();
1207                     }
1208                 }
1209                 
1210                 args.add(arg);
1211             }
1212         }
1213         catch (IOException e)
1214         {
1215             usageExit(e,ERR_UNKNOWN);
1216         }
1217         finally
1218         {
1219             Main.close(buf);
1220             Main.close(reader);
1221         }
1222 
1223         return args;
1224     }
1225 
1226     void addJvmArgs(List<String> jvmArgs)
1227     {
1228         _jvmArgs.addAll(jvmArgs);
1229     }
1230 }