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