[seam-commits] Seam SVN: r10140 - trunk/src/wicket/org/jboss/seam/wicket/ioc.
seam-commits at lists.jboss.org
seam-commits at lists.jboss.org
Tue Mar 10 09:39:04 EDT 2009
Author: cpopetz
Date: 2009-03-10 09:39:03 -0400 (Tue, 10 Mar 2009)
New Revision: 10140
Modified:
trunk/src/wicket/org/jboss/seam/wicket/ioc/JavassistInstrumentor.java
Log:
JBSEAM-3981, JBSEAM-3979
Modified: trunk/src/wicket/org/jboss/seam/wicket/ioc/JavassistInstrumentor.java
===================================================================
--- trunk/src/wicket/org/jboss/seam/wicket/ioc/JavassistInstrumentor.java 2009-03-10 13:38:00 UTC (rev 10139)
+++ trunk/src/wicket/org/jboss/seam/wicket/ioc/JavassistInstrumentor.java 2009-03-10 13:39:03 UTC (rev 10140)
@@ -1,5 +1,14 @@
package org.jboss.seam.wicket.ioc;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.IllegalClassFormatException;
+import java.lang.instrument.Instrumentation;
+import java.security.ProtectionDomain;
+import java.util.HashSet;
+import java.util.Set;
+
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtBehavior;
@@ -17,96 +26,231 @@
import org.jboss.seam.log.Logging;
import org.jboss.seam.wicket.WicketComponent;
+/**
+ * This class is responsible for instrumenting wicket component classes so that
+ * they can be seam-enabled. The exact notion of what "seam-enabled" means is
+ * left to the implementation in WicketComponent and WicketHandler and their
+ * delegate classes, in particular the interceptor chains they create.
+ *
+ * The instrumentations that take place are:
+ *
+ * <ul>
+ * <li> Add a to add a synthetic WicketHandler field similar to:
+ * <pre>
+ * WicketHandler handler = WicketHandler.create(this);
+ * </pre>
+ * as well as a synthetic getter for this field </li>
+ *
+ * <li> Add a static reference to WicketComponent is created, to ensure that the instrumented
+ * class is registered with WicketComponent:
+ * <pre>
+ * static WicketComponent component = new org.jboss.seam.wicket.WicketComponent(ThisClassName.class);
+ * </pre>
+ * </li>
+ * <li> Make the instrumented class implement org.jboss.seam.wicket.ioc.InstrumentedComponent, which includes
+ * adding this method:
+ * <pre>
+ * public InstrumentedComponent getEnclosingInstance()
+ * {
+ * return handler == null ? null : handler.getEnclosingInstance(this);
+ * }</pre></li>
+ * <li>For each non-abstract non-synthetic, non-static method (not constructor) named foobar() in this class, create
+ * a synthetic private instance method, call it foobar$100, which contains the original code from foobar(). Then instrument
+ * foobar to do the following:
+ * <pre>
+ * SomeReturnType foobar(arguments)
+ * {
+ * Method method = OurClass.class.getDeclaredMethod("foobar",argumentSignature);
+ * if (this.handler != null)
+ * this.handler.beforeInvoke(this,method);
+ * SomeReturnType result;
+ * try {
+ * result = foobar$100(arguments);
+ * } catch (Exception e) {
+ * throw new RuntimeException(this.handler == null ? e : this.handler.handleException(this, method, e));
+ * }
+ * if (this.handler != null)
+ * this.handler.affterInvoke(this,method,result);
+ * return SomeReturnType;
+ *}
+ *</pre></li>
+ *
+ * <li>A similar instrumentation occurs for constructors, with the except that a super() or this() call must precede the
+ * invocation of the handler.</li>
+ * </ul>
+ *
+ * This instrumentor can be activated in several ways:
+ * <ul>
+ * <li>The WicketClassLoader will use it to instrument any class in WEB-INF/wicket</li>
+ * <li>The WicketInstrumentationTask (an ant task) will use it to instrument classes specified in ant</li>
+ * <li>The seam-wicket maven plugin will use it to instrument classes specified by maven configuration properties</li>
+ * <li>This class implements the ClassFileTransformer interface from the java.lang.instrument package,
+ * which means it can be specified with -javaagent:path/to/jboss-seam-wicket.jar. In this case, the system
+ * property "org.jboss.seam.wicket.instrumented-packages" should specify a comma-separated list of package names
+ * to instrument</li></ul>
+ *
+ * @see java.lang.instrument.ClassFileTransformer
+ * @see org.jboss.seam.wicket.ioc.WicketClassLoader
+ * @see org.jboss.seam.wicket.ioc.WicketInstrumentationTask
+ * @author pmuir, cpopetz
+ *
+ */
+public class JavassistInstrumentor implements ClassFileTransformer
+{
-public class JavassistInstrumentor
-{
-
private static LogProvider log = Logging.getLogProvider(JavassistInstrumentor.class);
-
+
+ /**
+ * The javassist Classpool, used for obtaining references to needed CtClasses
+ */
private ClassPool classPool;
+ /**
+ * If the constructor is used which specifies a list of packages to instrument, for example when
+ * using the -javaagent startup option, this is the list of packages
+ */
+ private Set<String> packagesToInstrument;
+
+ /**
+ * The CtClass for the InstrumentedComponent interface
+ */
+ private CtClass instrumentedComponent;
+
+
public JavassistInstrumentor(ClassPool classPool)
{
- this.classPool = classPool;
+ this.classPool = classPool;
+ try
+ {
+ instrumentedComponent = classPool.get(InstrumentedComponent.class.getName());
+ }
+ catch (NotFoundException e)
+ {
+ throw new RuntimeException(e);
+ }
}
-
+
+ public JavassistInstrumentor(ClassPool classPool, Set<String> packagesToInstrument)
+ {
+ this(classPool);
+ this.packagesToInstrument = packagesToInstrument;
+ }
+
public CtClass instrumentClass(String className) throws NotFoundException, CannotCompileException
{
log.debug("Instrumenting " + className);
CtClass implementation = classPool.get(className);
if (isInstrumentable(implementation))
{
- CtClass handlerClass = classPool.get(WicketHandler.class.getName());
- CtClass componentClass = classPool.get(WicketComponent.class.getName());
-
- CtField handlerField = new CtField(handlerClass, "handler", implementation);
- Initializer handlerInitializer = Initializer.byCall(handlerClass, "create");
- implementation.addField(handlerField, handlerInitializer);
-
- CtField wicketComponentField = new CtField(componentClass,"component",implementation);
- wicketComponentField.setModifiers(Modifier.STATIC);
- Initializer componentInit = Initializer.byExpr("new org.jboss.seam.wicket.WicketComponent(" + className + ".class)");
- implementation.addField(wicketComponentField,componentInit);
+ instrumentClass(implementation);
+ }
+ return implementation;
+ }
+ public CtClass instrumentClass(byte[] bytes) throws IOException, RuntimeException, NotFoundException, CannotCompileException
+ {
+ CtClass clazz = classPool.makeClass(new ByteArrayInputStream(bytes));
+ if (isInstrumentable(clazz))
+ {
+ instrumentClass(clazz);
+ return clazz;
+ }
+ else
+ {
+ return null;
+ }
+ }
- CtClass exception = classPool.get(Exception.class.getName());
-
- CtClass instrumentedComponent = classPool.get(InstrumentedComponent.class.getName());
- implementation.addInterface(instrumentedComponent);
- CtMethod getHandlerMethod = CtNewMethod.getter("getHandler", handlerField);
- CtMethod getEnclosingInstance = CtNewMethod.make("public " + InstrumentedComponent.class.getName() +" getEnclosingInstance() { return handler == null ? null : handler.getEnclosingInstance(this); }", implementation);
- implementation.addMethod(getEnclosingInstance);
- implementation.addMethod(getHandlerMethod);
-
- for (CtMethod method : implementation.getDeclaredMethods())
+ /**
+ * The main entry point for instrumenting a given class. Note that this will not check if the class is instrumentable,
+ * but will assume that you have.
+ * @param implementation The CtClass representing the class to instrument.
+ * @throws NotFoundException
+ * @throws CannotCompileException
+ */
+ public void instrumentClass(CtClass implementation) throws NotFoundException, CannotCompileException
+ {
+ String className = implementation.getName();
+ CtClass handlerClass = classPool.get(WicketHandler.class.getName());
+ CtClass componentClass = classPool.get(WicketComponent.class.getName());
+
+ CtField handlerField = new CtField(handlerClass, "handler", implementation);
+ Initializer handlerInitializer = Initializer.byCall(handlerClass, "create");
+ implementation.addField(handlerField, handlerInitializer);
+
+ CtField wicketComponentField = new CtField(componentClass, "component", implementation);
+ wicketComponentField.setModifiers(Modifier.STATIC);
+ Initializer componentInit = Initializer.byExpr("new org.jboss.seam.wicket.WicketComponent(" + className + ".class)");
+ implementation.addField(wicketComponentField, componentInit);
+
+ CtClass exception = classPool.get(Exception.class.getName());
+
+ implementation.addInterface(instrumentedComponent);
+ CtMethod getHandlerMethod = CtNewMethod.getter("getHandler", handlerField);
+ CtMethod getEnclosingInstance = CtNewMethod.make("public " + InstrumentedComponent.class.getName() + " getEnclosingInstance() { return handler == null ? null : handler.getEnclosingInstance(this); }", implementation);
+ implementation.addMethod(getEnclosingInstance);
+ implementation.addMethod(getHandlerMethod);
+
+ for (CtMethod method : implementation.getDeclaredMethods())
+ {
+ if (!Modifier.isStatic(method.getModifiers()) && !Modifier.isAbstract(method.getModifiers()))
{
- if (!Modifier.isStatic(method.getModifiers()) && !Modifier.isAbstract(method.getModifiers()))
+ if (!("getHandler".equals(method.getName()) || "getEnclosingInstance".equals(method.getName())))
{
- if (!("getHandler".equals(method.getName()) || "getEnclosingInstance".equals(method.getName())))
- {
- String newName = implementation.makeUniqueName(method.getName());
-
- CtMethod newMethod = CtNewMethod.copy(method, newName, implementation, null);
- newMethod.setModifiers(Modifier.PRIVATE);
- implementation.addMethod(newMethod);
- method.setBody(createBody(implementation, method, newMethod));
- log.trace("instrumented method " + method.getName());
- }
+ String newName = implementation.makeUniqueName(method.getName());
+
+ CtMethod newMethod = CtNewMethod.copy(method, newName, implementation, null);
+ newMethod.setModifiers(Modifier.PRIVATE);
+ implementation.addMethod(newMethod);
+ method.setBody(createBody(implementation, method, newMethod));
+ log.trace("instrumented method " + method.getName());
}
}
- for (CtConstructor constructor : implementation.getConstructors())
+ }
+ for (CtConstructor constructor : implementation.getConstructors())
+ {
+ if (constructor.isConstructor())
{
- if (constructor.isConstructor())
{
- {
- String constructorObject = createConstructorObject(className,constructor);
- constructor.insertBeforeBody(constructorObject + "handler.beforeInvoke(this, constructor);");
- constructor.addCatch("{" + constructorObject + "throw new RuntimeException(handler.handleException(this, constructor, e));}", exception, "e");
- constructor.insertAfter(constructorObject + "handler.afterInvoke(this, constructor);");
- log.trace("instrumented constructor " + constructor.getName());
- }
+ String constructorObject = createConstructorObject(className, constructor);
+ constructor.insertBeforeBody(constructorObject + "handler.beforeInvoke(this, constructor);");
+ constructor.addCatch("{" + constructorObject + "throw new RuntimeException(handler.handleException(this, constructor, e));}", exception, "e");
+ constructor.insertAfter(constructorObject + "handler.afterInvoke(this, constructor);");
+ log.trace("instrumented constructor " + constructor.getName());
}
}
}
- return implementation;
}
-
-
+
+ /**
+ * Create the body of the synthetic method
+ * @param clazz in this class
+ * @param method for this method
+ * @param newMethod the synthetic method
+ * @return the string of code for the body
+ * @throws NotFoundException
+ */
private static String createBody(CtClass clazz, CtMethod method, CtMethod newMethod) throws NotFoundException
{
- String src = "{" + createMethodObject(clazz,method) + "if (this.handler != null) this.handler.beforeInvoke(this, method);" + createMethodDelegation(newMethod) + "if (this.handler != null) result = ($r) this.handler.afterInvoke(this, method, ($w) result); return ($r) result;}";
+ String src = "{" + createMethodObject(clazz, method) + "if (this.handler != null) this.handler.beforeInvoke(this, method);" + createMethodDelegation(newMethod) + "if (this.handler != null) result = ($r) this.handler.afterInvoke(this, method, ($w) result); return ($r) result;}";
log.trace("Creating method " + clazz.getName() + "." + newMethod.getName() + "(" + newMethod.getSignature() + ")" + src);
return src;
}
-
+
+ /**
+ * Create the code for delegating to a given method, including handling exceptions
+ * @param method The method to which we are delegating
+ * @return the string of code for the delegation
+ * @throws NotFoundException
+ */
private static String createMethodDelegation(CtMethod method) throws NotFoundException
{
- CtClass returnType = method.getReturnType();
+ CtClass returnType = method.getReturnType();
if (returnType.equals(CtClass.voidType))
{
return "Object result = null; " + wrapInExceptionHandler(method.getName() + "($$);");
- }
+ }
else
{
String src = returnType.getName() + " result;";
@@ -114,29 +258,54 @@
return src;
}
}
-
+
+ /**
+ * Wrap some code in an exception handler that uses the WicketHandler to handle the exception
+ * @param src The code to wrap
+ * @return The wrapped code
+ */
private static String wrapInExceptionHandler(String src)
{
return "try {" + src + "} catch (Exception e) { throw new RuntimeException(this.handler == null ? e : this.handler.handleException(this, method, e)); }";
}
-
+
+ /**
+ * Create an arrray of parameter types for a given method or constructor.
+ * @param behavior The method or constructor
+ * @return The source string representing the declaration and initialization of the parameterTypes array
+ * @throws NotFoundException
+ */
private static String createParameterTypesArray(CtBehavior behavior) throws NotFoundException
{
String src = "Class[] parameterTypes = new Class[" + behavior.getParameterTypes().length + "];";
for (int i = 0; i < behavior.getParameterTypes().length; i++)
{
- src += "parameterTypes[" + i + "] = " + behavior.getParameterTypes()[i].getName() + ".class;";
+ src += "parameterTypes[" + i + "] = " + behavior.getParameterTypes()[i].getName() + ".class;";
}
return src;
}
-
+
+ /**
+ * Create the code for initializing a Method object for a given method
+ * @param clazz The class in which the method can be looked up
+ * @param method The method in question
+ * @return Source for looking up the method and declaring/initializing the "method" local variable
+ * @throws NotFoundException
+ */
private static String createMethodObject(CtClass clazz, CtMethod method) throws NotFoundException
{
String src = createParameterTypesArray(method);
- src += "java.lang.reflect.Method method = " + clazz.getName() +".class.getDeclaredMethod(\""+ method.getName() + "\", parameterTypes);";
+ src += "java.lang.reflect.Method method = " + clazz.getName() + ".class.getDeclaredMethod(\"" + method.getName() + "\", parameterTypes);";
return src;
}
-
+
+ /**
+ * Create the code for initializing a Constructor object for a given constructor
+ * @param className The name of the class in which the constructor can be looked up
+ * @param constructor The constructor to look up
+ * @return Source for looking up the constructor and declaring/initializing the "consturctor" local variable
+ * @throws NotFoundException
+ */
private static String createConstructorObject(String className, CtConstructor constructor) throws NotFoundException
{
String src = createParameterTypesArray(constructor);
@@ -144,29 +313,121 @@
return src;
}
- private static boolean isInstrumentable(CtClass clazz)
+ /**
+ * Returns true if the given class can be instrumented. This will return false if:
+ * <ul>
+ * <li> The class is an interface or an enum
+ * <li> The class is annotated with Seam's @Name annotationa or is a non-static inner class of a @Named class
+ * <li> The class is already instrumented. We check this by checking if it already implements the InstrumentedComponent
+ * interface
+ * </ul>
+ * @param clazz The class to check
+ * @return
+ */
+ private boolean isInstrumentable(CtClass clazz)
{
int modifiers = clazz.getModifiers();
if (Modifier.isInterface(modifiers) || Modifier.isEnum(modifiers))
{
return false;
}
-
- try
- {
- for (Object a : clazz.getAnnotations())
- {
- if (a instanceof Name)
- {
- return false;
- }
- }
+
+ try
+ {
+ // do not instrument @Named components or nested non-static classes
+ // inside named components
+ CtClass checkName = clazz;
+ do
+ {
+ for (Object a : checkName.getAnnotations())
+ {
+ if (a instanceof Name)
+ {
+ return false;
+ }
+ }
+ checkName = Modifier.isStatic(clazz.getModifiers()) ? null : checkName.getDeclaringClass();
+ }
+ while (checkName != null);
+
+ // do not instrument something we've already instrumented.
+ // can't use 'isSubtype' because the superclass may be instrumented
+ // while we are not
+ for (String inf : clazz.getClassFile2().getInterfaces())
+ if (inf.equals(instrumentedComponent.getName()))
+ return false;
}
- catch (ClassNotFoundException e)
+ catch (Exception e)
{
throw new RuntimeException(e);
}
+
return true;
}
-
+
+ /**
+ * This is the implementation of the ClassFileTransformer interface.
+ * @see java.lang.instrument.ClassFileTransformer
+ */
+ public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException
+ {
+ int index = className.lastIndexOf("/");
+ if (index < 1)
+ return null;
+ String packageName = className.substring(0, index);
+ if (!packagesToInstrument.contains(packageName) || className.contains("_javassist_"))
+ {
+ return null;
+ }
+ try
+ {
+ CtClass result = instrumentClass(classfileBuffer);
+ if (result == null)
+ return null;
+ else
+ return result.toBytecode();
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * This premain will be called if the vm is started with -javaagent:/path/to/jar/with/this/class
+ */
+ public static void premain(String args, Instrumentation instrumentation)
+ {
+ initAgent(instrumentation);
+ }
+
+ /**
+ * This premain will be called if the vm is told to use this agent after startup, which is done
+ * in a vm-dependent way
+ */
+ public static void agentmain(String args, Instrumentation instrumentation)
+ {
+ initAgent(instrumentation);
+ }
+
+ /**
+ * Set up instrumentation. This adds ourselves as a transformer to the instrumentation, and
+ * loads the set of packages to transform from the "org.jboss.seam.wicket.instrumented-packages"
+ * System property.
+ */
+ private static void initAgent(Instrumentation instrumentation)
+ {
+ Set<String> packagesToInstrument = new HashSet<String>();
+ String list = System.getProperty("org.jboss.seam.wicket.instrumented-packages");
+ if (list == null)
+ return;
+ for (String packageName : list.split(","))
+ {
+ packagesToInstrument.add(packageName.replaceAll("\\.", "/"));
+ }
+
+ ClassPool classPool = new ClassPool();
+ classPool.appendSystemPath();
+ instrumentation.addTransformer(new JavassistInstrumentor(classPool, packagesToInstrument));
+ }
}
More information about the seam-commits
mailing list