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