View Javadoc

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.transform;
9   
10  import org.codehaus.aspectwerkz.util.Util;
11  import org.codehaus.aspectwerkz.expression.SubtypePatternType;
12  import org.codehaus.aspectwerkz.expression.regexp.Pattern;
13  import org.codehaus.aspectwerkz.expression.regexp.TypePattern;
14  import org.codehaus.aspectwerkz.hook.ClassPreProcessor;
15  import org.codehaus.aspectwerkz.hook.RuntimeClassProcessor;
16  import org.codehaus.aspectwerkz.transform.delegation.DelegationWeavingStrategy;
17  
18  import java.util.Collection;
19  import java.util.HashMap;
20  import java.util.Hashtable;
21  import java.util.Map;
22  
23  /***
24   * AspectWerkzPreProcessor is the entry point of the AspectWerkz layer 2. <p/>It implements the ClassPreProcessor
25   * interface defined in layer 1. <p/>Available options are:
26   * <ul>
27   * <li><code>-Daspectwerkz.transform.verbose=yes</code> turns on verbose mode: print on stdout all non filtered class
28   * names and which transformation are applied</li>
29   * <li><code>-Daspectwerkz.transform.dump=org.myapp.*</code> dumps transformed class matching pattern <i>org.myapp.*
30   * </i>(even unmodified ones) in <i>./_dump </i> directory (relative to where applications starts). The syntax
31   * <code>-Daspectwerkz.transform.dump=*</code> matchs all classes. The pattern language is the same as pointcut
32   * pattern language.</li>
33   * <li>else <code>-Daspectwerkz.transform.dump=org.myapp.*,before</code> dumps class before and after the
34   * transformation whose name starts with <i>org.myapp. </i>(even unmodified ones) in <i>./_dump/before </i> and
35   * <i>./_dump/after </i> directories (relative to where application starts)</li>
36   * <li><code>-Daspectwerkz.transform.filter=no</code> (or false) disables filtering of
37   * <code>org.codehaus.aspectwerkz</code> and related classes (trove, dom4j etc.). This should only be used in offline
38   * mode where weaving of those classes is needed. Setting this option in online mode will lead to
39   * <code>ClassCircularityError</code>.</li>
40   * </ul>
41   * 
42   * @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur </a>
43   * @author <a href="mailto:jboner@codehaus.org">Jonas Bonér </a>
44   */
45  public class AspectWerkzPreProcessor implements ClassPreProcessor, RuntimeClassProcessor {
46  
47      private final static String AW_WEAVING_STRATEGY = "aspectwerkz.weaving.strategy";
48  
49      private final static String AW_TRANSFORM_FILTER = "aspectwerkz.transform.filter";
50  
51      private final static String AW_TRANSFORM_VERBOSE = "aspectwerkz.transform.verbose";
52  
53      private final static String AW_TRANSFORM_DUMP = "aspectwerkz.transform.dump";
54  
55      private final static String AW_TRANSFORM_DETAILS = "aspectwerkz.transform.details";
56  
57      private final static TypePattern DUMP_PATTERN;
58  
59      private final static boolean NOFILTER;
60  
61      private final static boolean DUMP_BEFORE;
62  
63      private final static boolean DUMP_AFTER;
64  
65      public final static boolean VERBOSE;
66  
67      public final static boolean DETAILS;
68  
69      private final static int WEAVING_STRATEGY;
70  
71      static {
72          // define the weaving strategy
73          String weavingStrategy = System.getProperty(AW_WEAVING_STRATEGY, "delegation").trim();
74          if (weavingStrategy.equalsIgnoreCase("inlining")) {
75              WEAVING_STRATEGY = WeavingStrategy.INLINING;
76          } else if (weavingStrategy.equalsIgnoreCase("delegation")) {
77              WEAVING_STRATEGY = WeavingStrategy.DELEGATION;
78          } else {
79              throw new RuntimeException("unknown weaving strategy specified: " + weavingStrategy);
80          }
81          // define the tracing and dump options
82          String verbose = System.getProperty(AW_TRANSFORM_VERBOSE, null);
83          VERBOSE = "yes".equalsIgnoreCase(verbose) || "true".equalsIgnoreCase(verbose);
84          String details = System.getProperty(AW_TRANSFORM_DETAILS, null);
85          DETAILS = "yes".equalsIgnoreCase(details) || "true".equalsIgnoreCase(details);
86          String filter = System.getProperty(AW_TRANSFORM_FILTER, null);
87          NOFILTER = "no".equalsIgnoreCase(filter) || "false".equalsIgnoreCase(filter);
88          String dumpPattern = System.getProperty(AW_TRANSFORM_DUMP, null);
89          if (dumpPattern == null) {
90              DUMP_BEFORE = false;
91              DUMP_AFTER = false;
92              DUMP_PATTERN = null;
93          } else {
94              dumpPattern = dumpPattern.trim();
95              DUMP_AFTER = true;
96              DUMP_BEFORE = dumpPattern.indexOf(",before") > 0;
97              if (DUMP_BEFORE) {
98                  DUMP_PATTERN = Pattern.compileTypePattern(
99                      dumpPattern.substring(0, dumpPattern.indexOf(',')),
100                     SubtypePatternType.NOT_HIERARCHICAL);
101             } else {
102                 DUMP_PATTERN = Pattern.compileTypePattern(dumpPattern, SubtypePatternType.NOT_HIERARCHICAL);
103             }
104         }
105     }
106 
107     /***
108      * Bytecode cache for prepared class and runtime weaving.
109      * 
110      * @TODO: allow for other cache implementations (file, jms, clustered, jcache, JNDI, javagroups etc.)
111      */
112     private static Map s_classByteCache = new HashMap();
113 
114     /***
115      * Marks the pre-processor as initialized.
116      */
117     private boolean m_initialized = false;
118 
119     /***
120      * Pre processor weaving strategy.
121      */
122     private WeavingStrategy m_weavingStrategy;
123 
124     /***
125      * Initializes the transformer stack.
126      * 
127      * @param params not used
128      */
129     public void initialize(final Hashtable params) {
130         switch (WEAVING_STRATEGY) {
131             case WeavingStrategy.DELEGATION:
132                 m_weavingStrategy = new DelegationWeavingStrategy();
133                 break;
134 
135             case WeavingStrategy.INLINING:
136                 throw new Error("Not supported");
137         }
138         m_weavingStrategy.initialize(params);
139         m_initialized = true;
140     }
141 
142     /***
143      * Transform bytecode according to the transformer stack
144      * 
145      * @param name class name
146      * @param bytecode bytecode to transform
147      * @param loader classloader loading the class
148      * @return modified (or not) bytecode
149      */
150     public byte[] preProcess(final String name, final byte[] bytecode, final ClassLoader loader) {
151 
152         // filter out ExtClassLoader and BootClassLoader
153         if (!NOFILTER) {
154             if ((loader == null) || (loader.getParent() == null)) {
155                 return bytecode;
156             }
157         }
158         // needed for JRockit (as well as all in all TFs)
159         final String className = name.replace('/', '.');
160 
161         if (filter(className) || !m_initialized) {
162             return bytecode;
163         }
164         if (VERBOSE) {
165             log(Util.classLoaderToString(loader) + ':' + className + '[' + Thread.currentThread().getName() + ']');
166         }
167         return _preProcess(className, bytecode, loader);
168     }
169 
170     public byte[] _preProcess(final String name, final byte[] bytecode, final ClassLoader loader) {
171 
172         // needed for JRockit (as well as all in all TFs)
173         final String className = name.replace('/', '.');
174 
175         final Context context;
176         try {
177             // create a new transformation context
178             context = m_weavingStrategy.newContext(name, bytecode, loader);
179         } catch (Exception e) {
180             log("failed " + className);
181             e.printStackTrace();
182             return bytecode;
183         }
184 
185         // dump before (not compliant with multiple CL weaving same class differently, since based
186         // on class FQN name)
187         dumpBefore(className, context);
188 
189         // do the transformation
190         m_weavingStrategy.transform(className, context);
191 
192         // dump after as required
193         dumpAfter(className, context);
194 
195         // handle the prepared Class cache for further runtime weaving
196         if (context.isPrepared()) {
197             ClassCacheTuple key = new ClassCacheTuple(loader, className);
198             log("cache prepared " + className);
199             s_classByteCache.put(key, new ByteArray(context.getCurrentBytecode()));
200         }
201 
202         // return the transformed bytecode
203         return context.getCurrentBytecode();
204     }
205 
206     /***
207      * Runtime weaving of given Class according to the actual definition
208      * 
209      * @param klazz
210      * @return new bytes for Class representation
211      * @throws Throwable
212      */
213     public byte[] preProcessActivate(final Class klazz) throws Throwable {
214         String className = klazz.getName();
215 
216         // fetch class from prepared class cache
217         ClassCacheTuple key = new ClassCacheTuple(klazz);
218         ByteArray currentBytesArray = (ByteArray) s_classByteCache.get(key);
219         if (currentBytesArray == null) {
220             throw new RuntimeException("can not find cached class in cache for prepared classes: " + className);
221         }
222 
223         // flush class info repository cache so that new weaving is aware of wrapper method
224         // existence
225 
226         // FIXME implement and move method
227         //        JavassistClassInfoRepository.removeClassInfoFromAllClassLoaders(klazz.getName());
228 
229         // transform as if multi weaving
230         byte[] newBytes = preProcess(klazz.getName(), currentBytesArray.getBytes(), klazz.getClassLoader());
231 
232         // update cache
233         s_classByteCache.put(key, new ByteArray(newBytes));
234         return newBytes;
235     }
236 
237     /***
238      * Logs a message.
239      * 
240      * @param msg the message to log
241      */
242     public static void log(final String msg) {
243         if (VERBOSE) {
244             System.out.println(msg);
245         }
246     }
247 
248     /***
249      * Excludes instrumentation for the class used during the instrumentation
250      * 
251      * @param klass the AspectWerkz class
252      */
253     private static boolean filter(final String klass) {
254         return (klass == null)
255             || klass.startsWith("org.codehaus.aspectwerkz.")
256             || klass.startsWith("javassist.")
257             || klass.startsWith("org.objectweb.asm.")
258             || klass.startsWith("com.karneim.")
259             || klass.startsWith("com.bluecast.")
260             || klass.startsWith("gnu.trove.")
261             || klass.startsWith("org.dom4j.")
262             || klass.startsWith("org.xml.sax.")
263             || klass.startsWith("javax.xml.parsers.")
264             || klass.startsWith("sun.reflect.Generated")// issue on J2SE 5 reflection - AW-245
265 
266         // TODO: why have we had jMunit classes filtered out, they are not part of AW core, can't be filtered out since
267         // users want to advise on those
268             //|| klass.startsWith("junit.")
269         ;
270     }
271 
272     /***
273      * Dumps class before weaving.
274      * 
275      * @param className
276      * @param context
277      */
278     public static void dumpBefore(final String className, final Context context) {
279         if (DUMP_BEFORE) {
280             if (DUMP_PATTERN.matches(className)) {
281                 context.dump("_dump/before/");
282             }
283         }
284     }
285 
286     /***
287      * Dumps class after weaving.
288      * 
289      * @param className
290      * @param context
291      */
292     public static void dumpAfter(final String className, final Context context) {
293         if (DUMP_AFTER) {
294             if (DUMP_PATTERN.matches(className)) {
295                 context.dump("_dump/" + (DUMP_BEFORE ? "after/" : ""));
296             }
297         }
298     }
299 
300     /***
301      * Always dumps class.
302      * 
303      * @param context
304      */
305     public static void dumpForce(final Context context) {
306         context.dump("_dump/force/");
307     }
308 
309     /***
310      * Returns the caching tuples.
311      * 
312      * @return
313      */
314     public Collection getClassCacheTuples() {
315         return s_classByteCache.keySet();
316     }
317 }