View Javadoc

1   package org.codehaus.plexus.classworlds.launcher;
2   
3   /*
4    * Copyright 2001-2006 Codehaus Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  import java.io.FileInputStream;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.lang.reflect.InvocationTargetException;
23  import java.lang.reflect.Method;
24  import java.lang.reflect.Modifier;
25  import java.net.MalformedURLException;
26  import java.net.URL;
27  
28  import org.codehaus.plexus.classworlds.ClassWorld;
29  import org.codehaus.plexus.classworlds.realm.ClassRealm;
30  import org.codehaus.plexus.classworlds.realm.DuplicateRealmException;
31  import org.codehaus.plexus.classworlds.realm.NoSuchRealmException;
32  
33  /**
34   * Command-line invokable application launcher.
35   * <p/>
36   * <p/>
37   * This launcher class assists in the creation of classloaders and <code>ClassRealm</code>s
38   * from a configuration file and the launching of the application's <code>main</code>
39   * method from the correct class loaded through the correct classloader.
40   * </p>
41   * <p/>
42   * <p/>
43   * The path to the configuration file is specified using the <code>classworlds.conf</code>
44   * system property, typically specified using the <code>-D</code> switch to
45   * <code>java</code>.
46   * </p>
47   *
48   * @author <a href="mailto:bob@eng.werken.com">bob mcwhirter</a>
49   */
50  public class Launcher
51  {
52      protected static final String CLASSWORLDS_CONF = "classworlds.conf";
53  
54      protected static final String UBERJAR_CONF_DIR = "WORLDS-INF/conf/";
55  
56      protected ClassLoader systemClassLoader;
57  
58      protected String mainClassName;
59  
60      protected String mainRealmName;
61  
62      protected ClassWorld world;
63  
64      private int exitCode = 0;
65  
66      public Launcher()
67      {
68          this.systemClassLoader = Thread.currentThread().getContextClassLoader();
69      }
70  
71      public void setSystemClassLoader( ClassLoader loader )
72      {
73          this.systemClassLoader = loader;
74      }
75  
76      public ClassLoader getSystemClassLoader()
77      {
78          return this.systemClassLoader;
79      }
80  
81      public int getExitCode()
82      {
83          return exitCode;
84      }
85  
86      public void setAppMain( String mainClassName,
87                              String mainRealmName )
88      {
89          this.mainClassName = mainClassName;
90  
91          this.mainRealmName = mainRealmName;
92      }
93  
94      public String getMainRealmName()
95      {
96          return this.mainRealmName;
97      }
98  
99      public String getMainClassName()
100     {
101         return this.mainClassName;
102     }
103 
104     public void setWorld( ClassWorld world )
105     {
106         this.world = world;
107     }
108 
109     public ClassWorld getWorld()
110     {
111         return this.world;
112     }
113 
114     /**
115      * Configure from a file.
116      *
117      * @param is The config input stream.
118      * @throws IOException             If an error occurs reading the config file.
119      * @throws MalformedURLException   If the config file contains invalid URLs.
120      * @throws ConfigurationException  If the config file is corrupt.
121      * @throws org.codehaus.plexus.classworlds.realm.DuplicateRealmException If the config file defines two realms
122      *                                 with the same id.
123      * @throws org.codehaus.plexus.classworlds.realm.NoSuchRealmException    If the config file defines a main entry
124      *                                 point in a non-existent realm.
125      */
126     public void configure( InputStream is )
127         throws IOException, ConfigurationException, DuplicateRealmException, NoSuchRealmException
128     {
129         Configurator configurator = new Configurator( this );
130 
131         configurator.configure( is );
132     }
133 
134     /**
135      * Retrieve the main entry class.
136      *
137      * @return The main entry class.
138      * @throws ClassNotFoundException If the class cannot be found.
139      * @throws NoSuchRealmException   If the specified main entry realm does not exist.
140      */
141     public Class<?> getMainClass()
142         throws ClassNotFoundException, NoSuchRealmException
143     {
144         return getMainRealm().loadClass( getMainClassName() );
145     }
146 
147     /**
148      * Retrieve the main entry realm.
149      *
150      * @return The main entry realm.
151      * @throws NoSuchRealmException If the specified main entry realm does not exist.
152      */
153     public ClassRealm getMainRealm()
154         throws NoSuchRealmException
155     {
156         return getWorld().getRealm( getMainRealmName() );
157     }
158 
159     /**
160      * Retrieve the enhanced main entry method.
161      *
162      * @return The enhanced main entry method.
163      * @throws ClassNotFoundException If the main entry class cannot be found.
164      * @throws NoSuchMethodException  If the main entry method cannot be found.
165      * @throws NoSuchRealmException   If the main entry realm cannot be found.
166      */
167     protected Method getEnhancedMainMethod()
168         throws ClassNotFoundException, NoSuchMethodException, NoSuchRealmException
169     {
170         Class<?> cwClass = getMainRealm().loadClass( ClassWorld.class.getName() );
171 
172         Method m = getMainClass().getMethod( "main", new Class[]{String[].class, cwClass} );
173 
174         int modifiers = m.getModifiers();
175 
176         if ( Modifier.isStatic( modifiers ) && Modifier.isPublic( modifiers ) )
177         {
178             if ( m.getReturnType() == Integer.TYPE || m.getReturnType() == Void.TYPE )
179             {
180                 return m;
181             }
182         }
183 
184         throw new NoSuchMethodException( "public static void main(String[] args, ClassWorld world)" );
185     }
186 
187     /**
188      * Retrieve the main entry method.
189      *
190      * @return The main entry method.
191      * @throws ClassNotFoundException If the main entry class cannot be found.
192      * @throws NoSuchMethodException  If the main entry method cannot be found.
193      * @throws NoSuchRealmException   If the main entry realm cannot be found.
194      */
195     protected Method getMainMethod()
196         throws ClassNotFoundException, NoSuchMethodException, NoSuchRealmException
197     {
198         Method m = getMainClass().getMethod( "main", new Class[]{String[].class} );
199 
200         int modifiers = m.getModifiers();
201 
202         if ( Modifier.isStatic( modifiers ) && Modifier.isPublic( modifiers ) )
203         {
204             if ( m.getReturnType() == Integer.TYPE || m.getReturnType() == Void.TYPE )
205             {
206                 return m;
207             }
208         }
209 
210         throw new NoSuchMethodException( "public static void main(String[] args) in " + getMainClass() );
211     }
212 
213     /**
214      * Launch the application.
215      *
216      * @param args The application args.
217      * @throws ClassNotFoundException    If the main entry class cannot be found.
218      * @throws IllegalAccessException    If the method cannot be accessed.
219      * @throws InvocationTargetException If the target of the invokation is invalid.
220      * @throws NoSuchMethodException     If the main entry method cannot be found.
221      * @throws NoSuchRealmException      If the main entry realm cannot be found.
222      */
223     public void launch( String[] args )
224         throws ClassNotFoundException, IllegalAccessException, InvocationTargetException, NoSuchMethodException,
225         NoSuchRealmException
226     {
227         try
228         {
229             launchEnhanced( args );
230 
231             return;
232         }
233         catch ( NoSuchMethodException e )
234         {
235             // ignore
236         }
237 
238         launchStandard( args );
239     }
240 
241     /**
242      * Attempt to launch the application through the enhanced main method.
243      * <p/>
244      * <p/>
245      * This will seek a method with the exact signature of:
246      * </p>
247      * <p/>
248      * <pre>
249      *  public static void main(String[] args, ClassWorld world)
250      *  </pre>
251      *
252      * @param args The application args.
253      * @throws ClassNotFoundException    If the main entry class cannot be found.
254      * @throws IllegalAccessException    If the method cannot be accessed.
255      * @throws InvocationTargetException If the target of the invokation is
256      *                                   invalid.
257      * @throws NoSuchMethodException     If the main entry method cannot be found.
258      * @throws NoSuchRealmException      If the main entry realm cannot be found.
259      */
260     protected void launchEnhanced( String[] args )
261         throws ClassNotFoundException, IllegalAccessException, InvocationTargetException, NoSuchMethodException,
262         NoSuchRealmException
263     {
264         ClassRealm mainRealm = getMainRealm();
265 
266         Class<?> mainClass = getMainClass();
267 
268         Method mainMethod = getEnhancedMainMethod();
269 
270         ClassLoader cl = mainRealm;
271 
272         // ----------------------------------------------------------------------
273         // This is what the classloader for the main realm looks like when we
274         // boot from the command line:
275         // ----------------------------------------------------------------------
276         // [ AppLauncher$AppClassLoader ] : $CLASSPATH envar
277         //           ^
278         //           |
279         //           |
280         // [ AppLauncher$ExtClassLoader ] : ${java.home}/jre/lib/ext/*.jar
281         //           ^
282         //           |
283         //           |
284         // [ Strategy ]
285         // ----------------------------------------------------------------------
286 
287         Thread.currentThread().setContextClassLoader( cl );
288 
289         Object ret = mainMethod.invoke( mainClass, new Object[]{args, getWorld()} );
290 
291         if ( ret instanceof Integer )
292         {
293             exitCode = ( (Integer) ret ).intValue();
294         }
295 
296         Thread.currentThread().setContextClassLoader( systemClassLoader );
297     }
298 
299     /**
300      * Attempt to launch the application through the standard main method.
301      * <p/>
302      * <p/>
303      * This will seek a method with the exact signature of:
304      * </p>
305      * <p/>
306      * <pre>
307      *  public static void main(String[] args)
308      *  </pre>
309      *
310      * @param args The application args.
311      * @throws ClassNotFoundException    If the main entry class cannot be found.
312      * @throws IllegalAccessException    If the method cannot be accessed.
313      * @throws InvocationTargetException If the target of the invokation is
314      *                                   invalid.
315      * @throws NoSuchMethodException     If the main entry method cannot be found.
316      * @throws NoSuchRealmException      If the main entry realm cannot be found.
317      */
318     protected void launchStandard( String[] args )
319         throws ClassNotFoundException, IllegalAccessException, InvocationTargetException, NoSuchMethodException,
320         NoSuchRealmException
321     {
322         ClassRealm mainRealm = getMainRealm();
323 
324         Class<?> mainClass = getMainClass();
325 
326         Method mainMethod = getMainMethod();
327 
328         Thread.currentThread().setContextClassLoader( mainRealm );
329 
330         Object ret = mainMethod.invoke( mainClass, new Object[]{args} );
331 
332         if ( ret instanceof Integer )
333         {
334             exitCode = ( (Integer) ret ).intValue();
335         }
336 
337         Thread.currentThread().setContextClassLoader( systemClassLoader );
338 
339     }
340 
341     // ------------------------------------------------------------
342     //     Class methods
343     // ------------------------------------------------------------
344 
345     /**
346      * Launch the launcher from the command line.
347      * Will exit using System.exit with an exit code of 0 for success, 100 if there was an unknown exception,
348      * or some other code for an application error.
349      *
350      * @param args The application command-line arguments.
351      */
352     public static void main( String[] args )
353     {
354         try
355         {
356             int exitCode = mainWithExitCode( args );
357 
358             System.exit( exitCode );
359         }
360         catch ( Exception e )
361         {
362             e.printStackTrace();
363 
364             System.exit( 100 );
365         }
366     }
367 
368     /**
369      * Launch the launcher.
370      *
371      * @param args The application command-line arguments.
372      * @return an integer exit code
373      * @throws Exception If an error occurs.
374      */
375     public static int mainWithExitCode( String[] args )
376         throws Exception
377     {
378         String classworldsConf = System.getProperty( CLASSWORLDS_CONF );
379 
380         InputStream is;
381 
382         Launcher launcher = new Launcher();
383 
384         ClassLoader cl = Thread.currentThread().getContextClassLoader();
385 
386         launcher.setSystemClassLoader( cl );
387 
388         if ( classworldsConf != null )
389         {
390             is = new FileInputStream( classworldsConf );
391         }
392         else
393         {
394             if ( "true".equals( System.getProperty( "classworlds.bootstrapped" ) ) )
395             {
396                 is = cl.getResourceAsStream( UBERJAR_CONF_DIR + CLASSWORLDS_CONF );
397             }
398             else
399             {
400                 is = cl.getResourceAsStream( CLASSWORLDS_CONF );
401             }
402         }
403 
404         if ( is == null )
405         {
406             throw new Exception( "classworlds configuration not specified nor found in the classpath" );
407         }
408 
409         launcher.configure( is );
410 
411         is.close();
412 
413         try
414         {
415             launcher.launch( args );
416         }
417         catch ( InvocationTargetException e )
418         {
419             ClassRealm realm = launcher.getWorld().getRealm( launcher.getMainRealmName() );
420 
421             URL[] constituents = realm.getURLs();
422 
423             System.out.println( "---------------------------------------------------" );
424 
425             for ( int i = 0; i < constituents.length; i++ )
426             {
427                 System.out.println( "constituent[" + i + "]: " + constituents[i] );
428             }
429 
430             System.out.println( "---------------------------------------------------" );
431 
432             // Decode ITE (if we can)
433             Throwable t = e.getTargetException();
434 
435             if ( t instanceof Exception )
436             {
437                 throw (Exception) t;
438             }
439             if ( t instanceof Error )
440             {
441                 throw (Error) t;
442             }
443 
444             // Else just toss the ITE
445             throw e;
446         }
447 
448         return launcher.getExitCode();
449     }
450 }