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