View Javadoc

1   package org.codehaus.classworlds;
2   
3   /*
4    $Id: Configurator.java,v 1.3 2004/09/07 13:17:42 brett Exp $
5   
6    Copyright 2002 (C) The Werken Company. All Rights Reserved.
7   
8    Redistribution and use of this software and associated documentation
9    ("Software"), with or without modification, are permitted provided
10   that the following conditions are met:
11  
12   1. Redistributions of source code must retain copyright
13      statements and notices.  Redistributions must also contain a
14      copy of this document.
15  
16   2. Redistributions in binary form must reproduce the
17      above copyright notice, this list of conditions and the
18      following disclaimer in the documentation and/or other
19      materials provided with the distribution.
20  
21   3. The name "classworlds" must not be used to endorse or promote
22      products derived from this Software without prior written
23      permission of The Werken Company.  For written permission,
24      please contact bob@werken.com.
25  
26   4. Products derived from this Software may not be called "classworlds"
27      nor may "classworlds" appear in their names without prior written
28      permission of The Werken Company. "classworlds" is a registered
29      trademark of The Werken Company.
30  
31   5. Due credit should be given to The Werken Company.
32      (http://classworlds.werken.com/).
33  
34   THIS SOFTWARE IS PROVIDED BY THE WERKEN COMPANY AND CONTRIBUTORS
35   ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
36   NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
37   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
38   THE WERKEN COMPANY OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
39   INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
40   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
41   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
42   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
43   STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
44   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
45   OF THE POSSIBILITY OF SUCH DAMAGE.
46  
47   */
48  
49  import java.io.BufferedReader;
50  import java.io.File;
51  import java.io.FileNotFoundException;
52  import java.io.FilenameFilter;
53  import java.io.IOException;
54  import java.io.InputStream;
55  import java.io.InputStreamReader;
56  import java.io.FileInputStream;
57  import java.net.MalformedURLException;
58  import java.net.URL;
59  import java.util.ArrayList;
60  import java.util.Collections;
61  import java.util.Comparator;
62  import java.util.HashMap;
63  import java.util.Iterator;
64  import java.util.List;
65  import java.util.Map;
66  import java.util.Properties;
67  
68  /***
69   * <code>Launcher</code> configurator.
70   *
71   * @author <a href="mailto:bob@eng.werken.com">bob mcwhirter</a>
72   * @author <a href="mailto:jason@zenplex.com">Jason van Zyl</a>
73   * @version $Id: Configurator.java,v 1.3 2004/09/07 13:17:42 brett Exp $
74   */
75  public class Configurator
76  {
77      public static final String MAIN_PREFIX = "main is";
78  
79      public static final String SET_PREFIX = "set";
80  
81      public static final String IMPORT_PREFIX = "import";
82  
83      public static final String LOAD_PREFIX = "load";
84  
85      /*** Optionally spec prefix. */
86      public static final String OPTIONALLY_PREFIX = "optionally";
87  
88      /*** The launcher to configure. */
89      private Launcher launcher;
90  
91      private ClassWorld world;
92  
93      /*** Processed Realms. */
94      private Map configuredRealms;
95  
96      /*** Construct.
97       *
98       *  @param launcher The launcher to configure.
99       */
100     public Configurator( Launcher launcher )
101     {
102         this.launcher = launcher;
103 
104         configuredRealms = new HashMap();
105     }
106 
107     /*** Construct.
108      *
109      *  @param classWorld The classWorld to configure.
110      */
111     public Configurator( ClassWorld world )
112     {
113         setClassWorld( world );
114     }
115 
116     /*** set world.
117      *  this setter is provided so you can use the same configurator to configure several "worlds"
118      *
119      *  @param classWorld The classWorld to configure.
120      */
121     public void setClassWorld( ClassWorld world )
122     {
123         this.world = world;
124 
125         configuredRealms = new HashMap();
126     }
127 
128     /***
129      * Configure from a file.
130      *
131      * @param is The config input stream
132      * @throws IOException             If an error occurs reading the config file.
133      * @throws MalformedURLException   If the config file contains invalid URLs.
134      * @throws ConfigurationException  If the config file is corrupt.
135      * @throws DuplicateRealmException If the config file defines two realms with the same id.
136      * @throws NoSuchRealmException    If the config file defines a main entry point in
137      *                                 a non-existent realm.
138      */
139     public void configure( InputStream is )
140         throws IOException, MalformedURLException, ConfigurationException, DuplicateRealmException, NoSuchRealmException
141     {
142         BufferedReader reader = new BufferedReader( new InputStreamReader( is ) );
143 
144         if ( world == null )
145         {
146             world = new ClassWorld();
147         }
148 
149         ClassRealm curRealm = null;
150 
151         String line = null;
152 
153         int lineNo = 0;
154 
155         boolean mainSet = false;
156 
157         while ( true )
158         {
159             line = reader.readLine();
160 
161             if ( line == null )
162             {
163                 break;
164             }
165 
166             ++lineNo;
167             line = line.trim();
168 
169             if ( canIgnore( line ) )
170             {
171                 continue;
172             }
173 
174             if ( line.startsWith( MAIN_PREFIX ) )
175             {
176                 if ( mainSet )
177                 {
178                     throw new ConfigurationException( "Duplicate main configuration", lineNo, line );
179                 }
180 
181                 String conf = line.substring( MAIN_PREFIX.length() ).trim();
182 
183                 int fromLoc = conf.indexOf( "from" );
184 
185                 if ( fromLoc < 0 )
186                 {
187                     throw new ConfigurationException( "Missing from clause", lineNo, line );
188                 }
189 
190                 String mainClassName = conf.substring( 0, fromLoc ).trim();
191 
192                 String mainRealmName = conf.substring( fromLoc + 4 ).trim();
193 
194                 if ( this.launcher != null )
195                 {
196                     this.launcher.setAppMain( mainClassName, mainRealmName );
197                 }
198 
199                 mainSet = true;
200             }
201             else if ( line.startsWith( SET_PREFIX ) )
202             {
203                 String conf = line.substring( SET_PREFIX.length() ).trim();
204 
205                 int usingLoc = conf.indexOf( " using" ) + 1;
206 
207                 String property = null;
208                 String propertiesFileName = null;
209                 if ( usingLoc > 0 )
210                 {
211                     property = conf.substring( 0, usingLoc ).trim();
212 
213                     propertiesFileName = filter( conf.substring( usingLoc + 5 ).trim() );
214 
215                     conf = propertiesFileName;
216                 }
217 
218                 String defaultValue = null;
219 
220                 int defaultLoc = conf.indexOf( " default" ) + 1;
221 
222                 if ( defaultLoc > 0 )
223                 {
224                     defaultValue = conf.substring( defaultLoc + 7 ).trim();
225 
226                     if ( property == null )
227                     {
228                         property = conf.substring( 0, defaultLoc ).trim();
229                     }
230                     else
231                     {
232                         propertiesFileName = conf.substring( 0, defaultLoc ).trim();
233                     }
234                 }
235 
236                 String value = System.getProperty( property );
237                 
238                 if ( value != null )
239                 {
240                     continue;
241                 }
242 
243                 if ( propertiesFileName != null )
244                 {
245                     File propertiesFile = new File( propertiesFileName );
246 
247                     if ( propertiesFile.exists() )
248                     {
249                         Properties properties = new Properties();
250 
251                         try
252                         {
253                             properties.load( new FileInputStream( propertiesFileName ) );
254     
255                             value = properties.getProperty( property );
256                         }
257                         catch ( Exception e )
258                         {
259                             // do nothing
260                         }
261                     }
262                 }
263 
264                 if ( value == null && defaultValue != null )
265                 {
266                     value = defaultValue;
267                 }
268 
269                 if ( value != null )
270                 {
271                     value = filter( value );
272                     System.setProperty( property, value );
273                 }
274             }
275             else if ( line.startsWith( "[" ) )
276             {
277                 int rbrack = line.indexOf( "]" );
278 
279                 if ( rbrack < 0 )
280                 {
281                     throw new ConfigurationException( "Invalid realm specifier", lineNo, line );
282                 }
283 
284                 String realmName = line.substring( 1, rbrack );
285 
286                 curRealm = world.newRealm( realmName );
287 
288                 // Stash the configured realm for subsequent association processing.
289                 configuredRealms.put( realmName, curRealm );
290             }
291             else if ( line.startsWith( IMPORT_PREFIX ) )
292             {
293                 if ( curRealm == null )
294                 {
295                     throw new ConfigurationException( "Unhandled import", lineNo, line );
296                 }
297 
298                 String conf = line.substring( IMPORT_PREFIX.length() ).trim();
299 
300                 int fromLoc = conf.indexOf( "from" );
301 
302                 if ( fromLoc < 0 )
303                 {
304                     throw new ConfigurationException( "Missing from clause", lineNo, line );
305                 }
306 
307                 String importSpec = conf.substring( 0, fromLoc ).trim();
308 
309                 String relamName = conf.substring( fromLoc + 4 ).trim();
310 
311                 curRealm.importFrom( relamName, importSpec );
312 
313             }
314             else if ( line.startsWith( LOAD_PREFIX ) )
315             {
316                 String constituent = line.substring( LOAD_PREFIX.length() ).trim();
317 
318                 constituent = filter( constituent );
319 
320                 if ( constituent.indexOf( "*" ) >= 0 )
321                 {
322                     loadGlob( constituent, curRealm );
323                 }
324                 else
325                 {
326                     File file = new File( constituent );
327 
328                     if ( file.exists() )
329                     {
330                         curRealm.addConstituent( file.toURL() );
331                     }
332                     else
333                     {
334                         try
335                         {
336                             curRealm.addConstituent( new URL( constituent ) );
337                         }
338                         catch ( MalformedURLException e )
339                         {
340                             throw new FileNotFoundException( constituent );
341                         }
342                     }
343                 }
344             }
345             else if ( line.startsWith( OPTIONALLY_PREFIX ) )
346             {
347                 String constituent = line.substring( OPTIONALLY_PREFIX.length() ).trim();
348 
349                 constituent = filter( constituent );
350 
351                 if ( constituent.indexOf( "*" ) >= 0 )
352                 {
353                     loadGlob( constituent, curRealm, true );
354                 }
355                 else
356                 {
357                     File file = new File( constituent );
358 
359                     if ( file.exists() )
360                     {
361                         curRealm.addConstituent( file.toURL() );
362                     }
363                     else
364                     {
365                         try
366                         {
367                             curRealm.addConstituent( new URL( constituent ) );
368                         }
369                         catch (MalformedURLException e)
370                         {
371                             // swallow
372                         }
373                     }
374                 }
375             }
376             else
377             {
378                 throw new ConfigurationException( "Unhandled configuration", lineNo, line );
379             }
380         }
381 
382         // Associate child realms to their parents.
383         associateRealms();
384 
385         if ( this.launcher != null ) this.launcher.setWorld( world );
386 
387         reader.close();
388     }
389 
390     /***
391      * Associate parent realms with their children.
392      */
393     protected void associateRealms()
394     {
395         List sortRealmNames = new ArrayList( configuredRealms.keySet() );
396 
397         // sort by name
398         Comparator comparator = new Comparator()
399         {
400             public int compare( Object o1, Object o2 )
401             {
402                 String g1 = (String) o1;
403                 String g2 = (String) o2;
404                 return g1.compareTo( g2 );
405             }
406         };
407 
408         Collections.sort( sortRealmNames, comparator );
409 
410         // So now we have something like the following for defined
411         // realms:
412         //
413         // root
414         // root.maven
415         // root.maven.plugin
416         //
417         // Now if the name of a realm is a superset of an existing realm
418         // the we want to make child/parent associations.
419 
420         for ( Iterator i = sortRealmNames.iterator(); i.hasNext(); )
421         {
422             String realmName = (String) i.next();
423 
424             int j = realmName.lastIndexOf( '.' );
425 
426             if ( j > 0 )
427             {
428                 String parentRealmName = realmName.substring( 0, j );
429 
430                 ClassRealm parentRealm = (ClassRealm) configuredRealms.get( parentRealmName );
431 
432                 if ( parentRealm != null )
433                 {
434                     ClassRealm realm = (ClassRealm) configuredRealms.get( realmName );
435 
436                     realm.setParent( parentRealm );
437                 }
438             }
439         }
440     }
441 
442     /***
443      * Load a glob into the specified classloader.
444      *
445      * @param line  The path configuration line.
446      * @param realm The realm to populate
447      * @throws MalformedURLException If the line does not represent
448      *                               a valid path element.
449      * @throws FileNotFoundException If the line does not represent
450      *                               a valid path element in the filesystem.
451      */
452     protected void loadGlob( String line, ClassRealm realm )
453         throws MalformedURLException, FileNotFoundException
454     {
455         loadGlob( line, realm, false );
456     }
457 
458     /***
459      * Load a glob into the specified classloader.
460      *
461      * @param line  The path configuration line.
462      * @param realm The realm to populate
463      * @param optionally Whether the path is optional or required
464      * @throws MalformedURLException If the line does not represent
465      *                               a valid path element.
466      * @throws FileNotFoundException If the line does not represent
467      *                               a valid path element in the filesystem.
468      */
469     protected void loadGlob( String line, ClassRealm realm, boolean optionally )
470         throws MalformedURLException, FileNotFoundException
471     {
472         File globFile = new File( line );
473 
474         File dir = globFile.getParentFile();
475         if ( ! dir.exists() )
476         {
477             if ( optionally )
478             {
479                 return;
480             }
481             else
482             {
483                 throw new FileNotFoundException( dir.toString() );
484             }
485         }
486 
487         String localName = globFile.getName();
488 
489         int starLoc = localName.indexOf( "*" );
490 
491         final String prefix = localName.substring( 0, starLoc );
492 
493         final String suffix = localName.substring( starLoc + 1 );
494 
495         File[] matches = dir.listFiles( new FilenameFilter()
496         {
497             public boolean accept( File dir, String name )
498             {
499                 if ( !name.startsWith( prefix ) )
500                 {
501                     return false;
502                 }
503 
504                 if ( !name.endsWith( suffix ) )
505                 {
506                     return false;
507                 }
508 
509                 return true;
510             }
511         } );
512 
513         for ( int i = 0; i < matches.length; ++i )
514         {
515             realm.addConstituent( matches[i].toURL() );
516         }
517     }
518 
519     /***
520      * Filter a string for system properties.
521      *
522      * @param text The text to filter.
523      * @return The filtered text.
524      * @throws ConfigurationException If the property does not
525      *                                exist or if there is a syntax error.
526      */
527     protected String filter( String text )
528         throws ConfigurationException
529     {
530         String result = "";
531 
532         int cur = 0;
533         int textLen = text.length();
534 
535         int propStart = -1;
536         int propStop = -1;
537 
538         String propName = null;
539         String propValue = null;
540 
541         while ( cur < textLen )
542         {
543             propStart = text.indexOf( "${", cur );
544 
545             if ( propStart < 0 )
546             {
547                 break;
548             }
549 
550             result += text.substring( cur, propStart );
551 
552             propStop = text.indexOf( "}", propStart );
553 
554             if ( propStop < 0 )
555             {
556                 throw new ConfigurationException( "Unterminated property: " + text.substring( propStart ) );
557             }
558 
559             propName = text.substring( propStart + 2, propStop );
560 
561             propValue = System.getProperty( propName );
562 
563             if ( propValue == null )
564             {
565                 throw new ConfigurationException( "No such property: " + propName );
566             }
567             result += propValue;
568 
569             cur = propStop + 1;
570         }
571 
572         result += text.substring( cur );
573 
574         return result;
575     }
576 
577     /***
578      * Determine if a line can be ignored because it is
579      * a comment or simply blank.
580      *
581      * @param line The line to test.
582      * @return <code>true</code> if the line is ignorable,
583      *         otherwise <code>false</code>.
584      */
585     private boolean canIgnore( String line )
586     {
587         return ( line.length() == 0
588             ||
589             line.startsWith( "#" ) );
590     }
591 }
592