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 }