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