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