1 /*
2 * JBoss, the OpenSource J2EE webOS
3 *
4 * Distributable under LGPL license.
5 * See terms of license at gnu.org.
6 */
7
8 package org.jboss.tools;
9
10 import java.net;
11 import java.lang.reflect;
12 import java.io;
13 import java.util;
14
15 /**
16 * Starts multiple applications using seperate classloaders.
17 * This allows multiple applications to co-exist even if they typicaly could not due to
18 * class version problems. Each application is started in it's own thread.
19 *
20 * Usage is Boot [-debug] -cp app-classpath app-class-name app-arguments ( , -cp app-classpath app-class-name app-arguments )*
21 *
22 * Where:
23 * app-classpath is a comma seperated URL form classpath to the application classes.
24 * app-class-name is the class that will be started
25 * app-arguments will be the String[] that will be passed to the main method of the application class
26 *
27 * Jboss + Another Application boot example:
28 * Boot -cp file:run.jar org.jboss.Main default , -cp file:./myapp.jar,file:./util.jar test.App2TEST arg1 arg2
29 * Would start the JBoss Server using the default configuration and it would
30 * start the test.App2TEST application.
31 * Important Note: Notice that there are spaces before and after the ","!!!
32 *
33 * You can now boot other applications via ths Boot class from withing one of the applications
34 * that was booted by the Boot.
35 *
36 * Example usage:
37 * <code>
38 * Boot b = Boot.getInstance();
39 * Boot.ApplicationBoot ab = b.createApplicationBoot();
40 * ab.applicationClass = "org.jboss.Main"
41 * ab.classpath.add(new URL("file:run.jar"));
42 * ab.args.add("default");
43 *
44 * // this would start the application in a new thread.
45 * b.startApplication( ab );
46 *
47 * // Would boot the appp in the current thread.
48 * ab.boot();
49 *
50 * </code>
51 *
52 * @author <a href="mailto:cojonudo14@hotmail.com">Hiram Chirino</a>
53 */
54 public class Boot
55 {
56 /**
57 * Indicates whether this instance is running in debug mode.
58 */
59 protected boolean verbose = false;
60
61 /**
62 * For each booted application, we will store a ApplicationBoot object in this linked list.
63 */
64 protected LinkedList applicationBoots = new LinkedList();
65 static Boot instance;
66 ThreadGroup bootThreadGroup;
67
68 /**
69 * If boot is accessed via an API, force the use of the getInstance() method.
70 */
71 protected Boot () {
72 instance = this;
73 bootThreadGroup = Thread.currentThread().getThreadGroup();
74 }
75
76 public static Boot getInstance() {
77 if( instance == null )
78 instance = new Boot();
79 return instance;
80 }
81
82 /**
83 * Represents an application that can be booted.
84 */
85 public class ApplicationBoot implements Runnable
86 {
87 /** LinkedList of URL that will be used to load the application's classes and resources */
88 public LinkedList classpath = new LinkedList();
89 /** The applications class that will be executed. */
90 public String applicationClass;
91 /** The aruments that will appsed to the application. */
92 public LinkedList args = new LinkedList();
93
94 protected URLClassLoader classloader;
95 protected Thread bootThread;
96
97 /**
98 * This is what actually loads the application classes and
99 * invokes the main method. We send any unhandled exceptions to
100 * System.err
101 */
102 public void run()
103 {
104 try
105 {
106 boot();
107 }
108 catch (Throwable e)
109 {
110 System.err.println("Exception durring " + applicationClass + " application run: ");
111 e.printStackTrace(System.err);
112 }
113 }
114
115 /**
116 * This is what actually loads the application classes and
117 * invokes the main method.
118 */
119 public void boot()
120 throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException
121 {
122 verbose("Booting: "+applicationClass);
123 verbose("Classpath: "+classpath);
124 verbose("Arguments: "+args);
125
126 bootThread = Thread.currentThread();
127 URL urls[] = new URL[classpath.size()];
128 urls = (URL[]) classpath.toArray(urls);
129
130 String passThruArgs[] = new String[args.size()];
131 passThruArgs = (String[]) args.toArray(passThruArgs);
132
133 // Save the current loader so we can restore it.
134 ClassLoader oldCL = Thread.currentThread().getContextClassLoader();
135
136 try {
137
138 classloader = new URLClassLoader(urls); // The parent is the system CL
139 Thread.currentThread().setContextClassLoader(classloader);
140
141 Class appClass = classloader.loadClass(applicationClass);
142 Method mainMethod = appClass.getMethod("main", new Class[] { String[].class });
143
144 mainMethod.invoke(null, new Object[] { passThruArgs });
145 }
146 catch (InvocationTargetException e)
147 {
148 if (e.getTargetException() instanceof Error)
149 throw (Error) e.getTargetException();
150 else
151 throw e;
152 }
153 finally
154 {
155 // Restore the previous classloader ( in case we were called directly
156 // an not via a Thread.start() )
157 Thread.currentThread().setContextClassLoader(oldCL);
158 }
159 }
160 }
161
162 /**
163 * This can be used to boot another application
164 * From within another one.
165 *
166 * @returns ApplicationBoot data structure that must be configured before the application can be booted.
167 */
168 public ApplicationBoot createApplicationBoot() {
169 return new ApplicationBoot();
170 }
171
172 /**
173 * Boots the application in a new threadgroup and thread.
174 *
175 * @param bootData the application to boot.
176 * @exception thrown if a problem occurs during launching
177 */
178 synchronized public void startApplication(ApplicationBoot bootData) throws Exception
179 {
180 if( bootData == null )
181 throw new NullPointerException("Invalid argument: bootData argument was null");
182
183 applicationBoots.add(bootData);
184 ThreadGroup threads = new ThreadGroup(bootThreadGroup, bootData.applicationClass);
185 new Thread(threads, bootData, "main").start();
186 }
187
188 synchronized public ApplicationBoot[] getStartedApplications() {
189 ApplicationBoot rc[] = new ApplicationBoot[applicationBoots.size()];
190 return (ApplicationBoot[])applicationBoots.toArray(rc);
191 }
192
193 /** logs verbose message to the console */
194 protected void verbose(String msg) {
195 if( verbose )
196 System.out.println("[Boot] "+msg);
197 }
198
199 //////////////////////////////////////////////////////////////////////
200 //
201 // THE FOLLOWING SET OF FUNCTIONS ARE RELATED TO PROCESSING COMMAND LINE
202 // ARGUMENTS.
203 //
204 //////////////////////////////////////////////////////////////////////
205 protected static final String HELP = "-help";
206 protected static final String VERBOSE = "-verbose";
207 protected static final String BOOT_APP_SEPERATOR = System.getProperty("org.jboss.Boot.APP_SEPERATOR", ",");
208 protected static final String CP = "-cp";
209
210 protected static class InvalidCommandLineException extends Exception {
211 /**
212 * Constructor for InvalidCommandLineException.
213 * @param s
214 */
215 public InvalidCommandLineException(String s)
216 {
217 super(s);
218 }
219 }
220
221 /**
222 * Main entry point when called from the command line
223 * @param args the command line arguments
224 */
225 public static void main(String[] args)
226 {
227 Boot boot = Boot.getInstance();
228
229 // Put the args in a linked list since it easier to work with.
230 LinkedList llargs = new LinkedList();
231 for (int i = 0; i < args.length; i++)
232 llargs.add(args[i]);
233
234 try {
235
236 LinkedList ab = boot.processCommandLine(llargs);
237 Iterator i = ab.iterator();
238 while (i.hasNext())
239 {
240 ApplicationBoot bootData = (ApplicationBoot) i.next();
241 boot.startApplication(bootData);
242 }
243
244 } catch ( InvalidCommandLineException e ) {
245 System.err.println("Invalid Usage: "+e.getMessage());
246 System.err.println();
247 showUsage();
248 System.exit(1);
249 } catch ( Throwable e ) {
250 System.err.println("Failure occured while executing application: ");
251 e.printStackTrace(System.err);
252 System.exit(1);
253 }
254 }
255
256 /**
257 * This method is here so that if JBoss is running under
258 * Alexandria (An NT Service Installer), Alexandria can shutdown
259 * the system down correctly.
260 */
261 public static void systemExit(String argv[])
262 {
263 System.exit(0);
264 }
265
266 /**
267 * Processes the Boot class's command line arguments
268 *
269 * @return a linked list with ApplicationBoot objects
270 * @param args the command line arguments
271 */
272 protected LinkedList processCommandLine(LinkedList args) throws Exception
273 {
274 LinkedList rc = new LinkedList();
275
276 processBootOptions(args);
277 while (args.size() > 0)
278 {
279 ApplicationBoot d = processAppBootCommandLine(args);
280 if (d != null)
281 rc.add(d);
282 }
283
284 if (rc.size() == 0)
285 {
286 throw new InvalidCommandLineException("An application class name must be provided.");
287 }
288
289 return rc;
290 }
291
292 /**
293 * Processes to global options.
294 *
295 * @param args the command line arguments
296 */
297 protected void processBootOptions(LinkedList args) throws Exception
298 {
299 Iterator i = args.iterator();
300 while (i.hasNext())
301 {
302 String arg = (String) i.next();
303 if (arg.equalsIgnoreCase(VERBOSE))
304 {
305 verbose = true;
306 i.remove();
307 continue;
308 }
309 if (arg.equalsIgnoreCase(HELP))
310 {
311 showUsage();
312 System.exit(0);
313 }
314
315 // Didn't recognize it a boot option, then we must have started the application
316 // boot options.
317 return;
318 }
319 }
320
321 protected static void showUsage()
322 {
323 String programName = System.getProperty("org.jboss.Boot.proces-name", "boot");
324
325 System.out.println("usage: " + programName + " [boot-options] [app-options] class [args..]");
326 System.out.println(" to execute a class");
327 System.out.println(" or " + programName + " [boot-options] [app-options] class-1 [args..] , ... , [app-options] class-n [args..]");
328 System.out.println(" to execute multiple classes");
329 System.out.println();
330 System.out.println("boot-options:");
331 System.out.println(" -help show this help message");
332 System.out.println(" -verbose display detail messages regarding the boot process.");
333 System.out.println("app-options:");
334 System.out.println(" -cp <directories and zip/jar urls separated by ,> ");
335 System.out.println(" set search path for application classes and resources");
336 System.out.println();
337 }
338
339 /**
340 * Processes the command line argumenst for the next application on the command line.
341 *
342 * @param args the command line arguments
343 */
344 protected ApplicationBoot processAppBootCommandLine(LinkedList args) throws Exception
345 {
346 ApplicationBoot rc = new ApplicationBoot();
347 Iterator i = args.iterator();
348
349 while (i.hasNext())
350 {
351 String arg = (String) i.next();
352 i.remove();
353
354 if (rc.applicationClass == null)
355 {
356 if (arg.equalsIgnoreCase(CP))
357 {
358 if (!i.hasNext())
359 throw new InvalidCommandLineException("Invalid option: classpath missing after the " + CP + " option.");
360 String cp = (String) i.next();
361 i.remove();
362
363 StringTokenizer st = new StringTokenizer(cp, ",", false);
364 while (st.hasMoreTokens())
365 {
366 String t = st.nextToken();
367 if (t.length() == 0)
368 continue;
369 try
370 {
371 URL u = new URL(t);
372 rc.classpath.add(u);
373 }
374 catch (MalformedURLException e)
375 {
376 throw new InvalidCommandLineException("Application classpath value was invalid: " + e.getMessage());
377 }
378 }
379 continue;
380 }
381
382 rc.applicationClass = arg;
383 continue;
384 }
385 else
386 {
387 if (arg.equalsIgnoreCase(BOOT_APP_SEPERATOR))
388 {
389 break;
390 }
391 rc.args.add(arg);
392 }
393
394 }
395
396 if (rc.applicationClass == null)
397 return null;
398
399 return rc;
400 }
401
402 }