View Javadoc

1   /*************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.annotation;
9   
10  import org.codehaus.aspectwerkz.definition.AspectDefinition;
11  import org.codehaus.aspectwerkz.definition.DefinitionParserHelper;
12  import org.codehaus.aspectwerkz.definition.AdviceDefinition;
13  import org.codehaus.aspectwerkz.definition.DeploymentScope;
14  import org.codehaus.aspectwerkz.exception.DefinitionException;
15  import org.codehaus.aspectwerkz.reflect.ClassInfo;
16  import org.codehaus.aspectwerkz.reflect.FieldInfo;
17  import org.codehaus.aspectwerkz.reflect.MethodInfo;
18  import org.codehaus.aspectwerkz.reflect.ClassInfoHelper;
19  import org.codehaus.aspectwerkz.annotation.instrumentation.asm.AsmAnnotations;
20  import org.codehaus.aspectwerkz.DeploymentModel;
21  import org.codehaus.aspectwerkz.util.Strings;
22  import org.codehaus.aspectwerkz.aspect.AdviceType;
23  
24  import java.util.Iterator;
25  import java.util.List;
26  
27  /***
28   * Extracts the aspects annotations from the class files and creates a meta-data representation of them.
29   * <br/>
30   * Note: we are not using reflection to loop over fields, etc, so that we do not trigger nested loading, which could be
31   * potential target classes.
32   *
33   * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
34   * @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur </a>
35   */
36  public class AspectAnnotationParser {
37  
38      /***
39       * The sole instance.
40       */
41      private final static AspectAnnotationParser INSTANCE = new AspectAnnotationParser();
42  
43      /***
44       * Private constructor to prevent subclassing.
45       */
46      private AspectAnnotationParser() {
47      }
48  
49      /***
50       * Parse the attributes and create and return a meta-data representation of them.
51       *
52       * @param classInfo the class to extract attributes from
53       * @param aspectDef the aspect definition
54       * @param loader
55       */
56      public static void parse(final ClassInfo classInfo, final AspectDefinition aspectDef, final ClassLoader loader) {
57          INSTANCE.doParse(classInfo, aspectDef, loader);
58      }
59  
60      /***
61       * Parse the attributes and create and return a meta-data representation of them.
62       *
63       * @param classInfo the class to extract attributes from
64       * @param aspectDef the aspect definition
65       * @param loader
66       */
67      private void doParse(final ClassInfo classInfo, final AspectDefinition aspectDef, final ClassLoader loader) {
68          if (classInfo == null) {
69              throw new IllegalArgumentException("class to parse can not be null");
70          }
71  
72          Aspect aspectAnnotation = (Aspect) AsmAnnotations.getAnnotation(
73                  AnnotationConstants.ASPECT,
74                  classInfo
75          );
76  
77          String aspectName = classInfo.getName();
78          String deploymentModelAsString = null;
79  
80          if (aspectAnnotation != null) {
81              if (aspectAnnotation.value() != null) {
82                  //@Aspect(perJVM)
83                  deploymentModelAsString = aspectAnnotation.value();
84              } else {
85                  if (aspectAnnotation.name() != null) {
86                      //@Aspect(name=..)
87                      aspectName = aspectAnnotation.name();
88                  }
89                  if (aspectAnnotation.deploymentModel() != null) {
90                      //@Aspect(deploymentModel=..)
91                      deploymentModelAsString = aspectAnnotation.deploymentModel();
92                  }
93              }
94          }
95  
96          // attribute settings override the xml settings
97          aspectDef.setDeploymentModel(DeploymentModel.getDeploymentModelFor(deploymentModelAsString));
98          String className = classInfo.getName();
99          parseFieldAttributes(classInfo, aspectDef);
100         parseMethodAttributes(classInfo, className, aspectName, aspectDef);
101     }
102 
103     /***
104      * Parses the field attributes and creates a meta-data representation of them.
105      *
106      * @param classInfo the class to extract attributes from
107      * @param aspectDef the aspect definition
108      */
109     private void parseFieldAttributes(final ClassInfo classInfo, final AspectDefinition aspectDef) {
110         if (aspectDef == null) {
111             throw new IllegalArgumentException("aspect definition can not be null");
112         }
113         if (classInfo == null) {
114             return;
115         }
116 
117         FieldInfo[] fieldList = classInfo.getFields();
118         for (int i = 0; i < fieldList.length; i++) {
119             FieldInfo field = fieldList[i];
120             for (Iterator iterator = field.getAnnotations().iterator(); iterator.hasNext();) {
121                 AnnotationInfo annotationInfo = (AnnotationInfo) iterator.next();
122                 if (annotationInfo.getAnnotation() == null) {
123                     continue;
124                 }
125                 if (AnnotationConstants.EXPRESSION.equals(annotationInfo.getName())) {
126                     if (field.getType().getName().equals(DeploymentScope.class.getName())) {
127                         DefinitionParserHelper.createAndAddDeploymentScopeDef(
128                                 field.getName(),
129                                 ((Expression) annotationInfo.getAnnotation()).value(),
130                                 aspectDef.getSystemDefinition()
131                         );
132                     } else {
133                         DefinitionParserHelper.createAndAddPointcutDefToAspectDef(
134                                 field.getName(),
135                                 ((Expression) annotationInfo.getAnnotation()).value(),
136                                 aspectDef
137                         );
138                     }
139                 } else if (AnnotationConstants.INTRODUCE.equals(annotationInfo.getName())) {
140                     DefinitionParserHelper.createAndAddInterfaceIntroductionDefToAspectDef(
141                             ((Introduce) annotationInfo.getAnnotation()).value(),
142                             field.getName(),
143                             field.getType().getName(),
144                             aspectDef
145                     );
146                 }
147             }
148         }
149 
150         // recursive call, next iteration based on super class
151         parseFieldAttributes(classInfo.getSuperclass(), aspectDef);
152     }
153 
154     /***
155      * Parses the method attributes and creates a meta-data representation of them.
156      *
157      * @param classInfo       the class
158      * @param aspectClassName the aspect class name
159      * @param aspectName      the aspect name
160      * @param aspectDef       the aspect definition
161      */
162     private void parseMethodAttributes(final ClassInfo classInfo,
163                                        final String aspectClassName,
164                                        final String aspectName,
165                                        final AspectDefinition aspectDef) {
166         if (classInfo == null) {
167             throw new IllegalArgumentException("class can not be null");
168         }
169         if (aspectClassName == null) {
170             throw new IllegalArgumentException("aspect class name can not be null");
171         }
172         if (aspectName == null) {
173             throw new IllegalArgumentException("aspect name can not be null " + aspectClassName);
174         }
175         if (aspectDef == null) {
176             throw new IllegalArgumentException("aspect definition can not be null");
177         }
178         // get complete method list (includes inherited ones)
179         List methodList = ClassInfoHelper.createMethodList(classInfo);
180 
181         // iterate first on all method to lookup @Expression Pointcut annotations so that they can be resolved
182         parsePointcutAttributes(methodList, aspectDef);
183 
184         // iterate on the advice annotations
185         for (Iterator it = methodList.iterator(); it.hasNext();) {
186             MethodInfo method = (MethodInfo) it.next();
187             try {
188                 // create the advice name out of the class and method name, <classname>.<methodname>
189                 parseAroundAttributes(method, aspectName, aspectClassName, aspectDef);
190                 parseBeforeAttributes(method, aspectName, aspectClassName, aspectDef);
191                 parseAfterAttributes(method, aspectName, aspectClassName, aspectDef);
192             } catch (DefinitionException e) {
193                 System.err.println("AW::WARNING - unable to register advice: " + e.toString());
194                 // TODO AV - better handling of reg issue (f.e. skip the whole aspect, in DocumentParser, based on DefinitionE
195             }
196         }
197     }
198 
199     /***
200      * Parses the method pointcut attributes.
201      *
202      * @param methodList
203      * @param aspectDef
204      */
205     private void parsePointcutAttributes(final List methodList, final AspectDefinition aspectDef) {
206         for (Iterator it = methodList.iterator(); it.hasNext();) {
207             MethodInfo method = (MethodInfo) it.next();
208 
209             // Pointcut with signature
210             List expressionAnnotations = AsmAnnotations.getAnnotations(AnnotationConstants.EXPRESSION, method);
211             for (Iterator iterator = expressionAnnotations.iterator(); iterator.hasNext();) {
212                 Expression annotation = (Expression) iterator.next();
213                 if (annotation != null) {
214                     DefinitionParserHelper.createAndAddPointcutDefToAspectDef(
215                             getAdviceNameAsInSource(method),
216                             annotation.value(), aspectDef
217                     );
218                 }
219             }
220         }
221     }
222 
223     /***
224      * Parses the around attributes.
225      *
226      * @param method
227      * @param aspectName
228      * @param aspectClassName
229      * @param aspectDef
230      */
231     private void parseAroundAttributes(final MethodInfo method,
232                                        final String aspectName,
233                                        final String aspectClassName,
234                                        final AspectDefinition aspectDef) {
235         List aroundAnnotations = AsmAnnotations.getAnnotations(AnnotationConstants.AROUND, method);
236         for (Iterator iterator = aroundAnnotations.iterator(); iterator.hasNext();) {
237             Around aroundAnnotation = (Around) iterator.next();
238             if (aroundAnnotation != null) {
239                 AdviceDefinition adviceDef = DefinitionParserHelper.createAdviceDefinition(
240                         getAdviceNameAsInSource(method),
241                         AdviceType.AROUND,
242                         aroundAnnotation.value(),
243                         null,
244                         aspectName,
245                         aspectClassName,
246                         method,
247                         aspectDef
248                 );
249                 aspectDef.addAroundAdviceDefinition(adviceDef);
250             }
251         }
252     }
253 
254     /***
255      * Parses the before attributes.
256      *
257      * @param method
258      * @param aspectName
259      * @param aspectClassName
260      * @param aspectDef
261      */
262     private void parseBeforeAttributes(final MethodInfo method,
263                                        final String aspectName,
264                                        final String aspectClassName,
265                                        final AspectDefinition aspectDef) {
266         List beforeAnnotations = AsmAnnotations.getAnnotations(AnnotationConstants.BEFORE, method);
267         for (Iterator iterator = beforeAnnotations.iterator(); iterator.hasNext();) {
268             Before beforeAnnotation = (Before) iterator.next();
269             if (beforeAnnotation != null) {
270                 AdviceDefinition adviceDef = DefinitionParserHelper.createAdviceDefinition(
271                         getAdviceNameAsInSource(method),
272                         AdviceType.BEFORE,
273                         beforeAnnotation.value(),
274                         null,
275                         aspectName,
276                         aspectClassName,
277                         method,
278                         aspectDef
279                 );
280                 aspectDef.addBeforeAdviceDefinition(adviceDef);
281             }
282         }
283     }
284 
285     /***
286      * Parses the after attributes.
287      *
288      * @param method
289      * @param aspectName
290      * @param aspectClassName
291      * @param aspectDef
292      */
293     private void parseAfterAttributes(final MethodInfo method,
294                                       final String aspectName,
295                                       final String aspectClassName,
296                                       final AspectDefinition aspectDef) {
297         List afterAnnotations = AsmAnnotations.getAnnotations(AnnotationConstants.AFTER, method);
298         for (Iterator iterator = afterAnnotations.iterator(); iterator.hasNext();) {
299             After annotation = (After) iterator.next();
300             if (annotation != null) {
301                 AdviceDefinition adviceDef = DefinitionParserHelper.createAdviceDefinition(
302                         getAdviceNameAsInSource(method),
303                         AdviceType.AFTER,
304                         annotation.value(),
305                         null,
306                         aspectName,
307                         aspectClassName,
308                         method,
309                         aspectDef
310                 );
311                 aspectDef.addAfterAdviceDefinition(adviceDef);
312             }
313         }
314         afterAnnotations = AsmAnnotations.getAnnotations(AnnotationConstants.AFTER_RETURNING, method);
315         for (Iterator iterator = afterAnnotations.iterator(); iterator.hasNext();) {
316             AfterReturning annotation = (AfterReturning) iterator.next();
317             if (annotation != null) {
318                 AdviceDefinition adviceDef = DefinitionParserHelper.createAdviceDefinition(
319                         getAdviceNameAsInSource(method),
320                         AdviceType.AFTER_RETURNING,
321                         getExpressionElseValue(annotation.value(), annotation.pointcut()),
322                         annotation.type(),
323                         aspectName,
324                         aspectClassName,
325                         method,
326                         aspectDef
327                 );
328                 aspectDef.addAfterAdviceDefinition(adviceDef);
329             }
330         }
331         afterAnnotations = AsmAnnotations.getAnnotations(AnnotationConstants.AFTER_THROWING, method);
332         for (Iterator iterator = afterAnnotations.iterator(); iterator.hasNext();) {
333             AfterThrowing annotation = (AfterThrowing) iterator.next();
334             if (annotation != null) {
335                 AdviceDefinition adviceDef = DefinitionParserHelper.createAdviceDefinition(
336                         getAdviceNameAsInSource(method),
337                         AdviceType.AFTER_THROWING,
338                         getExpressionElseValue(annotation.value(), annotation.pointcut()),
339                         annotation.type(),
340                         aspectName,
341                         aspectClassName,
342                         method,
343                         aspectDef
344                 );
345                 aspectDef.addAfterAdviceDefinition(adviceDef);
346             }
347         }
348         afterAnnotations = AsmAnnotations.getAnnotations(AnnotationConstants.AFTER_FINALLY, method);
349         for (Iterator iterator = afterAnnotations.iterator(); iterator.hasNext();) {
350             AfterFinally annotation = (AfterFinally) iterator.next();
351             if (annotation != null) {
352                 AdviceDefinition adviceDef = DefinitionParserHelper.createAdviceDefinition(
353                         getAdviceNameAsInSource(method),
354                         AdviceType.AFTER_FINALLY,
355                         annotation.value(),
356                         null,
357                         aspectName,
358                         aspectClassName,
359                         method,
360                         aspectDef
361                 );
362                 aspectDef.addAfterAdviceDefinition(adviceDef);
363             }
364         }
365     }
366 
367     /***
368      * Returns the call signature of a Pointcut or advice with signature methodName(paramType paramName, ...) [we ignore
369      * the return type] If there is no parameters, the call signature is not "name()" but just "name"
370      *
371      * @param methodInfo
372      * @return string representation (see javavadoc)
373      */
374     private static String getAdviceNameAsInSource(final MethodInfo methodInfo) {
375         StringBuffer buffer = new StringBuffer(methodInfo.getName());
376         if (methodInfo.getParameterNames() == null
377             || methodInfo.getParameterNames().length != methodInfo.getParameterTypes().length
378             || (methodInfo.getParameterNames().length > 0 && methodInfo.getParameterNames()[0] == null)) {
379             return methodInfo.getName();
380 //            throw new DefinitionException(
381 //                    "Could not access source information for method " + methodInfo.getDeclaringType().getName() + "." +
382 //                    methodInfo.getName() +
383 //                    methodInfo.getSignature() +
384 //                    ". Compile aspects with javac -g."
385 //            );
386         }
387         if (methodInfo.getParameterNames().length > 0) {
388             buffer.append('(');
389             for (int i = 0; i < methodInfo.getParameterNames().length; i++) {
390                 if (i > 0) {
391                     buffer.append(", ");
392                 }
393                 String parameterName = methodInfo.getParameterNames()[i];
394                 buffer.append(methodInfo.getParameterTypes()[i].getName());
395                 buffer.append(' ').append(parameterName);
396             }
397             buffer.append(')');
398         }
399         return buffer.toString();
400     }
401 
402     /***
403      * Handles specific syntax for @AfterXXX annotation, where we can write it using the default "value" element
404      * or instead specify the pointcut using "pointcut", and optionally a "type" element.
405      *
406      * @param value
407      * @param pointcut
408      * @return the one of value or expression which is not null. Both cannot be specified at the same time
409      */
410     public static String getExpressionElseValue(String value, String pointcut) {
411         if (!Strings.isNullOrEmpty(pointcut)) {
412             return pointcut;
413         } else if (!Strings.isNullOrEmpty(value)) {
414             return value;
415         } else {
416             throw new DefinitionException("neither expression nor value had a valid value");
417         }
418     }
419 
420 }