001: /**************************************************************************************
002: * Copyright (c) Jonas BonŽr, Alexandre Vasseur. All rights reserved. *
003: * http://aspectwerkz.codehaus.org *
004: * ---------------------------------------------------------------------------------- *
005: * The software in this package is published under the terms of the LGPL license *
006: * a copy of which has been included with this distribution in the license.txt file. *
007: **************************************************************************************/package org.codehaus.aspectwerkz.transform.inlining.deployer;
008:
009: import java.util.Iterator;
010: import java.util.HashSet;
011: import java.util.Set;
012: import java.lang.reflect.Method;
013: import java.io.InputStream;
014:
015: import org.codehaus.aspectwerkz.expression.ExpressionInfo;
016: import org.codehaus.aspectwerkz.definition.AspectDefinition;
017: import org.codehaus.aspectwerkz.definition.SystemDefinition;
018: import org.codehaus.aspectwerkz.definition.SystemDefinitionContainer;
019: import org.codehaus.aspectwerkz.definition.AdviceDefinition;
020: import org.codehaus.aspectwerkz.definition.DeploymentScope;
021: import org.codehaus.aspectwerkz.definition.XmlParser;
022: import org.codehaus.aspectwerkz.definition.DocumentParser;
023: import org.codehaus.aspectwerkz.joinpoint.management.AdviceInfoContainer;
024: import org.codehaus.aspectwerkz.joinpoint.management.JoinPointManager;
025: import org.codehaus.aspectwerkz.annotation.AspectAnnotationParser;
026: import org.codehaus.aspectwerkz.reflect.impl.asm.AsmClassInfo;
027: import org.codehaus.aspectwerkz.reflect.impl.java.JavaClassInfo;
028: import org.codehaus.aspectwerkz.reflect.ClassInfo;
029: import org.codehaus.aspectwerkz.exception.DefinitionException;
030: import org.codehaus.aspectwerkz.transform.inlining.compiler.MatchingJoinPointInfo;
031: import org.codehaus.aspectwerkz.transform.inlining.compiler.JoinPointFactory;
032: import org.codehaus.aspectwerkz.transform.inlining.compiler.CompilationInfo;
033: import org.codehaus.aspectwerkz.transform.inlining.AspectModelManager;
034: import org.objectweb.asm.ClassReader;
035: import org.dom4j.Document;
036: import org.dom4j.DocumentException;
037:
038: /**
039: * Manages deployment and undeployment of aspects. Aspects can be deployed and undeployed into a running system(s).
040: * <p/>
041: * Supports annotation defined and XML defined aspects.
042: *
043: * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
044: */
045: public class Deployer {
046:
047: /**
048: * Deploys an annotation defined aspect.
049: * <p/>
050: * Deploys the aspect in all systems in the class loader that has loaded the aspect class.
051: * <p/>
052: * <b>CAUTION</b>: use a to own risk, the aspect might have a wider scope than your set of instrumented join points,
053: * then the aspect will not be applied to all intended points, to play safe -
054: * use <code>deploy(final Class aspect, final DeploymentScope deploymentScope)</code>
055: *
056: * @param aspect the aspect class
057: * @return a unique deployment handle for this deployment
058: */
059: public static DeploymentHandle deploy(final Class aspect) {
060: return deploy(aspect, DeploymentScope.MATCH_ALL);
061: }
062:
063: /**
064: * Deploys an annotation defined aspect.
065: * <p/>
066: * Deploys the aspect in all systems in the class loader that has loaded the aspect class.
067: * <p/>
068: * <b>CAUTION</b>: use a to own risk, the aspect might have a wider scope than your set of instrumented join points,
069: * then the aspect will not be applied to all intended points, to play safe -
070: * use <code>deploy(final Class aspect, final DeploymentScope preparedPointcut)</code>
071: *
072: * @param aspectClassName the aspect class name
073: * @return a unique deployment handle for this deployment
074: */
075: public static DeploymentHandle deploy(final String aspectClassName) {
076: return deploy(aspectClassName, DeploymentScope.MATCH_ALL);
077: }
078:
079: /**
080: * Deploys an annotation defined aspect.
081: * <p/>
082: * Deploys the aspect in all systems in the class loader that is specified.
083: * <p/>
084: * <b>CAUTION</b>: use a to own risk, the aspect might have a wider scope than your set of instrumented join points,
085: * then the aspect will not be applied to all intended points, to play safe -
086: * use <code>deploy(final Class aspect, final DeploymentScope preparedPointcut)</code>
087: *
088: * @param aspect the aspect class
089: * @param deployLoader
090: * @return a unique deployment handle for this deployment
091: */
092: public static DeploymentHandle deploy(final Class aspect,
093: final ClassLoader deployLoader) {
094: return deploy(aspect, DeploymentScope.MATCH_ALL, deployLoader);
095: }
096:
097: /**
098: * Deploys an annotation defined aspect.
099: * <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,
111: final ClassLoader deployLoader) {
112: return deploy(aspectClassName, DeploymentScope.MATCH_ALL,
113: deployLoader);
114: }
115:
116: /**
117: * Deploys an annotation defined aspect in the scope defined by the prepared pointcut.
118: * <p/>
119: * Deploys the aspect in all systems in the class loader that has loaded the aspect class.
120: *
121: * @param aspect the aspect class
122: * @param deploymentScope
123: * @return a unique deployment handle for this deployment
124: */
125: public static DeploymentHandle deploy(final Class aspect,
126: final DeploymentScope deploymentScope) {
127: return deploy(aspect, deploymentScope, Thread.currentThread()
128: .getContextClassLoader());
129: }
130:
131: /**
132: * Deploys an annotation defined aspect in the scope defined by the prepared pointcut.
133: * <p/>
134: * Deploys the aspect in all systems in the class loader that has loaded the aspect class.
135: *
136: * @param aspectClassName the aspect class name
137: * @param deploymentScope
138: * @return a unique deployment handle for this deployment
139: */
140: public static DeploymentHandle deploy(final String aspectClassName,
141: final DeploymentScope deploymentScope) {
142: return deploy(aspectClassName, deploymentScope, Thread
143: .currentThread().getContextClassLoader());
144: }
145:
146: /**
147: * TODO allow deployment in other systems than virtual system?
148: * <p/>
149: * Deploys an annotation defined aspect in the scope defined by the prepared pointcut.
150: * <p/>
151: * Deploys the aspect in the class loader that is specified.
152: *
153: * @param aspect the aspect class
154: * @param deployLoader the loader to deploy the aspect in
155: * @param deploymentScope the prepared pointcut
156: * @return a unique deployment handle for this deployment
157: */
158: public static DeploymentHandle deploy(final Class aspect,
159: final DeploymentScope deploymentScope,
160: final ClassLoader deployLoader) {
161: if (aspect == null) {
162: throw new IllegalArgumentException(
163: "aspect to deploy can not be null");
164: }
165: if (deploymentScope == null) {
166: throw new IllegalArgumentException(
167: "prepared pointcut can not be null");
168: }
169: if (deployLoader == null) {
170: throw new IllegalArgumentException(
171: "class loader to deploy aspect in can not be null");
172: }
173:
174: final String className = aspect.getName();
175: return deploy(className, deploymentScope, deployLoader);
176:
177: }
178:
179: /**
180: * Deploys an annotation defined aspect in the scope defined by the prepared pointcut.
181: * <p/>
182: * Deploys the aspect in the class loader that is specified.
183: *
184: * @param className
185: * @param deploymentScope
186: * @param deployLoader
187: * @return
188: */
189: public synchronized static DeploymentHandle deploy(
190: final String className,
191: final DeploymentScope deploymentScope,
192: final ClassLoader deployLoader) {
193: logDeployment(className, deployLoader);
194:
195: Class aspectClass = null;
196: try {
197: aspectClass = Class.forName(className, false, deployLoader);
198: } catch (ClassNotFoundException e) {
199: throw new RuntimeException("could not load class ["
200: + className + "] in class loader [" + deployLoader
201: + "]");
202: }
203:
204: final DeploymentHandle deploymentHandle = new DeploymentHandle(
205: aspectClass, deployLoader);
206:
207: final ClassInfo aspectClassInfo = JavaClassInfo
208: .getClassInfo(aspectClass);
209:
210: // create a new aspect def and fill it up with the annotation def from the aspect class
211: final SystemDefinition systemDef = SystemDefinitionContainer
212: .getVirtualDefinitionAt(deployLoader);
213: final AspectDefinition newAspectDef = new AspectDefinition(
214: className, aspectClassInfo, systemDef);
215: final Set newExpressions = getNewExpressionsForAspect(
216: aspectClass, newAspectDef, systemDef, deploymentScope,
217: deploymentHandle);
218:
219: redefine(newExpressions);
220: return deploymentHandle;
221: }
222:
223: /**
224: * Deploys an XML defined aspect in the scope defined by the prepared pointcut.
225: * <p/>
226: * If the aspect class has annotations, those will be read but the XML definition will override the
227: * annotation definition.
228: * <p/>
229: * Deploys the aspect in the class loader that has loaded the aspect.
230: *
231: * @param aspect the aspect class
232: * @param xmlDef
233: * @return
234: */
235: public static DeploymentHandle deploy(final Class aspect,
236: final String xmlDef) {
237: return deploy(aspect, xmlDef, DeploymentScope.MATCH_ALL);
238: }
239:
240: /**
241: * Deploys an XML defined aspect in the scope defined by the prepared pointcut.
242: * <p/>
243: * If the aspect class has annotations, those will be read but the XML definition will override the
244: * annotation definition.
245: * <p/>
246: * Deploys the aspect in the class loader that has loaded the aspect.
247: *
248: * @param aspect the aspect class
249: * @param xmlDef
250: * @param deploymentScope
251: * @return
252: */
253: public static DeploymentHandle deploy(final Class aspect,
254: final String xmlDef, final DeploymentScope deploymentScope) {
255: return deploy(aspect, xmlDef, deploymentScope, aspect
256: .getClassLoader());
257: }
258:
259: /**
260: * Deploys an XML defined aspect in the scope defined by the prepared pointcut.
261: * <p/>
262: * If the aspect class has annotations, those will be read but the XML definition will override the
263: * annotation definition.
264: * <p/>
265: * Deploys the aspect in the class loader that is specified.
266: *
267: * @param aspect the aspect class
268: * @param xmlDef
269: * @param deployLoader
270: * @return
271: */
272: public static DeploymentHandle deploy(final Class aspect,
273: final String xmlDef, final ClassLoader deployLoader) {
274: return deploy(aspect, xmlDef, DeploymentScope.MATCH_ALL,
275: deployLoader);
276: }
277:
278: /**
279: * TODO allow deployment in other systems than virtual system?
280: * <p/>
281: * Deploys an XML defined aspect in the scope defined by the prepared pointcut.
282: * <p/>
283: * If the aspect class has annotations, those will be read but the XML definition will override the
284: * annotation definition.
285: * <p/>
286: * Deploys the aspect in the class loader that is specified.
287: *
288: * @param aspect the aspect class
289: * @param deploymentScope
290: * @param xmlDef
291: * @param deployLoader
292: * @return
293: */
294: public synchronized static DeploymentHandle deploy(
295: final Class aspect, final String xmlDef,
296: final DeploymentScope deploymentScope,
297: final ClassLoader deployLoader) {
298: if (aspect == null) {
299: throw new IllegalArgumentException(
300: "aspect to deploy can not be null");
301: }
302: if (deploymentScope == null) {
303: throw new IllegalArgumentException(
304: "prepared pointcut can not be null");
305: }
306: if (xmlDef == null) {
307: throw new IllegalArgumentException(
308: "xml definition can not be null");
309: }
310: if (deployLoader == null) {
311: throw new IllegalArgumentException(
312: "class loader to deploy aspect in can not be null");
313: }
314: final String className = aspect.getName();
315: logDeployment(className, deployLoader);
316:
317: final DeploymentHandle deploymentHandle = new DeploymentHandle(
318: aspect, deployLoader);
319:
320: final SystemDefinition systemDef = SystemDefinitionContainer
321: .getVirtualDefinitionAt(deployLoader);
322: try {
323: final Document document = XmlParser.createDocument(xmlDef);
324: final AspectDefinition newAspectDef = DocumentParser
325: .parseAspectDefinition(document, systemDef, aspect);
326: final Set newExpressions = getNewExpressionsForAspect(
327: aspect, newAspectDef, systemDef, deploymentScope,
328: deploymentHandle);
329:
330: redefine(newExpressions);
331: } catch (DocumentException e) {
332: throw new DefinitionException(
333: "XML definition for aspect is not well-formed: "
334: + xmlDef);
335: }
336: return deploymentHandle;
337: }
338:
339: /**
340: * Undeploys an aspect from the same loader that has loaded the class.
341: *
342: * @param aspect the aspect class
343: */
344: public static void undeploy(final Class aspect) {
345: undeploy(aspect, aspect.getClassLoader());
346: }
347:
348: /**
349: * Undeploys an aspect from a specific class loader.
350: *
351: * @param aspect the aspect class
352: * @param loader the loader that you want to undeploy the aspect from
353: */
354: public static void undeploy(final Class aspect,
355: final ClassLoader loader) {
356: if (aspect == null) {
357: throw new IllegalArgumentException(
358: "aspect to undeploy can not be null");
359: }
360: if (loader == null) {
361: throw new IllegalArgumentException(
362: "loader to undeploy aspect from can not be null");
363: }
364: undeploy(aspect.getName(), loader);
365: }
366:
367: /**
368: * Undeploys an aspect from a specific class loader.
369: *
370: * @param className the aspect class name
371: * @param loader the loader that you want to undeploy the aspect from
372: */
373: public static void undeploy(final String className,
374: final ClassLoader loader) {
375: logUndeployment(className, loader);
376:
377: //TODO: this one should acquire lock or something
378:
379: // lookup only in the given classloader scope
380: // since the system hierarchy holds reference, they will see the change
381: Set systemDefs = SystemDefinitionContainer
382: .getDefinitionsAt(loader);
383:
384: for (Iterator it = systemDefs.iterator(); it.hasNext();) {
385: SystemDefinition systemDef = (SystemDefinition) it.next();
386: final AspectDefinition aspectDef = systemDef
387: .getAspectDefinition(className);
388: if (aspectDef != null) {
389:
390: final Set newExpressions = new HashSet();
391: for (Iterator it2 = aspectDef.getAdviceDefinitions()
392: .iterator(); it2.hasNext();) {
393: AdviceDefinition adviceDef = (AdviceDefinition) it2
394: .next();
395: ExpressionInfo oldExpression = adviceDef
396: .getExpressionInfo();
397: if (oldExpression == null) { // if null, then already undeployed
398: continue;
399: }
400: adviceDef.setExpressionInfo(null);
401: newExpressions.add(oldExpression);
402: }
403: redefine(newExpressions);
404: }
405: }
406: }
407:
408: /**
409: * Undeploys an aspect in the same way that it has been deployed in in the previous deploy event
410: * defined by the deployment handle.
411: *
412: * @param deploymentHandle the handle to the previous deployment event
413: */
414: public static void undeploy(final DeploymentHandle deploymentHandle) {
415: if (deploymentHandle == null) {
416: throw new IllegalArgumentException(
417: "deployment handle can not be null");
418: }
419:
420: deploymentHandle.revertChanges();
421:
422: final Class aspectClass = deploymentHandle.getAspectClass();
423: if (aspectClass == null) {
424: return; // already undeployed
425: }
426: undeploy(aspectClass);
427: }
428:
429: /**
430: * Redefines all join points that are affected by the system redefinition.
431: *
432: * @param expressions the expressions that will pick out the join points that are affected
433: */
434: private static void redefine(final Set expressions) {
435: final Set allMatchingJoinPoints = new HashSet();
436: for (Iterator itExpr = expressions.iterator(); itExpr.hasNext();) {
437: ExpressionInfo expression = (ExpressionInfo) itExpr.next();
438: Set matchingJoinPoints = JoinPointFactory
439: .getJoinPointsMatching(expression);
440: allMatchingJoinPoints.addAll(matchingJoinPoints);
441: }
442:
443: final ChangeSet changeSet = new ChangeSet();
444: for (Iterator it = allMatchingJoinPoints.iterator(); it
445: .hasNext();) {
446: final MatchingJoinPointInfo joinPointInfo = (MatchingJoinPointInfo) it
447: .next();
448:
449: final CompilationInfo compilationInfo = joinPointInfo
450: .getCompilationInfo();
451: compilationInfo.incrementRedefinitionCounter();
452:
453: changeSet.addElement(new ChangeSet.Element(compilationInfo,
454: joinPointInfo));
455: }
456:
457: doRedefine(changeSet);
458: }
459:
460: /**
461: * Do the redefinition of the existing join point and the compilation of the new join point.
462: *
463: * @param changeSet
464: */
465: private static void doRedefine(final ChangeSet changeSet) {
466: for (Iterator it = changeSet.getElements().iterator(); it
467: .hasNext();) {
468: compileNewJoinPoint((ChangeSet.Element) it.next());
469: }
470: redefineInitialJoinPoints(changeSet);
471: }
472:
473: /**
474: * Compiles a completely new join point instance based on the new redefined model.
475: *
476: * @param changeSetElement the change set item
477: */
478: private static void compileNewJoinPoint(
479: final ChangeSet.Element changeSetElement) {
480: final CompilationInfo compilationInfo = changeSetElement
481: .getCompilationInfo();
482: final MatchingJoinPointInfo joinPointInfo = changeSetElement
483: .getJoinPointInfo();
484: final ClassLoader loader = joinPointInfo.getJoinPointClass()
485: .getClassLoader();
486: final AdviceInfoContainer newAdviceContainer = JoinPointManager
487: .getAdviceInfoContainerForJoinPoint(joinPointInfo
488: .getExpressionContext(), loader);
489: final CompilationInfo.Model redefinedModel = new CompilationInfo.Model(
490: compilationInfo.getInitialModel().getEmittedJoinPoint(), // copy the reference since it is the same
491: newAdviceContainer, compilationInfo
492: .getRedefinitionCounter(), compilationInfo
493: .getInitialModel().getThisClassInfo());
494: JoinPointFactory.compileJoinPointAndAttachToClassLoader(
495: redefinedModel, loader);
496:
497: compilationInfo.setRedefinedModel(redefinedModel);
498: JoinPointFactory.addCompilationInfo(joinPointInfo
499: .getJoinPointClass(), compilationInfo);
500: }
501:
502: /**
503: * Redefines the intial (weaved in) join point to delegate to the newly compiled "real" join point which is
504: * based on the new redefined model.
505: *
506: * @param changeSet the change set
507: */
508: private static void redefineInitialJoinPoints(
509: final ChangeSet changeSet) {
510: // TODO type should be pluggable
511: RedefinerFactory.newRedefiner(RedefinerFactory.Type.HOTSWAP)
512: .redefine(changeSet);
513: }
514:
515: /**
516: * Returns a set with the new expressions for the advice in the aspect to deploy.
517: *
518: * @param aspectClass s * @param newAspectDef
519: * @param systemDef
520: * @param deploymentScope
521: * @param deploymentHandle
522: * @return a set with the new expressions
523: */
524: private static Set getNewExpressionsForAspect(
525: final Class aspectClass,
526: final AspectDefinition newAspectDef,
527: final SystemDefinition systemDef,
528: final DeploymentScope deploymentScope,
529: final DeploymentHandle deploymentHandle) {
530: final ClassLoader aspectLoader = aspectClass.getClassLoader();
531: final String aspectName = aspectClass.getName();
532:
533: final ClassInfo classInfo = AsmClassInfo.getClassInfo(
534: aspectName, aspectLoader);
535:
536: AspectModelManager.defineAspect(classInfo, newAspectDef,
537: aspectLoader);
538:
539: AspectAnnotationParser.parse(classInfo, newAspectDef,
540: aspectLoader);
541:
542: AspectDefinition aspectDef = systemDef
543: .getAspectDefinition(aspectName);
544: if (aspectDef != null) {
545: // if in def already reuse some of the settings that can have been overridded by XML def
546: newAspectDef.setContainerClassName(aspectDef
547: .getContainerClassName());
548: newAspectDef.setDeploymentModel(aspectDef
549: .getDeploymentModel());
550: }
551:
552: systemDef.addAspectOverwriteIfExists(newAspectDef);
553:
554: final Set newExpressions = new HashSet();
555: for (Iterator it2 = newAspectDef.getAdviceDefinitions()
556: .iterator(); it2.hasNext();) {
557: AdviceDefinition adviceDef = (AdviceDefinition) it2.next();
558: ExpressionInfo oldExpression = adviceDef
559: .getExpressionInfo();
560: if (oldExpression == null) {
561: continue;
562: }
563: deploymentHandle.registerDefinitionChange(adviceDef,
564: oldExpression);
565:
566: final ExpressionInfo newExpression = deploymentScope
567: .newExpressionInfo(oldExpression);
568: adviceDef.setExpressionInfo(newExpression);
569: newExpressions.add(newExpression);
570: }
571: return newExpressions;
572: }
573:
574: /**
575: * Imports a class from one class loader to another one.
576: *
577: * @param clazz the class to import
578: * @param toLoader the loader to import to
579: */
580: private static void importClassIntoLoader(final Class clazz,
581: final ClassLoader toLoader) {
582: final ClassLoader fromLoader = clazz.getClassLoader();
583: if (toLoader == fromLoader) {
584: return;
585: }
586: final String className = clazz.getName();
587: try {
588: Class.forName(className, false, toLoader);
589: } catch (ClassNotFoundException cnfe) {
590: try {
591: InputStream stream = null;
592: byte[] bytes;
593: try {
594: stream = fromLoader.getResourceAsStream(className
595: .replace('.', '/')
596: + ".class");
597: bytes = new ClassReader(stream).b;
598: } finally {
599: try {
600: stream.close();
601: } catch (Exception e) {
602: ;
603: }
604: }
605: Class klass = Class.forName("java.lang.ClassLoader",
606: false, toLoader);
607: Method method = klass.getDeclaredMethod("defineClass",
608: new Class[] { String.class, byte[].class,
609: int.class, int.class });
610: method.setAccessible(true);
611: Object[] args = new Object[] { clazz.getName(), bytes,
612: new Integer(0), new Integer(bytes.length) };
613: method.invoke(toLoader, args);
614: method.setAccessible(false);
615: } catch (Exception e) {
616: throw new RuntimeException(new StringBuffer().append(
617: "could not deploy aspect [").append(className)
618: .append("] in class loader [").append(toLoader)
619: .append(']').toString());
620: }
621: }
622: }
623:
624: /**
625: * Logs undeployment.
626: * <p/>
627: * TODO unified way or at least format for logging
628: *
629: * @param className
630: * @param loader
631: */
632: private static void logUndeployment(final String className,
633: final ClassLoader loader) {
634: System.out.println(new StringBuffer().append(
635: "Deployer::INFO - undeploying aspect [").append(
636: className).append("] from class loader [").append(
637: loader).append(']').toString());
638: }
639:
640: /**
641: * Logs deployment.
642: * <p/>
643: * TODO unified way or at least format for logging
644: *
645: * @param className
646: * @param loader
647: */
648: private static void logDeployment(final String className,
649: final ClassLoader loader) {
650: System.out.println(new StringBuffer().append(
651: "Deployer::INFO - deploying aspect [")
652: .append(className).append("] in class loader [")
653: .append(loader).append(']').toString());
654: }
655: }
|