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