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