001: /**************************************************************************************
002: * Copyright (c) Jonas BonŽr, Alexandre Vasseur. All rights reserved. *
003: * http://aspectwerkz.codehaus.org *
004: * ---------------------------------------------------------------------------------- *
005: * The software in this package is published under the terms of the LGPL license *
006: * a copy of which has been included with this distribution in the license.txt file. *
007: **************************************************************************************/package org.codehaus.aspectwerkz.hook;
008:
009: import com.sun.jdi.Bootstrap;
010: import com.sun.jdi.ReferenceType;
011: import com.sun.jdi.VirtualMachine;
012: import com.sun.jdi.connect.AttachingConnector;
013: import com.sun.jdi.connect.Connector;
014: import com.sun.jdi.connect.IllegalConnectorArgumentsException;
015:
016: import java.io.BufferedOutputStream;
017: import java.io.ByteArrayOutputStream;
018: import java.io.DataOutputStream;
019: import java.io.File;
020: import java.io.FileOutputStream;
021: import java.io.IOException;
022: import java.io.InputStream;
023: import java.lang.reflect.InvocationTargetException;
024: import java.lang.reflect.Method;
025: import java.net.ConnectException;
026: import java.util.HashMap;
027: import java.util.Iterator;
028: import java.util.List;
029: import java.util.Map;
030:
031: /**
032: * Utility methods to manipulate class redefinition of java.lang.ClassLoader in xxxStarter
033: *
034: * @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur </a>
035: */
036: public class ClassLoaderPatcher {
037: /**
038: * Converts an input stream to a byte[]
039: */
040: public static byte[] inputStreamToByteArray(InputStream is)
041: throws IOException {
042: ByteArrayOutputStream os = new ByteArrayOutputStream();
043: for (int b = is.read(); b != -1; b = is.read()) {
044: os.write(b);
045: }
046: return os.toByteArray();
047: }
048:
049: /**
050: * Gets the bytecode of the modified java.lang.ClassLoader using given ClassLoaderPreProcessor class name
051: */
052: static byte[] getPatchedClassLoader(String preProcessorName) {
053: byte[] abyte = null;
054: InputStream is = null;
055: try {
056: is = ClassLoader.getSystemClassLoader().getParent()
057: .getResourceAsStream("java/lang/ClassLoader.class");
058: abyte = inputStreamToByteArray(is);
059: } catch (IOException e) {
060: throw new Error("failed to read java.lang.ClassLoader: "
061: + e.toString());
062: } finally {
063: try {
064: is.close();
065: } catch (Exception e) {
066: ;
067: }
068: }
069: if (preProcessorName != null) {
070: try {
071: ClassLoaderPreProcessor clpi = (ClassLoaderPreProcessor) Class
072: .forName(preProcessorName).newInstance();
073: abyte = clpi.preProcess(abyte);
074: } catch (Exception e) {
075: System.err
076: .println("failed to instrument java.lang.ClassLoader: preprocessor not found");
077: e.printStackTrace();
078: }
079: }
080: return abyte;
081: }
082:
083: /**
084: * Dump bytecode bytes in dir/className.class directory, created if needed
085: */
086: private static void writeClass(String className, byte[] bytes,
087: String dir) {
088: String filename = dir + File.separatorChar
089: + className.replace('.', File.separatorChar) + ".class";
090: int pos = filename.lastIndexOf(File.separatorChar);
091: if (pos > 0) {
092: String finalDir = filename.substring(0, pos);
093: (new File(finalDir)).mkdirs();
094: }
095: try {
096: DataOutputStream out = new DataOutputStream(
097: new BufferedOutputStream(new FileOutputStream(
098: filename)));
099: out.write(bytes);
100: out.close();
101: } catch (IOException e) {
102: System.err.println("failed to write " + className + " in "
103: + dir);
104: e.printStackTrace();
105: }
106: }
107:
108: /**
109: * HotSwap className in target VM
110: */
111: private static void redefineClass(VirtualMachine vm,
112: String className, byte[] bytes) {
113: // determine if VM support class HotSwap with introspection
114: try {
115: Method canM = VirtualMachine.class.getMethod(
116: "canRedefineClasses", new Class[] {});
117: if (((Boolean) canM.invoke(vm, new Object[] {}))
118: .equals(Boolean.FALSE)) {
119: throw new Error(
120: "target JVM cannot redefine classes, please force the use of -Xbootclasspath");
121: }
122: List classList = vm.classesByName(className);
123: if (classList.size() == 0) {
124: throw new Error("Fatal error: Can't find class "
125: + className);
126: }
127: ReferenceType rt = (ReferenceType) classList.get(0);
128: Map map = new HashMap();
129: map.put(rt, bytes);
130: Method doM = VirtualMachine.class.getMethod(
131: "redefineClasses", new Class[] { Map.class });
132: doM.invoke(vm, new Object[] { map });
133: } catch (NoSuchMethodException e) {
134: // java 1.3 or not HotSwap compatible JVM
135: throw new Error(
136: "target JVM cannot redefine classes, please force the use of -Xbootclasspath");
137: } catch (InvocationTargetException e) {
138: // java 1.4+ failure
139: System.err.println("failed to HotSwap " + className + ':');
140: e.getTargetException().printStackTrace();
141: throw new Error(
142: "try to force force the use of -Xbootclasspath");
143: } catch (IllegalAccessException e) {
144: // java 1.4+ failure
145: System.err.println("failed to HotSwap " + className + ':');
146: e.printStackTrace();
147: throw new Error(
148: "try to force force the use of -Xbootclasspath");
149: }
150: }
151:
152: /**
153: * Patch java.lang.ClassLoader with preProcessorName instance and dump class bytecode in dir
154: */
155: public static void patchClassLoader(String preProcessorName,
156: String dir) {
157: byte[] cl = getPatchedClassLoader(preProcessorName);
158: writeClass("java.lang.ClassLoader", cl, dir);
159: }
160:
161: /**
162: * Patch java.lang.ClassLoader with preProcessorName instance and hotswap in target VM using a JDWP attaching
163: * connector Don't wait before connecting
164: */
165: public static VirtualMachine hotswapClassLoader(
166: String preProcessorName, String transport, String address) {
167: return hotswapClassLoader(preProcessorName, transport, address,
168: 0);
169: }
170:
171: /**
172: * Patch java.lang.ClassLoader with preProcessorName instance and hotswap in target VM using a JDWP attaching
173: * connector
174: */
175: public static VirtualMachine hotswapClassLoader(
176: String preProcessorName, String transport, String address,
177: int secondsToWait) {
178: String name = null;
179: if ("dt_socket".equals(transport)) {
180: name = "com.sun.jdi.SocketAttach";
181: } else if ("dt_shmem".equals(transport)) {
182: name = "com.sun.jdi.SharedMemoryAttach";
183: }
184: AttachingConnector connector = null;
185: for (Iterator i = Bootstrap.virtualMachineManager()
186: .attachingConnectors().iterator(); i.hasNext();) {
187: AttachingConnector aConnector = (AttachingConnector) i
188: .next();
189: if (aConnector.name().equals(name)) {
190: connector = aConnector;
191: break;
192: }
193: }
194: if (connector == null) {
195: throw new Error("no AttachingConnector for transport: "
196: + transport);
197: }
198: Map args = connector.defaultArguments();
199: if ("dt_socket".equals(transport)) {
200: ((Connector.Argument) args.get("port")).setValue(address);
201: } else if ("dt_shmem".equals(transport)) {
202: ((Connector.Argument) args.get("name")).setValue(address);
203: }
204: try {
205: if (secondsToWait > 0) {
206: try {
207: Thread.sleep(1000 * secondsToWait);
208: } catch (Exception e) {
209: ;
210: }
211: }
212:
213: // loop 10 times, during 5 sec max. It appears some VM under Linux take time to accept
214: // connections
215: // this avoid to specifically set -Daspectwerkz.classloader.wait
216: VirtualMachine vm = null;
217: ConnectException vmConnectionRefused = new ConnectException(
218: "should not appear as is");
219: for (int retry = 0; retry < 10; retry++) {
220: try {
221: vm = connector.attach(args);
222: break;
223: } catch (ConnectException ce) {
224: vmConnectionRefused = ce;
225: try {
226: Thread.sleep(500);
227: } catch (Throwable t) {
228: ;
229: }
230: }
231: }
232: if (vm == null) {
233: throw vmConnectionRefused;
234: }
235: redefineClass(vm, "java.lang.ClassLoader",
236: getPatchedClassLoader(preProcessorName));
237: return vm;
238: } catch (IllegalConnectorArgumentsException e) {
239: System.err.println("failed to attach to VM (" + transport
240: + ", " + address + "):");
241: e.printStackTrace();
242: for (Iterator i = e.argumentNames().iterator(); i.hasNext();) {
243: System.err.println("wrong or missing argument - "
244: + i.next());
245: }
246: return null;
247: } catch (IOException e) {
248: System.err.println("failed to attach to VM (" + transport
249: + ", " + address + "):");
250: e.printStackTrace();
251: return null;
252: }
253: }
254: }
|