KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > codehaus > aspectwerkz > hook > ProcessStarter


1 /**************************************************************************************
2  * Copyright (c) Jonas Bonér, Alexandre Vasseur. All rights reserved. *
3  * http://aspectwerkz.codehaus.org *
4  * ---------------------------------------------------------------------------------- *
5  * The software in this package is published under the terms of the LGPL license *
6  * a copy of which has been included with this distribution in the license.txt file. *
7  **************************************************************************************/

8 package org.codehaus.aspectwerkz.hook;
9
10 import com.sun.jdi.VirtualMachine;
11
12 import java.io.File JavaDoc;
13 import java.io.IOException JavaDoc;
14 import java.util.StringTokenizer JavaDoc;
15
16 /**
17  * ProcessStarter uses JPDA JDI api to start a VM with a runtime modified java.lang.ClassLoader, or transparently use a
18  * Xbootclasspath style (java 1.3 detected or forced) <p/>
19  * <p/>
20  * <h2>Important note</h2>
21  * Due to a JPDA issue in LauchingConnector, this implementation is based on Process forking. If Xbootclasspath is not
22  * used the target VM is started with JDWP options <i>transport=dt_socket,address=9300 </i> unless other specified.
23  * <br/>It is possible after the short startup sequence to attach a debugger or any other JPDA attaching connector. It
24  * has been validated against a WebLogic 7 startup and is the <i>must use </i> implementation.
25  * </p>
26  * <p/>
27  * <p/>
28  * <h2>Implementation Note</h2>
29  * See http://java.sun.com/products/jpda/ <br/>See http://java.sun.com/j2se/1.4.1/docs/guide/jpda/jdi/index.html <br/>
30  * </p>
31  * <p/><p/>For java 1.3, it launch the target VM using a modified java.lang.ClassLoader by generating it and putting it
32  * in the bootstrap classpath of the target VM. The java 1.3 version should only be run for experimentation since it
33  * breaks the Java 2 Runtime Environment binary code license by overriding a class of rt.jar
34  * </p>
35  * <p/><p/>For java 1.4, it hotswaps java.lang.ClassLoader with a runtime patched version, wich is compatible with the
36  * Java 2 Runtime Environment binary code license. For JVM not supporting the class hotswapping, the same mechanism as
37  * for java 1.3 is used.
38  * </p>
39  * <p/>
40  * <p/>
41  * <h2>Usage</h2>
42  * Use it as a replacement of "java" :<br/><code>java [target jvm option] [target classpath]
43  * targetMainClass [targetMainClass args]</code>
44  * <br/>should be called like: <br/><code>java [jvm option] [classpath]
45  * org.codehaus.aspectwerkz.hook.ProcessStarter [target jvm option] [target classpath] targetMainClass [targetMainClass
46  * args]</code>
47  * <br/><b>[classpath] must contain %JAVA_HOME%/tools.jar for HotSwap support </b> <br/>[target jvm option] can contain
48  * JDWP options, transport and address are preserved if specified.
49  * </p>
50  * <p/>
51  * <p/>
52  * <h2>Options</h2>
53  * [classpath] must contain %JAVA_HOME%/tools.jar and the jar you want for bytecode modification (asm, bcel, ...)
54  * <br/>The java.lang.ClassLoader is patched using the <code>-Daspectwerkz.classloader.clpreprocessor=...</code> in
55  * [jvm option]. Specify the FQN of your implementation of hook.ClassLoaderPreProcessor. See {@link
56  * org.codehaus.aspectwerkz.hook.ClassLoaderPreProcessor} If not given, the default AspectWerkz layer 1 ASM
57  * implementation hook.impl.* is used, which is equivalent to
58  * <code>-Daspectwerkz.classloader.clpreprocessor=org.codehaus.aspectwerkz.hook.impl.ClassLoaderPreProcessorImpl</code>
59  * <br/>Use -Daspectwerkz.classloader.wait=2 in [jvm option] to force a pause of 2 seconds between process fork and JPDA
60  * connection for HotSwap. Defaults to no wait.
61  * </p>
62  * <p/>
63  * <p/>
64  * <h2>Disabling HotSwap</h2>
65  * You disable HotSwap and thus force the use of -Xbootclasspath (like in java 1.3 mode) and specify the directory where
66  * the modified class loader bytecode will be stored using in [jvm option]
67  * <code>-Daspectwerkz.classloader.clbootclasspath=...</code>. Specify the directory where you want the patched
68  * java.lang.ClassLoader to be stored. Default is "./_boot". The directory is created if needed (with the subdirectories
69  * corresponding to package names). <br/>The directory is <b>automatically </b> incorporated in the -Xbootclasspath
70  * option of [target jvm option]. <br/>You shoud use this option mainly for debuging purpose, or if you need to start
71  * different jvm with different classloader preprocessor implementations.
72  * </p>
73  * <p/>
74  * <p/>
75  * <h2>Option for AspectWerkz layer 1 ASM implementation</h2>
76  * When using the default AspectWerkz layer 1 ASM implementation
77  * <code>org.codehaus.aspectwerkz.hook.impl.ClassLoaderPreProcessorImpl</code>, java.lang.ClassLoader is modified to
78  * call a class preprocessor at each class load (except for class loaded by the bootstrap classloader). <br/>The
79  * effective class preprocessor is defined with <code>-Daspectwerkz.classloader.preprocessor=...</code> in [target jvm
80  * option]. Specify the FQN of your implementation of org.codehaus.aspectwerkz.hook.ClassPreProcessor interface. <br/>If
81  * this parameter is not given, the default AspectWerkz layer 2
82  * org.codehaus.aspectwerkz.transform.AspectWerkzPreProcessor is used. <br/>
83  * </p>
84  *
85  * @author <a HREF="mailto:alex@gnilux.com">Alexandre Vasseur </a>
86  */

87 public class ProcessStarter {
88     /**
89      * option for classloader preprocessor target
90      */

91     final static String JavaDoc CL_PRE_PROCESSOR_CLASSNAME_PROPERTY = "aspectwerkz.classloader.clpreprocessor";
92
93     /**
94      * default dir when -Xbootclasspath is forced or used (java 1.3)
95      */

96     private final static String JavaDoc CL_BOOTCLASSPATH_FORCE_DEFAULT = "." + File.separatorChar + "_boot";
97
98     /**
99      * option for target dir when -Xbootclasspath is forced or used (java 1.3)
100      */

101     private final static String JavaDoc CL_BOOTCLASSPATH_FORCE_PROPERTY = "aspectwerkz.classloader.clbootclasspath";
102
103     /**
104      * option for seconds to wait before connecting
105      */

106     private final static String JavaDoc CONNECTION_WAIT_PROPERTY = "aspectwerkz.classloader.wait";
107
108     /**
109      * target process
110      */

111     private Process JavaDoc process = null;
112
113     /**
114      * used if target VM exits before launching VM
115      */

116     private boolean executeShutdownHook = true;
117
118     /**
119      * thread to redirect streams of target VM in launching VM
120      */

121     private Thread JavaDoc inThread;
122
123     /**
124      * thread to redirect streams of target VM in launching VM
125      */

126     private Thread JavaDoc outThread;
127
128     /**
129      * thread to redirect streams of target VM in launching VM
130      */

131     private Thread JavaDoc errThread;
132
133     /**
134      * Test if current java installation supports HotSwap
135      */

136     private static boolean hasCanRedefineClass() {
137         try {
138             VirtualMachine.class.getMethod("canRedefineClasses", new Class JavaDoc[]{});
139         } catch (NoSuchMethodException JavaDoc e) {
140             return false;
141         }
142         return true;
143     }
144
145     private int run(String JavaDoc[] args) {
146         // retrieve options and main
147
String JavaDoc[] javaArgs = parseJavaCommandLine(args);
148         String JavaDoc optionArgs = javaArgs[0];
149         String JavaDoc cpArgs = javaArgs[1];
150         String JavaDoc mainArgs = javaArgs[2];
151         String JavaDoc options = optionArgs + " -cp " + cpArgs;
152         String JavaDoc clp = System.getProperty(
153                 CL_PRE_PROCESSOR_CLASSNAME_PROPERTY,
154                 "org.codehaus.aspectwerkz.hook.impl.ClassLoaderPreProcessorImpl"
155         );
156
157         // if java version does not support method "VirtualMachine.canRedefineClass"
158
// or if bootclasspath is forced, transform optionsArg
159
if (!hasCanRedefineClass() || (System.getProperty(CL_BOOTCLASSPATH_FORCE_PROPERTY) != null)) {
160             String JavaDoc bootDir = System.getProperty(CL_BOOTCLASSPATH_FORCE_PROPERTY, CL_BOOTCLASSPATH_FORCE_DEFAULT);
161             if (System.getProperty(CL_BOOTCLASSPATH_FORCE_PROPERTY) != null) {
162                 System.out.println("HotSwap deactivated, using bootclasspath: " + bootDir);
163             } else {
164                 System.out.println("HotSwap not supported by this java version, using bootclasspath: " + bootDir);
165             }
166             ClassLoaderPatcher.patchClassLoader(clp, bootDir);
167             BootClasspathStarter starter = new BootClasspathStarter(options, mainArgs, bootDir);
168             try {
169                 process = starter.launchVM();
170             } catch (IOException JavaDoc e) {
171                 System.err.println("failed to launch process :" + starter.getCommandLine());
172                 e.printStackTrace();
173                 return -1;
174             }
175
176             // attach stdout VM streams to this streams
177
// this is needed early to support -verbose:class like options
178
redirectStdoutStreams();
179         } else {
180             // lauch VM in suspend mode
181
JDWPStarter starter = new JDWPStarter(options, mainArgs, "dt_socket", "9300");
182             try {
183                 process = starter.launchVM();
184             } catch (IOException JavaDoc e) {
185                 System.err.println("failed to launch process :" + starter.getCommandLine());
186                 e.printStackTrace();
187                 return -1;
188             }
189
190             // attach stdout VM streams to this streams
191
// this is needed early to support -verbose:class like options
192
redirectStdoutStreams();
193
194             // override class loader in VM thru an attaching connector
195
int secondsToWait = 0;
196             try {
197                 secondsToWait = Integer.parseInt(System.getProperty(CONNECTION_WAIT_PROPERTY, "0"));
198             } catch (NumberFormatException JavaDoc nfe) {
199                 ;
200             }
201             VirtualMachine vm = ClassLoaderPatcher.hotswapClassLoader(
202                     clp,
203                     starter.getTransport(),
204                     starter.getAddress(),
205                     secondsToWait
206             );
207             if (vm == null) {
208                 process.destroy();
209             } else {
210                 vm.resume();
211                 vm.dispose();
212             }
213         }
214
215         // attach VM other streams to this streams
216
redirectOtherStreams();
217
218         // add a shutdown hook to "this" to shutdown VM
219
Thread JavaDoc shutdownHook = new Thread JavaDoc() {
220             public void run() {
221                 shutdown();
222             }
223         };
224         try {
225             Runtime.getRuntime().addShutdownHook(shutdownHook);
226             int exitCode = process.waitFor();
227             executeShutdownHook = false;
228             return exitCode;
229         } catch (Exception JavaDoc e) {
230             executeShutdownHook = false;
231             e.printStackTrace();
232             return -1;
233         }
234     }
235
236     /**
237      * shutdown target VM (used by shutdown hook of lauching VM)
238      */

239     private void shutdown() {
240         if (executeShutdownHook) {
241             process.destroy();
242         }
243         try {
244             outThread.join();
245             errThread.join();
246         } catch (InterruptedException JavaDoc e) {
247             ;
248         }
249     }
250
251     /**
252      * Set up stream redirection in target VM for stdout
253      */

254     private void redirectStdoutStreams() {
255         outThread = new StreamRedirectThread("out.redirect", process.getInputStream(), System.out);
256         outThread.start();
257     }
258
259     /**
260      * Set up stream redirection in target VM for stderr and stdin
261      */

262     private void redirectOtherStreams() {
263         inThread = new StreamRedirectThread("in.redirect", System.in, process.getOutputStream());
264         inThread.setDaemon(true);
265         errThread = new StreamRedirectThread("err.redirect", process.getErrorStream(), System.err);
266         inThread.start();
267         errThread.start();
268     }
269
270     public static void main(String JavaDoc[] args) {
271         System.exit((new ProcessStarter()).run(args));
272     }
273
274     private static String JavaDoc escapeWhiteSpace(String JavaDoc s) {
275         if (s.indexOf(' ') > 0) {
276             StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
277             StringTokenizer JavaDoc st = new StringTokenizer JavaDoc(s, " ", true);
278             String JavaDoc current = null;
279             while (st.hasMoreTokens()) {
280                 current = st.nextToken();
281                 if (" ".equals(current)) {
282                     sb.append("\\ ");
283                 } else {
284                     sb.append(current);
285                 }
286             }
287             return sb.toString();
288         } else {
289             return s;
290         }
291     }
292
293     /**
294      * Remove first and last " or ' if any
295      *
296      * @param s string to handle
297      * @return s whitout first and last " or ' if any
298      */

299     public static String JavaDoc removeEmbracingQuotes(String JavaDoc s) {
300         if ((s.length() >= 2) && (s.charAt(0) == '"') && (s.charAt(s.length() - 1) == '"')) {
301             return s.substring(1, s.length() - 1);
302         } else if ((s.length() >= 2) && (s.charAt(0) == '\'') && (s.charAt(s.length() - 1) == '\'')) {
303             return s.substring(1, s.length() - 1);
304         } else {
305             return s;
306         }
307     }
308
309     /**
310      * Analyse the args[] as a java command line
311      *
312      * @param args
313      * @return String[] [0]:jvm options except -cp|-classpath, [1]:classpath without -cp, [2]: mainClass + mainOptions
314      */

315     public String JavaDoc[] parseJavaCommandLine(String JavaDoc[] args) {
316         StringBuffer JavaDoc optionsArgB = new StringBuffer JavaDoc();
317         StringBuffer JavaDoc cpOptionsArgB = new StringBuffer JavaDoc();
318         StringBuffer JavaDoc mainArgB = new StringBuffer JavaDoc();
319         String JavaDoc previous = null;
320         boolean foundMain = false;
321         for (int i = 0; i < args.length; i++) {
322             //System.out.println("" + i + " " + args[i]);
323
if (args[i].startsWith("-") && !foundMain) {
324                 if (!("-cp".equals(args[i])) && !("-classpath").equals(args[i])) {
325                     optionsArgB.append(args[i]).append(" ");
326                 }
327             } else if (!foundMain && ("-cp".equals(previous) || "-classpath".equals(previous))) {
328                 if (cpOptionsArgB.length() > 0) {
329                     cpOptionsArgB.append(
330                             (System.getProperty("os.name", "").toLowerCase().indexOf("windows") >= 0)
331                             ? ";"
332                             : ":"
333                     );
334                 }
335                 cpOptionsArgB.append(removeEmbracingQuotes(args[i]));
336             } else {
337                 foundMain = true;
338                 mainArgB.append(args[i]).append(" ");
339             }
340             previous = args[i];
341         }
342
343         // restore quote around classpath or escape whitespace depending on win*/*nix
344
StringBuffer JavaDoc classPath = new StringBuffer JavaDoc();
345         if (System.getProperty("os.name", "").toLowerCase().indexOf("windows") >= 0) {
346             classPath = classPath.append("\"").append(cpOptionsArgB.toString()).append("\"");
347         } else {
348             classPath = classPath.append(escapeWhiteSpace(cpOptionsArgB.toString()));
349         }
350         String JavaDoc[] res = new String JavaDoc[]{
351             optionsArgB.toString(), classPath.toString(), mainArgB.toString()
352         };
353         return res;
354     }
355 }
Popular Tags