View Javadoc

1   // ========================================================================
2   // Copyright (c) Webtide LLC
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   //
8   // The Eclipse Public License is available at
9   // http://www.eclipse.org/legal/epl-v10.html
10  //
11  // The Apache License v2.0 is available at
12  // http://www.apache.org/licenses/LICENSE-2.0.txt
13  //
14  // You may elect to redistribute this code under either of these licenses.
15  // ========================================================================
16  
17  package org.eclipse.jetty.start;
18  
19  import java.io.BufferedReader;
20  import java.io.File;
21  import java.io.FileFilter;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.io.InputStreamReader;
25  import java.io.Reader;
26  import java.io.StringReader;
27  import java.lang.reflect.Constructor;
28  import java.lang.reflect.InvocationTargetException;
29  import java.net.URL;
30  import java.security.Policy;
31  import java.text.CollationKey;
32  import java.text.Collator;
33  import java.util.ArrayList;
34  import java.util.Arrays;
35  import java.util.Collection;
36  import java.util.Collections;
37  import java.util.Comparator;
38  import java.util.Enumeration;
39  import java.util.HashMap;
40  import java.util.Iterator;
41  import java.util.List;
42  import java.util.Map;
43  import java.util.Properties;
44  import java.util.Set;
45  import java.util.StringTokenizer;
46  import java.util.TreeSet;
47  
48  /**
49   * <p>
50   * It allows an application to be started with the command <code>"java -jar start.jar"</code>.
51   * </p>
52   * 
53   * <p>
54   * The behaviour of Main is controlled by the <code>"org/eclipse/start/start.config"</code> file obtained as a resource
55   * or file. This can be overridden with the START system property. The format of each line in this file is:
56   * </p>
57   * 
58   * <p>
59   * Each line contains entry in the format:
60   * </p>
61   * 
62   * <pre>
63   *   SUBJECT [ [!] CONDITION [AND|OR] ]*
64   * </pre>
65   * 
66   * <p>
67   * where SUBJECT:
68   * </p>
69   * <ul>
70   * <li>ends with <code>".class"</code> is the Main class to run.</li>
71   * <li>ends with <code>".xml"</code> is a configuration file for the command line</li>
72   * <li>ends with <code>"/"</code> is a directory from which to add all jar and zip files.</li>
73   * <li>ends with <code>"/*"</code> is a directory from which to add all unconsidered jar and zip files.</li>
74   * <li>ends with <code>"/**"</code> is a directory from which to recursively add all unconsidered jar and zip files.</li>
75   * <li>Containing <code>=</code> are used to assign system properties.</li>
76   * <li>Containing <code>~=</code> are used to assign start properties.</li>
77   * <li>Containing <code>/=</code> are used to assign a canonical path.</li>
78   * <li>all other subjects are treated as files to be added to the classpath.</li>
79   * </ul>
80   * 
81   * <p>
82   * property expansion:
83   * </p>
84   * <ul>
85   * <li><code>${name}</code> is expanded to a start property</li>
86   * <li><code>$(name)</code> is expanded to either a start property or a system property.</li>
87   * <li>The start property <code>${version}</code> is defined as the version of the start.jar</li>
88   * </ul>
89   * 
90   * <p>
91   * Files starting with <code>"/"</code> are considered absolute, all others are relative to the home directory.
92   * </p>
93   * 
94   * <p>
95   * CONDITION is one of:
96   * </p>
97   * <ul>
98   * <li><code>always</code></li>
99   * <li><code>never</code></li>
100  * <li><code>available classname</code> - true if class on classpath</li>
101  * <li><code>property name</code> - true if set as start property</li>
102  * <li><code>system name</code> - true if set as system property</li>
103  * <li><code>exists file</code> - true if file/dir exists</li>
104  * <li><code>java OPERATOR version</code> - java version compared to literal</li>
105  * <li><code>nargs OPERATOR number</code> - number of command line args compared to literal</li>
106  * <li>OPERATOR := one of <code>"&lt;"</code>,<code>"&gt;"</code>,<code>"&lt;="</code>,<code>"&gt;="</code>,
107  * <code>"=="</code>,<code>"!="</code></li>
108  * </ul>
109  * 
110  * <p>
111  * CONDITIONS can be combined with <code>AND</code> <code>OR</code> or <code>!</code>, with <code>AND</code> being the
112  * assume operator for a list of CONDITIONS.
113  * </p>
114  * 
115  * <p>
116  * Classpath operations are evaluated on the fly, so once a class or jar is added to the classpath, subsequent available
117  * conditions will see that class.
118  * </p>
119  * 
120  * <p>
121  * The configuration file may be divided into sections with option names like: [ssl,default]
122  * </p>
123  * 
124  * <p>
125  * Note: a special discovered section identifier <code>[=path_to_directory/*]</code> is allowed to auto-create section
126  * IDs, based on directory names found in the path specified in the "path_to_directory/" part of the identifier.
127  * </p>
128  * 
129  * <p>
130  * Clauses after a section header will only be included if they match one of the tags in the options property. By
131  * default options are set to "default,*" or the OPTIONS property may be used to pass in a list of tags, eg. :
132  * </p>
133  * 
134  * <pre>
135  *    java -jar start.jar OPTIONS=jetty,jsp,ssl
136  * </pre>
137  * 
138  * <p>
139  * The tag '*' is always appended to the options, so any section with the * tag is always applied.
140  * </p>
141  * 
142  * <p>
143  * The property map maintained by this class is static and shared between all instances in the same classloader
144  * </p>
145  */
146 public class Config
147 {
148     public static final String DEFAULT_SECTION = "";
149     static
150     {
151         Package pkg = Config.class.getPackage();
152         if (pkg != null && 
153                 "Eclipse.org - Jetty".equals(pkg.getImplementationVendor()) &&
154                 (pkg.getImplementationVersion() != null))
155             _version = pkg.getImplementationVersion();
156         else
157             _version = System.getProperty("jetty.version","Unknown");
158     }
159 
160     /**
161      * Natural language sorting for key names.
162      */
163     private final Comparator<String> keySorter = new Comparator<String>()
164     {
165         private final Collator collator = Collator.getInstance();
166 
167         public int compare(String o1, String o2)
168         {
169             CollationKey key1 = collator.getCollationKey(o1);
170             CollationKey key2 = collator.getCollationKey(o2);
171             return key1.compareTo(key2);
172         }
173     };
174 
175     private static final String _version;
176     private static boolean DEBUG = false;
177     private static final Map<String, String> __properties = new HashMap<String, String>();
178     private final Map<String, Classpath> _classpaths = new HashMap<String, Classpath>();
179     private final List<String> _xml = new ArrayList<String>();
180     private String _policyDirectory = null;
181     private String _classname = null;
182 
183     private int argCount = 0;
184     
185     private final Set<String> _activeOptions = new TreeSet<String>(new Comparator<String>()
186     {
187         // Make sure "*" is always at the end of the list
188         public int compare(String o1, String o2)
189         {
190             if ("*".equals(o1))
191             {
192                 return 1;
193             }
194             if ("*".equals(o2))
195             {
196                 return -1;
197             }
198             return o1.compareTo(o2);
199         }
200     });
201 
202     private boolean addClasspathComponent(List<String> sections, String component)
203     {
204         for (String section : sections)
205         {
206             Classpath cp = _classpaths.get(section);
207             if (cp == null)
208                 cp = new Classpath();
209 
210             boolean added = cp.addComponent(component);
211             _classpaths.put(section,cp);
212 
213             if (!added)
214             {
215                 // First failure means all failed.
216                 return false;
217             }
218         }
219 
220         return true;
221     }
222 
223     private boolean addClasspathPath(List<String> sections, String path)
224     {
225         for (String section : sections)
226         {
227             Classpath cp = _classpaths.get(section);
228             if (cp == null)
229             {
230                 cp = new Classpath();
231             }
232             if (!cp.addClasspath(path))
233             {
234                 // First failure means all failed.
235                 return false;
236             }
237             _classpaths.put(section,cp);
238         }
239 
240         return true;
241     }
242 
243     private void addJars(List<String> sections, File dir, boolean recurse) throws IOException
244     {
245         List<File> entries = new ArrayList<File>();
246         File[] files = dir.listFiles();
247         if (files == null)
248         {
249             // No files found, skip it.
250             return;
251         }
252         entries.addAll(Arrays.asList(files));
253         Collections.sort(entries,FilenameComparator.INSTANCE);
254 
255         for (File entry : entries)
256         {
257             if (entry.isDirectory())
258             {
259                 if (recurse)
260                     addJars(sections,entry,recurse);
261             }
262             else
263             {
264                 String name = entry.getName().toLowerCase();
265                 if (name.endsWith(".jar") || name.endsWith(".zip"))
266                 {
267                     String jar = entry.getCanonicalPath();
268                     boolean added = addClasspathComponent(sections,jar);
269                     debug((added?"  CLASSPATH+=":"  !") + jar);
270                 }
271             }
272         }
273     }
274 
275     private void close(InputStream stream)
276     {
277         if (stream == null)
278             return;
279 
280         try
281         {
282             stream.close();
283         }
284         catch (IOException ignore)
285         {
286             /* ignore */
287         }
288     }
289 
290     private void close(Reader reader)
291     {
292         if (reader == null)
293             return;
294 
295         try
296         {
297             reader.close();
298         }
299         catch (IOException ignore)
300         {
301             /* ignore */
302         }
303     }
304 
305     public static boolean isDebug()
306     {
307         return DEBUG;
308     }
309 
310     public static void debug(String msg)
311     {
312         if (DEBUG)
313         {
314             System.err.println(msg);
315         }
316     }
317 
318     public static void debug(Throwable t)
319     {
320         if (DEBUG)
321         {
322             t.printStackTrace(System.err);
323         }
324     }
325 
326     private String expand(String s)
327     {
328         int i1 = 0;
329         int i2 = 0;
330         while (s != null)
331         {
332             i1 = s.indexOf("$(",i2);
333             if (i1 < 0)
334                 break;
335             i2 = s.indexOf(")",i1 + 2);
336             if (i2 < 0)
337                 break;
338             String name = s.substring(i1 + 2,i2);
339             String property = getProperty(name);
340             s = s.substring(0,i1) + property + s.substring(i2 + 1);
341         }
342 
343         i1 = 0;
344         i2 = 0;
345         while (s != null)
346         {
347             i1 = s.indexOf("${",i2);
348             if (i1 < 0)
349                 break;
350             i2 = s.indexOf("}",i1 + 2);
351             if (i2 < 0)
352                 break;
353             String name = s.substring(i1 + 2,i2);
354             String property = getProperty(name);
355             s = s.substring(0,i1) + property + s.substring(i2 + 1);
356         }
357 
358         return s;
359     }
360 
361     /**
362      * Get the default classpath.
363      * 
364      * @return the default classpath
365      */
366     public Classpath getClasspath()
367     {
368         return _classpaths.get(DEFAULT_SECTION);
369     }
370 
371     /**
372      * Get the active classpath, as dictated by OPTIONS= entries.
373      * 
374      * @return the Active classpath
375      * @see #getCombinedClasspath(Collection)
376      */
377     public Classpath getActiveClasspath()
378     {
379         return getCombinedClasspath(_activeOptions);
380     }
381 
382     /**
383      * Get the combined classpath representing the default classpath plus all named sections.
384      * 
385      * NOTE: the default classpath will be prepended, and the '*' classpath will be appended.
386      * 
387      * @param optionIds
388      *            the list of section ids to fetch
389      * @return the {@link Classpath} representing combination all of the selected sectionIds, combined with the default
390      *         section id, and '*' special id.
391      */
392     public Classpath getCombinedClasspath(Collection<String> optionIds)
393     {
394         Classpath cp = new Classpath();
395 
396         cp.overlay(_classpaths.get(DEFAULT_SECTION));
397         for (String optionId : optionIds)
398         {
399             Classpath otherCp = _classpaths.get(optionId);
400             if (otherCp == null)
401             {
402                 throw new IllegalArgumentException("No such OPTIONS: " + optionId);
403             }
404             cp.overlay(otherCp);
405         }
406         cp.overlay(_classpaths.get("*"));
407         return cp;
408     }
409 
410     public String getMainClassname()
411     {
412         return _classname;
413     }
414 
415     public static void clearProperties()
416     {
417         __properties.clear();
418     }
419     
420     public static Properties getProperties()
421     {
422         Properties properties = new Properties();
423         // Add System Properties First
424         Enumeration<?> ensysprop = System.getProperties().propertyNames();
425         while(ensysprop.hasMoreElements()) {
426             String name = (String)ensysprop.nextElement();
427             properties.put(name, System.getProperty(name));
428         }
429         // Add Config Properties Next (overwriting any System Properties that exist)
430         for (String key : __properties.keySet()) {
431             properties.put(key,__properties.get(key));
432         }
433         return properties;
434     }
435     
436     public static String getProperty(String name)
437     {
438         if ("version".equalsIgnoreCase(name)) {
439             return _version;
440         }
441         // Search Config Properties First
442         if (__properties.containsKey(name)) {
443             return __properties.get(name);
444         }
445         // Return what exists in System.Properties otherwise.
446         return System.getProperty(name);
447     }
448 
449     public static String getProperty(String name, String defaultValue)
450     {
451         // Search Config Properties First
452         if (__properties.containsKey(name))
453             return __properties.get(name);
454         // Return what exists in System.Properties otherwise.
455         return System.getProperty(name, defaultValue);
456     }
457 
458     /**
459      * Get the classpath for the named section
460      * 
461      * @param sectionId
462      * @return the classpath for the specified section id
463      */
464     public Classpath getSectionClasspath(String sectionId)
465     {
466         return _classpaths.get(sectionId);
467     }
468 
469     /**
470      * Get the list of section Ids.
471      * 
472      * @return the set of unique section ids
473      */
474     public Set<String> getSectionIds()
475     {
476         Set<String> ids = new TreeSet<String>(keySorter);
477         ids.addAll(_classpaths.keySet());
478         return ids;
479     }
480 
481     public List<String> getXmlConfigs()
482     {
483         return _xml;
484     }
485 
486     private boolean isAvailable(List<String> options, String classname)
487     {
488         // Try default/parent class loader first.
489         try
490         {
491             Class.forName(classname);
492             return true;
493         }
494         catch (NoClassDefFoundError e)
495         {
496             debug(e);
497         }
498         catch (ClassNotFoundException e)
499         {
500             debug("ClassNotFoundException (parent class loader): " + classname);
501         }
502 
503         // Try option classloaders instead
504         ClassLoader loader;
505         Classpath classpath;
506         for (String optionId : options)
507         {
508             classpath = _classpaths.get(optionId);
509             if (classpath == null)
510             {
511                 // skip, no classpath
512                 continue;
513             }
514 
515             loader = classpath.getClassLoader();
516 
517             try
518             {
519                 loader.loadClass(classname);
520                 return true;
521             }
522             catch (NoClassDefFoundError e)
523             {
524                 debug(e);
525             }
526             catch (ClassNotFoundException e)
527             {
528                 debug("ClassNotFoundException (section class loader: " + optionId + "): " + classname);
529             }
530         }
531         return false;
532     }
533 
534     /**
535      * Parse the configuration
536      * 
537      * @param buf
538      * @throws IOException
539      */
540     public void parse(CharSequence buf) throws IOException
541     {
542         parse(new StringReader(buf.toString()));
543     }
544 
545     /**
546      * Parse the configuration
547      * 
548      * @param stream the stream to read from
549      * @throws IOException
550      */
551     public void parse(InputStream stream) throws IOException
552     {
553         InputStreamReader reader = null;
554         try
555         {
556             reader = new InputStreamReader(stream);
557             parse(reader);
558         }
559         finally
560         {
561             close(reader);
562         }
563     }
564 
565     /**
566      */
567     public void parse(Reader reader) throws IOException
568     {
569         BufferedReader buf = null;
570 
571         try
572         {
573             buf = new BufferedReader(reader);
574 
575             List<String> options = new ArrayList<String>();
576             options.add(DEFAULT_SECTION);
577             _classpaths.put(DEFAULT_SECTION,new Classpath());
578             Version java_version = new Version(System.getProperty("java.version"));
579             Version ver = new Version();
580 
581             String line = null;
582             while ((line = buf.readLine()) != null)
583             {
584                 String trim = line.trim();
585                 if (trim.length() == 0) // empty line
586                     continue;
587 
588                 if (trim.startsWith("#")) // comment
589                     continue;
590 
591                 // handle options
592                 if (trim.startsWith("[") && trim.endsWith("]"))
593                 {
594                     String identifier = trim.substring(1,trim.length() - 1);
595 
596                     // Normal case: section identifier (possibly separated by commas)
597                     options = Arrays.asList(identifier.split(","));
598                     List<String> option_ids=new ArrayList<String>();
599                     
600                     // Ensure section classpaths exist
601                     for (String optionId : options)
602                     {
603                         if (optionId.charAt(0) == '=')
604                             continue;
605 
606                         if (!_classpaths.containsKey(optionId))
607                             _classpaths.put(optionId,new Classpath());
608                         
609                         if (!option_ids.contains(optionId))
610                             option_ids.add(optionId);
611                     }
612                     
613 
614                     // Process Dynamic
615                     for (String optionId : options)
616                     {
617                         if (optionId.charAt(0) != '=')
618                             continue;
619                         
620                         option_ids = processDynamicSectionIdentifier(optionId.substring(1),option_ids);
621                     }
622                     
623                     options = option_ids;
624                     
625                     continue;
626                 }
627 
628                 try
629                 {
630                     StringTokenizer st = new StringTokenizer(line);
631                     String subject = st.nextToken();
632                     boolean expression = true;
633                     boolean not = false;
634                     String condition = null;
635                     // Evaluate all conditions
636                     while (st.hasMoreTokens())
637                     {
638                         condition = st.nextToken();
639                         if (condition.equalsIgnoreCase("!"))
640                         {
641                             not = true;
642                             continue;
643                         }
644                         if (condition.equalsIgnoreCase("OR"))
645                         {
646                             if (expression)
647                                 break;
648                             expression = true;
649                             continue;
650                         }
651                         if (condition.equalsIgnoreCase("AND"))
652                         {
653                             if (!expression)
654                                 break;
655                             continue;
656                         }
657                         boolean eval = true;
658                         if (condition.equals("true") || condition.equals("always"))
659                         {
660                             eval = true;
661                         }
662                         else if (condition.equals("false") || condition.equals("never"))
663                         {
664                             eval = false;
665                         }
666                         else if (condition.equals("available"))
667                         {
668                             String class_to_check = st.nextToken();
669                             eval = isAvailable(options,class_to_check);
670                         }
671                         else if (condition.equals("exists"))
672                         {
673                             try
674                             {
675                                 eval = false;
676                                 File file = new File(expand(st.nextToken()));
677                                 eval = file.exists();
678                             }
679                             catch (Exception e)
680                             {
681                                 debug(e);
682                             }
683                         }
684                         else if (condition.equals("property"))
685                         {
686                             String property = getProperty(st.nextToken());
687                             eval = property != null && property.length() > 0;
688                         }
689                         else if (condition.equals("system"))
690                         {
691                             String property = System.getProperty(st.nextToken());
692                             eval = property != null && property.length() > 0;
693                         }
694                         else if (condition.equals("java"))
695                         {
696                             String operator = st.nextToken();
697                             String version = st.nextToken();
698                             ver.parse(version);
699                             eval = (operator.equals("<") && java_version.compare(ver) < 0) || (operator.equals(">") && java_version.compare(ver) > 0)
700                             || (operator.equals("<=") && java_version.compare(ver) <= 0) || (operator.equals("=<") && java_version.compare(ver) <= 0)
701                             || (operator.equals("=>") && java_version.compare(ver) >= 0) || (operator.equals(">=") && java_version.compare(ver) >= 0)
702                             || (operator.equals("==") && java_version.compare(ver) == 0) || (operator.equals("!=") && java_version.compare(ver) != 0);
703                         }
704                         else if (condition.equals("nargs"))
705                         {
706                             String operator = st.nextToken();
707                             int number = Integer.parseInt(st.nextToken());
708                             eval = (operator.equals("<") && argCount < number) || (operator.equals(">") && argCount > number)
709                             || (operator.equals("<=") && argCount <= number) || (operator.equals("=<") && argCount <= number)
710                             || (operator.equals("=>") && argCount >= number) || (operator.equals(">=") && argCount >= number)
711                             || (operator.equals("==") && argCount == number) || (operator.equals("!=") && argCount != number);
712                         }
713                         else
714                         {
715                             System.err.println("ERROR: Unknown condition: " + condition);
716                             eval = false;
717                         }
718                         expression &= not?!eval:eval;
719                         not = false;
720                     }
721 
722                     String file = expand(subject);
723                     debug((expression?"T ":"F ") + line);
724                     if (!expression)
725                         continue;
726 
727                     // Setting of a start property
728                     if (subject.indexOf("~=") > 0)
729                     {
730                         int i = file.indexOf("~=");
731                         String property = file.substring(0,i);
732                         String value = fixPath(file.substring(i + 2));
733                         debug("  " + property + "~=" + value);
734                         setProperty(property,value);
735                         continue;
736                     }
737 
738                     // Setting of start property with canonical path
739                     if (subject.indexOf("/=") > 0)
740                     {
741                         int i = file.indexOf("/=");
742                         String property = file.substring(0,i);
743                         String value = fixPath(file.substring(i + 2));
744                         String canonical = new File(value).getCanonicalPath();
745                         debug("  " + property + "/=" + value + "==" + canonical);
746                         setProperty(property,canonical);
747                         continue;
748                     }
749 
750                     // Setting of system property
751                     if (subject.indexOf("=") > 0)
752                     {
753                         int i = file.indexOf("=");
754                         String property = file.substring(0,i);
755                         String value = fixPath(file.substring(i + 1));
756                         debug("  " + property + "=" + value);
757                         System.setProperty(property,value);
758                         continue;
759                     }
760 
761                     // Add all unconsidered JAR and ZIP files to classpath
762                     if (subject.endsWith("/*"))
763                     {
764                         // directory of JAR files - only add jars and zips within the directory
765                         File dir = new File(fixPath(file.substring(0,file.length() - 1)));
766                         addJars(options,dir,false);
767                         continue;
768                     }
769 
770                     // Recursively add all unconsidered JAR and ZIP files to classpath
771                     if (subject.endsWith("/**"))
772                     {
773                         //directory hierarchy of jar files - recursively add all jars and zips in the hierarchy
774                         File dir = new File(fixPath(file.substring(0,file.length() - 2)));
775                         addJars(options,dir,true);
776                         continue;
777                     }
778 
779                     // Add raw classpath directory to classpath
780                     if (subject.endsWith("/"))
781                     {
782                         // class directory
783                         File cd = new File(fixPath(file));
784                         String d = cd.getCanonicalPath();
785                         boolean added = addClasspathComponent(options,d);
786                         debug((added?"  CLASSPATH+=":"  !") + d);
787                         continue;
788                     }
789 
790                     // Add XML configuration
791                     if (subject.toLowerCase().endsWith(".xml"))
792                     {
793                         // Config file
794                         File f = new File(fixPath(file));
795                         if (f.exists())
796                             _xml.add(f.getCanonicalPath());
797                         debug("  ARGS+=" + f);
798                         continue;
799                     }
800 
801                     // Set the main class to execute (overrides any previously set)
802                     if (subject.toLowerCase().endsWith(".class"))
803                     {
804                         // Class
805                         String cn = expand(subject.substring(0,subject.length() - 6));
806                         if (cn != null && cn.length() > 0)
807                         {
808                             debug("  CLASS=" + cn);
809                             _classname = cn;
810                         }
811                         continue;
812                     }
813 
814                     // Add raw classpath entry
815                     if (subject.toLowerCase().endsWith(".path"))
816                     {
817                         // classpath (jetty.class.path?) to add to runtime classpath
818                         String cn = expand(subject.substring(0,subject.length() - 5));
819                         if (cn != null && cn.length() > 0)
820                         {
821                             debug("  PATH=" + cn);
822                             addClasspathPath(options,cn);
823                         }
824                         continue;
825                     }
826 
827                     // single JAR file
828                     File f = new File(fixPath(file));
829                     if (f.exists())
830                     {
831                         String d = f.getCanonicalPath();
832                         boolean added = addClasspathComponent(options,d);
833                         if (!added)
834                         {
835                             added = addClasspathPath(options,expand(subject));
836                         }
837                         debug((added?"  CLASSPATH+=":"  !") + d);
838                     }
839                 }
840                 catch (Exception e)
841                 {
842                     System.err.println("on line: '" + line + "'");
843                     e.printStackTrace();
844                 }
845             }
846         }
847         finally
848         {
849             close(buf);
850         }
851     }
852 
853     private List<String> processDynamicSectionIdentifier(String dynamicPathId,List<String> sections) throws IOException
854     {
855         String rawPath;
856         boolean deep;
857         
858         if (dynamicPathId.endsWith("/*"))
859         {
860             deep=false;
861             rawPath = fixPath(dynamicPathId.substring(0,dynamicPathId.length() - 1));
862         }
863         else if (dynamicPathId.endsWith("/**"))
864         {
865             deep=true;
866             rawPath = fixPath(dynamicPathId.substring(0,dynamicPathId.length() - 2));
867         }
868         else 
869         {
870             String msg = "Illegal dynamic path [" + dynamicPathId + "]";
871             throw new IOException(msg);
872         }
873         
874         File parentDir = new File(expand(rawPath));
875         if (!parentDir.exists())
876             return sections;
877         debug("dynamic: " + parentDir);
878 
879         File dirs[] = parentDir.listFiles(new FileFilter()
880         {
881             public boolean accept(File path)
882             {
883                 return path.isDirectory();
884             }
885         });
886 
887         List<String> dyn_sections = new ArrayList<String>();
888         List<String> super_sections = new ArrayList<String>();
889         if (sections!=null)
890             super_sections.addAll(sections);
891         
892         for (File dir : dirs)
893         {
894             String id = dir.getName();
895             if (!_classpaths.keySet().contains(id))
896                 _classpaths.put(id, new Classpath());
897             
898             dyn_sections.clear();
899             if (sections!=null)
900                 dyn_sections.addAll(sections);
901             dyn_sections.add(id);
902             super_sections.add(id);
903             debug("dynamic: " + dyn_sections);
904             addJars(dyn_sections,dir,deep);
905         }
906         
907         return super_sections;
908     }
909 
910     private String fixPath(String path)
911     {
912         return path.replace('/',File.separatorChar);
913     }
914 
915     public void parse(URL url) throws IOException
916     {
917         InputStream stream = null;
918         InputStreamReader reader = null;
919         try
920         {
921             stream = url.openStream();
922             reader = new InputStreamReader(stream);
923             parse(reader);
924         }
925         finally
926         {
927             close(reader);
928             close(stream);
929         }
930     }
931 
932     public void setArgCount(int argCount)
933     {
934         this.argCount = argCount;
935     }
936 
937     public void setProperty(String name, String value)
938     {
939         if (name.equals("DEBUG"))
940         {
941             DEBUG = Boolean.parseBoolean(value);
942             if (DEBUG)
943             {
944                 System.setProperty("org.eclipse.jetty.util.log.stderr.DEBUG","true");
945                 System.setProperty("org.eclipse.jetty.start.DEBUG","true");
946             }
947         }
948         if (name.equals("OPTIONS"))
949         {
950             _activeOptions.clear();
951             String ids[] = value.split(",");
952             for (String id : ids)
953             {
954                 addActiveOption(id);
955             }
956         }
957         __properties.put(name,value);
958     }
959 
960     public void addActiveOption(String option)
961     {
962         _activeOptions.add(option); 
963         __properties.put("OPTIONS",join(_activeOptions,","));
964     }
965 
966     public Set<String> getActiveOptions()
967     {
968         return _activeOptions;
969     }
970 
971     public void removeActiveOption(String option)
972     {
973         _activeOptions.remove(option);
974         __properties.put("OPTIONS",join(_activeOptions,","));
975     }
976     
977     private String join(Collection<?> coll, String delim)
978     {
979         StringBuffer buf = new StringBuffer();
980         Iterator<?> i = coll.iterator();
981         boolean hasNext = i.hasNext();
982         while (hasNext)
983         {
984             buf.append(String.valueOf(i.next()));
985             hasNext = i.hasNext();
986             if (hasNext)
987                 buf.append(delim);
988         }
989 
990         return buf.toString();
991     }
992 
993 }