[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