/** * Helios, OpenSource Monitoring * Brought to you by the Helios Development Group * * Copyright 2007, Helios Development Group and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. * */ package org.helios.apmrouter.instrumentation; import javassist.*; import org.helios.apmrouter.trace.ITracer; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.security.ProtectionDomain; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * <p>Title: TraceClassFileTransformer</p> * <p>Description: Implementation of a {@link ClassFileTransformer} that instruments {@link Trace} annotated methods</p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.apmrouter.instrumentation.TraceClassFileTransformer</code></p> */ public class TraceClassFileTransformer implements ClassFileTransformer { /** A set of package names that will be inspected for {@link Trace} annotated methods */ protected final Set<String> targetPackages = new HashSet<String>(); /** * Creates a new TraceClassFileTransformer * @param targetPackages A set of package names that will be inspected for {@link Trace} annotated methods */ public TraceClassFileTransformer(Set<String> targetPackages) { if(targetPackages!=null) { this.targetPackages.addAll(targetPackages); } } public static void log(Object msg) { System.out.println("[TraceClassFileTransformer]:" + msg); } /** * {@inheritDoc} * @see java.lang.instrument.ClassFileTransformer#transform(java.lang.ClassLoader, java.lang.String, java.lang.Class, java.security.ProtectionDomain, byte[]) */ @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { ClassPool tClassPool = null; try { final String clazzName = className.replace('/', '.'); int dotIndex = clazzName.lastIndexOf("."); final String packageName = dotIndex==-1 ? "" : clazzName.substring(0, dotIndex); if(!targetPackages.contains(packageName)) return classfileBuffer; log("Inspecting class [" + clazzName + "]"); tClassPool = new ClassPool(true); tClassPool.importPackage("org.helios.apmrouter.trace"); tClassPool.appendClassPath(new LoaderClassPath(loader)); tClassPool.appendClassPath(new ByteArrayClassPath(className, classfileBuffer)); CtClass clazz = tClassPool.get(clazzName); Map<CtMethod, TraceImpl> methodAnnotations = new HashMap<CtMethod, TraceImpl>(); for(CtMethod ctMethod : clazz.getDeclaredMethods()) { Object traceAnnotation = ctMethod.getAnnotation(Trace.class); if(traceAnnotation!=null) { log("Found @Trace annotation [" + traceAnnotation+ "] on [" + ctMethod.getLongName() + "]"); try { TraceImpl trace = new TraceImpl(traceAnnotation); methodAnnotations.put(ctMethod, trace); log("Trace:\n" + trace); } catch (Throwable t) { t.printStackTrace(System.err); return classfileBuffer; } } } if(!methodAnnotations.isEmpty()) { byte[] byteCode = instrumentClass(tClassPool, clazz, methodAnnotations); if(byteCode==null) { log("Failed to instrument class [" + clazzName + "]"); return classfileBuffer; } return byteCode; } return classfileBuffer; } catch (Exception e) { e.printStackTrace(System.err); return classfileBuffer; } finally { } } /** * Instruments the passed class * @param cp The class pool in use * @param clazz The class to instrument * @param methodAnnotations A map of trace annotations keyed by the methods they annotated * @return the byte code array of the instrumented class */ protected byte[] instrumentClass(ClassPool cp, CtClass clazz, Map<CtMethod, TraceImpl> methodAnnotations) { try { CtField tracerField = new CtField(cp.get(ITracer.class.getName()),TraceCollection.TRACER_FIELD, clazz); tracerField.setModifiers(tracerField.getModifiers() | Modifier.STATIC | Modifier.FINAL); clazz.addField(tracerField, "TracerFactory.getTracer()"); for(Map.Entry<CtMethod, TraceImpl> entry: methodAnnotations.entrySet()) { CtMethod method = entry.getKey(); TraceImpl ti = entry.getValue(); String originalName = method.getName(); String newName = originalName +"$impl"; method.setName(newName); CtMethod wrapperMethod = CtNewMethod.copy(method, originalName, clazz, null); String namespaceFieldName = originalName + "_" + method.getSignature().hashCode(); CtField namespaceField = new CtField(cp.get(String[].class.getName()),namespaceFieldName, clazz); namespaceField.setModifiers(tracerField.getModifiers() | Modifier.STATIC | Modifier.FINAL); StringBuilder namespaceInit = new StringBuilder("new String[]{"); String[] namespace = ti.getNamespace(); if(namespace.length>0) { for(String ns: namespace) { namespaceInit.append("\"").append(ns).append("\","); } namespaceInit.deleteCharAt(namespaceInit.length()-1); } namespaceInit.append("};"); clazz.addField(namespaceField, namespaceInit.toString()); log("Added field [" + namespaceFieldName + "] with init of [" + namespaceInit + "]"); final StringBuilder body = new StringBuilder("{"); // ====================== Do Pre Inv ========================== for(TraceCollection tc: entry.getValue().getCollections()) { try { tc.addPreInvoke(clazz, method, wrapperMethod, ti, body); } catch (Exception e) { e.printStackTrace(System.err); return null; } } // ====================== Add Dispatch ========================== String type = method.getReturnType().getName(); if (!"void".equals(type)) { body.append(type + " result = "); } body.append("\n").append(newName + "($$);\n"); // ====================== Do Posty Inv ========================== for(TraceCollection tc: entry.getValue().getCollections()) { try { tc.addPostInvoke(clazz, method, wrapperMethod, ti, body); } catch (Exception e) { e.printStackTrace(System.err); return null; } } body.append("}"); log(body); wrapperMethod.setBody(body.toString()); clazz.addMethod(wrapperMethod); } return clazz.toBytecode(); } catch (Exception e) { e.printStackTrace(System.err); return null; } } }