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
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
348
349
350
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) {
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;
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(),
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
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
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 }