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.inlining.deployer;
9   
10  import java.util.Iterator;
11  import java.util.HashSet;
12  import java.util.Set;
13  import java.lang.reflect.Method;
14  import java.io.InputStream;
15  
16  import org.codehaus.aspectwerkz.expression.ExpressionInfo;
17  import org.codehaus.aspectwerkz.definition.AspectDefinition;
18  import org.codehaus.aspectwerkz.definition.SystemDefinition;
19  import org.codehaus.aspectwerkz.definition.SystemDefinitionContainer;
20  import org.codehaus.aspectwerkz.definition.AdviceDefinition;
21  import org.codehaus.aspectwerkz.definition.DeploymentScope;
22  import org.codehaus.aspectwerkz.definition.XmlParser;
23  import org.codehaus.aspectwerkz.definition.DocumentParser;
24  import org.codehaus.aspectwerkz.joinpoint.management.AdviceInfoContainer;
25  import org.codehaus.aspectwerkz.joinpoint.management.JoinPointManager;
26  import org.codehaus.aspectwerkz.annotation.AspectAnnotationParser;
27  import org.codehaus.aspectwerkz.reflect.impl.asm.AsmClassInfo;
28  import org.codehaus.aspectwerkz.reflect.impl.java.JavaClassInfo;
29  import org.codehaus.aspectwerkz.reflect.ClassInfo;
30  import org.codehaus.aspectwerkz.exception.DefinitionException;
31  import org.codehaus.aspectwerkz.transform.inlining.compiler.MatchingJoinPointInfo;
32  import org.codehaus.aspectwerkz.transform.inlining.compiler.JoinPointFactory;
33  import org.codehaus.aspectwerkz.transform.inlining.compiler.CompilationInfo;
34  import org.codehaus.aspectwerkz.transform.inlining.AspectModelManager;
35  import org.objectweb.asm.ClassReader;
36  import org.dom4j.Document;
37  import org.dom4j.DocumentException;
38  
39  /***
40   * Manages deployment and undeployment of aspects. Aspects can be deployed and undeployed into a running system(s).
41   * <p/>
42   * Supports annotation defined and XML defined aspects.
43   *
44   * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
45   */
46  public class Deployer {
47  
48      /***
49       * Deploys an annotation defined aspect.
50       * <p/>
51       * Deploys the aspect in all systems in the class loader that has loaded the aspect class.
52       * <p/>
53       * <b>CAUTION</b>: use a to own risk, the aspect might have a wider scope than your set of instrumented join points,
54       * then the aspect will not be applied to all intended points, to play safe -
55       * use <code>deploy(final Class aspect, final DeploymentScope deploymentScope)</code>
56       *
57       * @param aspect the aspect class
58       * @return a unique deployment handle for this deployment
59       */
60      public static DeploymentHandle deploy(final Class aspect) {
61          return deploy(aspect, DeploymentScope.MATCH_ALL);
62      }
63  
64      /***
65       * Deploys an annotation defined aspect.
66       * <p/>
67       * Deploys the aspect in all systems in the class loader that has loaded the aspect class.
68       * <p/>
69       * <b>CAUTION</b>: use a to own risk, the aspect might have a wider scope than your set of instrumented join points,
70       * then the aspect will not be applied to all intended points, to play safe -
71       * use <code>deploy(final Class aspect, final DeploymentScope preparedPointcut)</code>
72       *
73       * @param aspectClassName the aspect class name
74       * @return a unique deployment handle for this deployment
75       */
76      public static DeploymentHandle deploy(final String aspectClassName) {
77          return deploy(aspectClassName, DeploymentScope.MATCH_ALL);
78      }
79  
80      /***
81       * Deploys an annotation defined aspect.
82       * <p/>
83       * Deploys the aspect in all systems in the class loader that is specified.
84       * <p/>
85       * <b>CAUTION</b>: use a to own risk, the aspect might have a wider scope than your set of instrumented join points,
86       * then the aspect will not be applied to all intended points, to play safe -
87       * use <code>deploy(final Class aspect, final DeploymentScope preparedPointcut)</code>
88       *
89       * @param aspect       the aspect class
90       * @param deployLoader
91       * @return a unique deployment handle for this deployment
92       */
93      public static DeploymentHandle deploy(final Class aspect, final ClassLoader deployLoader) {
94          return deploy(aspect, DeploymentScope.MATCH_ALL, deployLoader);
95      }
96  
97      /***
98       * Deploys an annotation defined aspect.
99       * <p/>
100      * Deploys the aspect in all systems in the class loader that is specified.
101      * <p/>
102      * <b>CAUTION</b>: use a to own risk, the aspect might have a wider scope than your set of instrumented join points,
103      * then the aspect will not be applied to all intended points, to play safe -
104      * use <code>deploy(final Class aspect, final DeploymentScope preparedPointcut)</code>
105      *
106      * @param aspectClassName the aspect class name
107      * @param deployLoader
108      * @return a unique deployment handle for this deployment
109      */
110     public static DeploymentHandle deploy(final String aspectClassName, final ClassLoader deployLoader) {
111         return deploy(aspectClassName, DeploymentScope.MATCH_ALL, deployLoader);
112     }
113 
114     /***
115      * Deploys an annotation defined aspect in the scope defined by the prepared pointcut.
116      * <p/>
117      * Deploys the aspect in all systems in the class loader that has loaded the aspect class.
118      *
119      * @param aspect          the aspect class
120      * @param deploymentScope
121      * @return a unique deployment handle for this deployment
122      */
123     public static DeploymentHandle deploy(final Class aspect, final DeploymentScope deploymentScope) {
124         return deploy(aspect, deploymentScope, Thread.currentThread().getContextClassLoader());
125     }
126 
127     /***
128      * Deploys an annotation defined aspect in the scope defined by the prepared pointcut.
129      * <p/>
130      * Deploys the aspect in all systems in the class loader that has loaded the aspect class.
131      *
132      * @param aspectClassName the aspect class name
133      * @param deploymentScope
134      * @return a unique deployment handle for this deployment
135      */
136     public static DeploymentHandle deploy(final String aspectClassName, final DeploymentScope deploymentScope) {
137         return deploy(aspectClassName, deploymentScope, Thread.currentThread().getContextClassLoader());
138     }
139 
140     /***
141      * TODO allow deployment in other systems than virtual system?
142      * <p/>
143      * Deploys an annotation defined aspect in the scope defined by the prepared pointcut.
144      * <p/>
145      * Deploys the aspect in the class loader that is specified.
146      *
147      * @param aspect          the aspect class
148      * @param deployLoader    the loader to deploy the aspect in
149      * @param deploymentScope the prepared pointcut
150      * @return a unique deployment handle for this deployment
151      */
152     public static DeploymentHandle deploy(final Class aspect,
153                                           final DeploymentScope deploymentScope,
154                                           final ClassLoader deployLoader) {
155         if (aspect == null) {
156             throw new IllegalArgumentException("aspect to deploy can not be null");
157         }
158         if (deploymentScope == null) {
159             throw new IllegalArgumentException("prepared pointcut can not be null");
160         }
161         if (deployLoader == null) {
162             throw new IllegalArgumentException("class loader to deploy aspect in can not be null");
163         }
164 
165         final String className = aspect.getName();
166         return deploy(className, deploymentScope, deployLoader);
167 
168     }
169 
170     /***
171      * Deploys an annotation defined aspect in the scope defined by the prepared pointcut.
172      * <p/>
173      * Deploys the aspect in the class loader that is specified.
174      *
175      * @param className
176      * @param deploymentScope
177      * @param deployLoader
178      * @return
179      */
180     public synchronized static DeploymentHandle deploy(final String className,
181                                                        final DeploymentScope deploymentScope,
182                                                        final ClassLoader deployLoader) {
183         logDeployment(className, deployLoader);
184 
185         Class aspectClass = null;
186         try {
187             aspectClass = Class.forName(className, false, deployLoader);
188         } catch (ClassNotFoundException e) {
189             throw new RuntimeException(
190                     "could not load class [" + className + "] in class loader [" + deployLoader + "]"
191             );
192         }
193 
194         final DeploymentHandle deploymentHandle = new DeploymentHandle(aspectClass, deployLoader);
195 
196         final ClassInfo aspectClassInfo = JavaClassInfo.getClassInfo(aspectClass);
197 
198         // create a new aspect def and fill it up with the annotation def from the aspect class
199         final SystemDefinition systemDef = SystemDefinitionContainer.getVirtualDefinitionAt(deployLoader);
200         final AspectDefinition newAspectDef = new AspectDefinition(className, aspectClassInfo, systemDef);
201         final Set newExpressions = getNewExpressionsForAspect(
202                 aspectClass, newAspectDef, systemDef, deploymentScope, deploymentHandle
203         );
204 
205         redefine(newExpressions);
206         return deploymentHandle;
207     }
208 
209     /***
210      * Deploys an XML defined aspect in the scope defined by the prepared pointcut.
211      * <p/>
212      * If the aspect class has annotations, those will be read but the XML definition will override the
213      * annotation definition.
214      * <p/>
215      * Deploys the aspect in the class loader that has loaded the aspect.
216      *
217      * @param aspect the aspect class
218      * @param xmlDef
219      * @return
220      */
221     public static DeploymentHandle deploy(final Class aspect, final String xmlDef) {
222         return deploy(aspect, xmlDef, DeploymentScope.MATCH_ALL);
223     }
224 
225     /***
226      * Deploys an XML defined aspect in the scope defined by the prepared pointcut.
227      * <p/>
228      * If the aspect class has annotations, those will be read but the XML definition will override the
229      * annotation definition.
230      * <p/>
231      * Deploys the aspect in the class loader that has loaded the aspect.
232      *
233      * @param aspect          the aspect class
234      * @param xmlDef
235      * @param deploymentScope
236      * @return
237      */
238     public static DeploymentHandle deploy(final Class aspect,
239                                           final String xmlDef,
240                                           final DeploymentScope deploymentScope) {
241         return deploy(aspect, xmlDef, deploymentScope, aspect.getClassLoader());
242     }
243 
244     /***
245      * Deploys an XML defined aspect in the scope defined by the prepared pointcut.
246      * <p/>
247      * If the aspect class has annotations, those will be read but the XML definition will override the
248      * annotation definition.
249      * <p/>
250      * Deploys the aspect in the class loader that is specified.
251      *
252      * @param aspect       the aspect class
253      * @param xmlDef
254      * @param deployLoader
255      * @return
256      */
257     public static DeploymentHandle deploy(final Class aspect, final String xmlDef, final ClassLoader deployLoader) {
258         return deploy(aspect, xmlDef, DeploymentScope.MATCH_ALL, deployLoader);
259     }
260 
261     /***
262      * TODO allow deployment in other systems than virtual system?
263      * <p/>
264      * Deploys an XML defined aspect in the scope defined by the prepared pointcut.
265      * <p/>
266      * If the aspect class has annotations, those will be read but the XML definition will override the
267      * annotation definition.
268      * <p/>
269      * Deploys the aspect in the class loader that is specified.
270      *
271      * @param aspect          the aspect class
272      * @param deploymentScope
273      * @param xmlDef
274      * @param deployLoader
275      * @return
276      */
277     public synchronized static DeploymentHandle deploy(final Class aspect,
278                                                        final String xmlDef,
279                                                        final DeploymentScope deploymentScope,
280                                                        final ClassLoader deployLoader) {
281         if (aspect == null) {
282             throw new IllegalArgumentException("aspect to deploy can not be null");
283         }
284         if (deploymentScope == null) {
285             throw new IllegalArgumentException("prepared pointcut can not be null");
286         }
287         if (xmlDef == null) {
288             throw new IllegalArgumentException("xml definition can not be null");
289         }
290         if (deployLoader == null) {
291             throw new IllegalArgumentException("class loader to deploy aspect in can not be null");
292         }
293         final String className = aspect.getName();
294         logDeployment(className, deployLoader);
295 
296         final DeploymentHandle deploymentHandle = new DeploymentHandle(aspect, deployLoader);
297 
298         final SystemDefinition systemDef = SystemDefinitionContainer.getVirtualDefinitionAt(deployLoader);
299         try {
300             final Document document = XmlParser.createDocument(xmlDef);
301             final AspectDefinition newAspectDef = DocumentParser.parseAspectDefinition(document, systemDef, aspect);
302             final Set newExpressions = getNewExpressionsForAspect(
303                     aspect, newAspectDef, systemDef, deploymentScope, deploymentHandle
304             );
305 
306             redefine(newExpressions);
307         } catch (DocumentException e) {
308             throw new DefinitionException("XML definition for aspect is not well-formed: " + xmlDef);
309         }
310         return deploymentHandle;
311     }
312 
313     /***
314      * Undeploys an aspect from the same loader that has loaded the class.
315      *
316      * @param aspect the aspect class
317      */
318     public static void undeploy(final Class aspect) {
319         undeploy(aspect, aspect.getClassLoader());
320     }
321 
322     /***
323      * Undeploys an aspect from a specific class loader.
324      *
325      * @param aspect the aspect class
326      * @param loader the loader that you want to undeploy the aspect from
327      */
328     public static void undeploy(final Class aspect, final ClassLoader loader) {
329         if (aspect == null) {
330             throw new IllegalArgumentException("aspect to undeploy can not be null");
331         }
332         if (loader == null) {
333             throw new IllegalArgumentException("loader to undeploy aspect from can not be null");
334         }
335         undeploy(aspect.getName(), loader);
336     }
337 
338     /***
339      * Undeploys an aspect from a specific class loader.
340      *
341      * @param className the aspect class name
342      * @param loader    the loader that you want to undeploy the aspect from
343      */
344     public static void undeploy(final String className, final ClassLoader loader) {
345         logUndeployment(className, loader);
346 
347         //TODO: this one should acquire lock or something
348 
349         // lookup only in the given classloader scope
350         // since the system hierarchy holds reference, they will see the change
351         Set systemDefs = SystemDefinitionContainer.getDefinitionsAt(loader);
352 
353         for (Iterator it = systemDefs.iterator(); it.hasNext();) {
354             SystemDefinition systemDef = (SystemDefinition) it.next();
355             final AspectDefinition aspectDef = systemDef.getAspectDefinition(className);
356             if (aspectDef != null) {
357 
358                 final Set newExpressions = new HashSet();
359                 for (Iterator it2 = aspectDef.getAdviceDefinitions().iterator(); it2.hasNext();) {
360                     AdviceDefinition adviceDef = (AdviceDefinition) it2.next();
361                     ExpressionInfo oldExpression = adviceDef.getExpressionInfo();
362                     if (oldExpression == null) { // if null, then already undeployed
363                         continue;
364                     }
365                     adviceDef.setExpressionInfo(null);
366                     newExpressions.add(oldExpression);
367                 }
368                 redefine(newExpressions);
369             }
370         }
371     }
372 
373     /***
374      * Undeploys an aspect in the same way that it has been deployed in in the previous deploy event
375      * defined by the deployment handle.
376      *
377      * @param deploymentHandle the handle to the previous deployment event
378      */
379     public static void undeploy(final DeploymentHandle deploymentHandle) {
380         if (deploymentHandle == null) {
381             throw new IllegalArgumentException("deployment handle can not be null");
382         }
383 
384         deploymentHandle.revertChanges();
385 
386         final Class aspectClass = deploymentHandle.getAspectClass();
387         if (aspectClass == null) {
388             return; // already undeployed
389         }
390         undeploy(aspectClass);
391     }
392 
393     /***
394      * Redefines all join points that are affected by the system redefinition.
395      *
396      * @param expressions the expressions that will pick out the join points that are affected
397      */
398     private static void redefine(final Set expressions) {
399         final Set allMatchingJoinPoints = new HashSet();
400         for (Iterator itExpr = expressions.iterator(); itExpr.hasNext();) {
401             ExpressionInfo expression = (ExpressionInfo) itExpr.next();
402             Set matchingJoinPoints = JoinPointFactory.getJoinPointsMatching(expression);
403             allMatchingJoinPoints.addAll(matchingJoinPoints);
404         }
405 
406         final ChangeSet changeSet = new ChangeSet();
407         for (Iterator it = allMatchingJoinPoints.iterator(); it.hasNext();) {
408             final MatchingJoinPointInfo joinPointInfo = (MatchingJoinPointInfo) it.next();
409 
410             final CompilationInfo compilationInfo = joinPointInfo.getCompilationInfo();
411             compilationInfo.incrementRedefinitionCounter();
412 
413             changeSet.addElement(new ChangeSet.Element(compilationInfo, joinPointInfo));
414         }
415 
416         doRedefine(changeSet);
417     }
418 
419     /***
420      * Do the redefinition of the existing join point and the compilation of the new join point.
421      *
422      * @param changeSet
423      */
424     private static void doRedefine(final ChangeSet changeSet) {
425         for (Iterator it = changeSet.getElements().iterator(); it.hasNext();) {
426             compileNewJoinPoint((ChangeSet.Element) it.next());
427         }
428         redefineInitialJoinPoints(changeSet);
429     }
430 
431     /***
432      * Compiles a completely new join point instance based on the new redefined model.
433      *
434      * @param changeSetElement the change set item
435      */
436     private static void compileNewJoinPoint(final ChangeSet.Element changeSetElement) {
437         final CompilationInfo compilationInfo = changeSetElement.getCompilationInfo();
438         final MatchingJoinPointInfo joinPointInfo = changeSetElement.getJoinPointInfo();
439         final ClassLoader loader = joinPointInfo.getJoinPointClass().getClassLoader();
440         final AdviceInfoContainer newAdviceContainer = JoinPointManager.getAdviceInfoContainerForJoinPoint(
441                 joinPointInfo.getExpressionContext(),
442                 loader
443         );
444         final CompilationInfo.Model redefinedModel = new CompilationInfo.Model(
445                 compilationInfo.getInitialModel().getEmittedJoinPoint(), // copy the reference since it is the same
446                 newAdviceContainer,
447                 compilationInfo.getRedefinitionCounter(),
448                 compilationInfo.getInitialModel().getThisClassInfo()
449         );
450         JoinPointFactory.compileJoinPointAndAttachToClassLoader(redefinedModel, loader);
451 
452         compilationInfo.setRedefinedModel(redefinedModel);
453         JoinPointFactory.addCompilationInfo(joinPointInfo.getJoinPointClass(), compilationInfo);
454     }
455 
456     /***
457      * Redefines the intial (weaved in) join point to delegate to the newly compiled "real" join point which is
458      * based on the new redefined model.
459      *
460      * @param changeSet the change set
461      */
462     private static void redefineInitialJoinPoints(final ChangeSet changeSet) {
463         // TODO type should be pluggable
464         RedefinerFactory.newRedefiner(RedefinerFactory.Type.HOTSWAP).redefine(changeSet);
465     }
466 
467     /***
468      * Returns a set with the new expressions for the advice in the aspect to deploy.
469      *
470      * @param aspectClass      s     * @param newAspectDef
471      * @param systemDef
472      * @param deploymentScope
473      * @param deploymentHandle
474      * @return a set with the new expressions
475      */
476     private static Set getNewExpressionsForAspect(final Class aspectClass,
477                                                   final AspectDefinition newAspectDef,
478                                                   final SystemDefinition systemDef,
479                                                   final DeploymentScope deploymentScope,
480                                                   final DeploymentHandle deploymentHandle) {
481         final ClassLoader aspectLoader = aspectClass.getClassLoader();
482         final String aspectName = aspectClass.getName();
483 
484         final ClassInfo classInfo = AsmClassInfo.getClassInfo(aspectName, aspectLoader);
485 
486         AspectModelManager.defineAspect(classInfo, newAspectDef, aspectLoader);
487 
488         AspectAnnotationParser.parse(classInfo, newAspectDef, aspectLoader);
489 
490         AspectDefinition aspectDef = systemDef.getAspectDefinition(aspectName);
491         if (aspectDef != null) {
492             // if in def already reuse some of the settings that can have been overridded by XML def
493             newAspectDef.setContainerClassName(aspectDef.getContainerClassName());
494             newAspectDef.setDeploymentModel(aspectDef.getDeploymentModel());
495         }
496 
497         systemDef.addAspectOverwriteIfExists(newAspectDef);
498 
499         final Set newExpressions = new HashSet();
500         for (Iterator it2 = newAspectDef.getAdviceDefinitions().iterator(); it2.hasNext();) {
501             AdviceDefinition adviceDef = (AdviceDefinition) it2.next();
502             ExpressionInfo oldExpression = adviceDef.getExpressionInfo();
503             if (oldExpression == null) {
504                 continue;
505             }
506             deploymentHandle.registerDefinitionChange(adviceDef, oldExpression);
507 
508             final ExpressionInfo newExpression = deploymentScope.newExpressionInfo(oldExpression);
509             adviceDef.setExpressionInfo(newExpression);
510             newExpressions.add(newExpression);
511         }
512         return newExpressions;
513     }
514 
515     /***
516      * Imports a class from one class loader to another one.
517      *
518      * @param clazz    the class to import
519      * @param toLoader the loader to import to
520      */
521     private static void importClassIntoLoader(final Class clazz, final ClassLoader toLoader) {
522         final ClassLoader fromLoader = clazz.getClassLoader();
523         if (toLoader == fromLoader) {
524             return;
525         }
526         final String className = clazz.getName();
527         try {
528             Class.forName(className, false, toLoader);
529         } catch (ClassNotFoundException cnfe) {
530             try {
531                 InputStream stream = null;
532                 byte[] bytes;
533                 try {
534                     stream = fromLoader.getResourceAsStream(className.replace('.', '/') + ".class");
535                     bytes = new ClassReader(stream).b;
536                 } finally {
537                     try {
538                         stream.close();
539                     } catch (Exception e) {
540                         ;
541                     }
542                 }
543                 Class klass = Class.forName("java.lang.ClassLoader", false, toLoader);
544                 Method method = klass.getDeclaredMethod(
545                         "defineClass",
546                         new Class[]{String.class, byte[].class, int.class, int.class}
547                 );
548                 method.setAccessible(true);
549                 Object[] args = new Object[]{
550                     clazz.getName(), bytes, new Integer(0), new Integer(bytes.length)
551                 };
552                 method.invoke(toLoader, args);
553                 method.setAccessible(false);
554             } catch (Exception e) {
555                 throw new RuntimeException(
556                         new StringBuffer().append("could not deploy aspect [").
557                         append(className).append("] in class loader [").append(toLoader)
558                         .append(']').toString()
559                 );
560             }
561         }
562     }
563 
564     /***
565      * Logs undeployment.
566      * <p/>
567      * TODO unified way or at least format for logging
568      *
569      * @param className
570      * @param loader
571      */
572     private static void logUndeployment(final String className, final ClassLoader loader) {
573         System.out.println(
574                 new StringBuffer().append("Deployer::INFO - undeploying aspect [").
575                 append(className).append("] from class loader [").
576                 append(loader).append(']').toString()
577         );
578     }
579 
580     /***
581      * Logs deployment.
582      * <p/>
583      * TODO unified way or at least format for logging
584      *
585      * @param className
586      * @param loader
587      */
588     private static void logDeployment(final String className, final ClassLoader loader) {
589         System.out.println(
590                 new StringBuffer().append("Deployer::INFO - deploying aspect [").
591                 append(className).append("] in class loader [").
592                 append(loader).append(']').toString()
593         );
594     }
595 }