/************************************************************************************** * Copyright (c) Jonas Bon�r, Alexandre Vasseur. All rights reserved. * * http://aspectwerkz.codehaus.org * * ---------------------------------------------------------------------------------- * * The software in this package is published under the terms of the LGPL license * * a copy of which has been included with this distribution in the license.txt file. * **************************************************************************************/ package org.codehaus.aspectwerkz.transform.inlining.deployer; import java.util.Iterator; import java.util.HashSet; import java.util.Set; import java.lang.reflect.Method; import java.io.InputStream; import org.codehaus.aspectwerkz.expression.ExpressionInfo; import org.codehaus.aspectwerkz.definition.AspectDefinition; import org.codehaus.aspectwerkz.definition.SystemDefinition; import org.codehaus.aspectwerkz.definition.SystemDefinitionContainer; import org.codehaus.aspectwerkz.definition.AdviceDefinition; import org.codehaus.aspectwerkz.definition.DeploymentScope; import org.codehaus.aspectwerkz.definition.XmlParser; import org.codehaus.aspectwerkz.definition.DocumentParser; import org.codehaus.aspectwerkz.joinpoint.management.AdviceInfoContainer; import org.codehaus.aspectwerkz.joinpoint.management.JoinPointManager; import org.codehaus.aspectwerkz.annotation.AspectAnnotationParser; import org.codehaus.aspectwerkz.reflect.impl.asm.AsmClassInfo; import org.codehaus.aspectwerkz.reflect.impl.java.JavaClassInfo; import org.codehaus.aspectwerkz.reflect.ClassInfo; import org.codehaus.aspectwerkz.exception.DefinitionException; import org.codehaus.aspectwerkz.transform.inlining.compiler.MatchingJoinPointInfo; import org.codehaus.aspectwerkz.transform.inlining.compiler.JoinPointFactory; import org.codehaus.aspectwerkz.transform.inlining.compiler.CompilationInfo; import org.codehaus.aspectwerkz.transform.inlining.AspectModelManager; import org.codehaus.aspectwerkz.DeploymentModel; import org.objectweb.asm.ClassReader; import org.dom4j.Document; import org.dom4j.DocumentException; /** * Manages deployment and undeployment of aspects. Aspects can be deployed and undeployed into a running system(s). * <p/> * Supports annotation defined and XML defined aspects. * * @author <a href="mailto:jboner@codehaus.org">Jonas Bon�r </a> */ public class Deployer { /** * Deploys an annotation defined aspect. * <p/> * Deploys the aspect in all systems in the class loader that has loaded the aspect class. * <p/> * <b>CAUTION</b>: use a to own risk, the aspect might have a wider scope than your set of instrumented join points, * then the aspect will not be applied to all intended points, to play safe - * use <code>deploy(final Class aspect, final DeploymentScope deploymentScope)</code> * * @param aspect the aspect class * @return a unique deployment handle for this deployment */ public static DeploymentHandle deploy(final Class aspect) { return deploy(aspect, DeploymentScope.MATCH_ALL); } /** * Deploys an annotation defined aspect. * <p/> * Deploys the aspect in all systems in the class loader that has loaded the aspect class. * <p/> * <b>CAUTION</b>: use a to own risk, the aspect might have a wider scope than your set of instrumented join points, * then the aspect will not be applied to all intended points, to play safe - * use <code>deploy(final Class aspect, final DeploymentScope preparedPointcut)</code> * * @param aspectClassName the aspect class name * @return a unique deployment handle for this deployment */ public static DeploymentHandle deploy(final String aspectClassName) { return deploy(aspectClassName, DeploymentScope.MATCH_ALL); } /** * Deploys an annotation defined aspect. * <p/> * Deploys the aspect in all systems in the class loader that is specified. * <p/> * <b>CAUTION</b>: use a to own risk, the aspect might have a wider scope than your set of instrumented join points, * then the aspect will not be applied to all intended points, to play safe - * use <code>deploy(final Class aspect, final DeploymentScope preparedPointcut)</code> * * @param aspect the aspect class * @param deployLoader * @return a unique deployment handle for this deployment */ public static DeploymentHandle deploy(final Class aspect, final ClassLoader deployLoader) { return deploy(aspect, DeploymentScope.MATCH_ALL, deployLoader); } /** * Deploys an annotation defined aspect. * <p/> * Deploys the aspect in all systems in the class loader that is specified. * <p/> * <b>CAUTION</b>: use a to own risk, the aspect might have a wider scope than your set of instrumented join points, * then the aspect will not be applied to all intended points, to play safe - * use <code>deploy(final Class aspect, final DeploymentScope preparedPointcut)</code> * * @param aspectClassName the aspect class name * @param deployLoader * @return a unique deployment handle for this deployment */ public static DeploymentHandle deploy(final String aspectClassName, final ClassLoader deployLoader) { return deploy(aspectClassName, DeploymentScope.MATCH_ALL, deployLoader); } /** * Deploys an annotation defined aspect in the scope defined by the prepared pointcut. * <p/> * Deploys the aspect in all systems in the class loader that has loaded the aspect class. * * @param aspect the aspect class * @param deploymentScope * @return a unique deployment handle for this deployment */ public static DeploymentHandle deploy(final Class aspect, final DeploymentScope deploymentScope) { return deploy(aspect, deploymentScope, Thread.currentThread().getContextClassLoader()); } /** * Deploys an annotation defined aspect in the scope defined by the prepared pointcut. * <p/> * Deploys the aspect in all systems in the class loader that has loaded the aspect class. * * @param aspectClassName the aspect class name * @param deploymentScope * @return a unique deployment handle for this deployment */ public static DeploymentHandle deploy(final String aspectClassName, final DeploymentScope deploymentScope) { return deploy(aspectClassName, deploymentScope, Thread.currentThread().getContextClassLoader()); } /** * TODO allow deployment in other systems than virtual system? * <p/> * Deploys an annotation defined aspect in the scope defined by the prepared pointcut. * <p/> * Deploys the aspect in the class loader that is specified. * * @param aspect the aspect class * @param deployLoader the loader to deploy the aspect in * @param deploymentScope the prepared pointcut * @return a unique deployment handle for this deployment */ public static DeploymentHandle deploy(final Class aspect, final DeploymentScope deploymentScope, final ClassLoader deployLoader) { if (aspect == null) { throw new IllegalArgumentException("aspect to deploy can not be null"); } if (deploymentScope == null) { throw new IllegalArgumentException("prepared pointcut can not be null"); } if (deployLoader == null) { throw new IllegalArgumentException("class loader to deploy aspect in can not be null"); } final String className = aspect.getName(); return deploy(className, deploymentScope, deployLoader); } /** * Deploys an annotation defined aspect in the scope defined by the prepared pointcut. * <p/> * Deploys the aspect in the class loader that is specified. * * @param className * @param deploymentScope * @param deployLoader * @return */ public synchronized static DeploymentHandle deploy(final String className, final DeploymentScope deploymentScope, final ClassLoader deployLoader) { logDeployment(className, deployLoader); Class aspectClass = null; try { aspectClass = Class.forName(className, false, deployLoader); } catch (ClassNotFoundException e) { throw new RuntimeException( "could not load class [" + className + "] in class loader [" + deployLoader + "]" ); } final DeploymentHandle deploymentHandle = new DeploymentHandle(aspectClass, deployLoader); final ClassInfo aspectClassInfo = JavaClassInfo.getClassInfo(aspectClass); // create a new aspect def and fill it up with the annotation def from the aspect class final SystemDefinition systemDef = SystemDefinitionContainer.getVirtualDefinitionAt(deployLoader); final AspectDefinition newAspectDef = new AspectDefinition(className, aspectClassInfo, systemDef); final Set newExpressions = getNewExpressionsForAspect( aspectClass, newAspectDef, systemDef, deploymentScope, deploymentHandle ); redefine(newExpressions); return deploymentHandle; } /** * Deploys an XML defined aspect in the scope defined by the prepared pointcut. * <p/> * If the aspect class has annotations, those will be read but the XML definition will override the * annotation definition. * <p/> * Deploys the aspect in the class loader that has loaded the aspect. * * @param aspect the aspect class * @param xmlDef * @return */ public static DeploymentHandle deploy(final Class aspect, final String xmlDef) { return deploy(aspect, xmlDef, DeploymentScope.MATCH_ALL); } /** * Deploys an XML defined aspect in the scope defined by the prepared pointcut. * <p/> * If the aspect class has annotations, those will be read but the XML definition will override the * annotation definition. * <p/> * Deploys the aspect in the class loader that has loaded the aspect. * * @param aspect the aspect class * @param xmlDef * @param deploymentScope * @return */ public static DeploymentHandle deploy(final Class aspect, final String xmlDef, final DeploymentScope deploymentScope) { return deploy(aspect, xmlDef, deploymentScope, aspect.getClassLoader()); } /** * Deploys an XML defined aspect in the scope defined by the prepared pointcut. * <p/> * If the aspect class has annotations, those will be read but the XML definition will override the * annotation definition. * <p/> * Deploys the aspect in the class loader that is specified. * * @param aspect the aspect class * @param xmlDef * @param deployLoader * @return */ public static DeploymentHandle deploy(final Class aspect, final String xmlDef, final ClassLoader deployLoader) { return deploy(aspect, xmlDef, DeploymentScope.MATCH_ALL, deployLoader); } /** * TODO allow deployment in other systems than virtual system? * <p/> * Deploys an XML defined aspect in the scope defined by the prepared pointcut. * <p/> * If the aspect class has annotations, those will be read but the XML definition will override the * annotation definition. * <p/> * Deploys the aspect in the class loader that is specified. * * @param aspect the aspect class * @param deploymentScope * @param xmlDef * @param deployLoader * @return */ public synchronized static DeploymentHandle deploy(final Class aspect, final String xmlDef, final DeploymentScope deploymentScope, final ClassLoader deployLoader) { if (aspect == null) { throw new IllegalArgumentException("aspect to deploy can not be null"); } if (deploymentScope == null) { throw new IllegalArgumentException("prepared pointcut can not be null"); } if (xmlDef == null) { throw new IllegalArgumentException("xml definition can not be null"); } if (deployLoader == null) { throw new IllegalArgumentException("class loader to deploy aspect in can not be null"); } final String className = aspect.getName(); logDeployment(className, deployLoader); final DeploymentHandle deploymentHandle = new DeploymentHandle(aspect, deployLoader); final SystemDefinition systemDef = SystemDefinitionContainer.getVirtualDefinitionAt(deployLoader); try { final Document document = XmlParser.createDocument(xmlDef); final AspectDefinition newAspectDef = DocumentParser.parseAspectDefinition(document, systemDef, aspect); final Set newExpressions = getNewExpressionsForAspect( aspect, newAspectDef, systemDef, deploymentScope, deploymentHandle ); redefine(newExpressions); } catch (DocumentException e) { throw new DefinitionException("XML definition for aspect is not well-formed: " + xmlDef); } return deploymentHandle; } /** * Undeploys an aspect from the same loader that has loaded the class. * * @param aspect the aspect class */ public static void undeploy(final Class aspect) { undeploy(aspect, aspect.getClassLoader()); } /** * Undeploys an aspect from a specific class loader. * * @param aspect the aspect class * @param loader the loader that you want to undeploy the aspect from */ public static void undeploy(final Class aspect, final ClassLoader loader) { if (aspect == null) { throw new IllegalArgumentException("aspect to undeploy can not be null"); } if (loader == null) { throw new IllegalArgumentException("loader to undeploy aspect from can not be null"); } undeploy(aspect.getName(), loader); } /** * Undeploys an aspect from a specific class loader. * * @param className the aspect class name * @param loader the loader that you want to undeploy the aspect from */ public static void undeploy(final String className, final ClassLoader loader) { logUndeployment(className, loader); //TODO: this one should acquire lock or something // lookup only in the given classloader scope // since the system hierarchy holds reference, they will see the change Set systemDefs = SystemDefinitionContainer.getDefinitionsAt(loader); for (Iterator it = systemDefs.iterator(); it.hasNext();) { SystemDefinition systemDef = (SystemDefinition) it.next(); final AspectDefinition aspectDef = systemDef.getAspectDefinition(className); if (aspectDef != null) { final Set newExpressions = new HashSet(); for (Iterator it2 = aspectDef.getAdviceDefinitions().iterator(); it2.hasNext();) { AdviceDefinition adviceDef = (AdviceDefinition) it2.next(); ExpressionInfo oldExpression = adviceDef.getExpressionInfo(); if (oldExpression == null) { // if null, then already undeployed continue; } adviceDef.setExpressionInfo(null); newExpressions.add(oldExpression); } redefine(newExpressions); } } } /** * Undeploys an aspect in the same way that it has been deployed in in the previous deploy event * defined by the deployment handle. * * @param deploymentHandle the handle to the previous deployment event */ public static void undeploy(final DeploymentHandle deploymentHandle) { if (deploymentHandle == null) { throw new IllegalArgumentException("deployment handle can not be null"); } deploymentHandle.revertChanges(); final Class aspectClass = deploymentHandle.getAspectClass(); if (aspectClass == null) { return; // already undeployed } undeploy(aspectClass); } /** * Redefines all join points that are affected by the system redefinition. * * @param expressions the expressions that will pick out the join points that are affected */ private static void redefine(final Set expressions) { final Set allMatchingJoinPoints = new HashSet(); for (Iterator itExpr = expressions.iterator(); itExpr.hasNext();) { ExpressionInfo expression = (ExpressionInfo) itExpr.next(); Set matchingJoinPoints = JoinPointFactory.getJoinPointsMatching(expression); allMatchingJoinPoints.addAll(matchingJoinPoints); } final ChangeSet changeSet = new ChangeSet(); for (Iterator it = allMatchingJoinPoints.iterator(); it.hasNext();) { final MatchingJoinPointInfo joinPointInfo = (MatchingJoinPointInfo) it.next(); final CompilationInfo compilationInfo = joinPointInfo.getCompilationInfo(); compilationInfo.incrementRedefinitionCounter(); changeSet.addElement(new ChangeSet.Element(compilationInfo, joinPointInfo)); } doRedefine(changeSet); } /** * Do the redefinition of the existing join point and the compilation of the new join point. * * @param changeSet */ private static void doRedefine(final ChangeSet changeSet) { for (Iterator it = changeSet.getElements().iterator(); it.hasNext();) { compileNewJoinPoint((ChangeSet.Element) it.next()); } redefineInitialJoinPoints(changeSet); } /** * Compiles a completely new join point instance based on the new redefined model. * * @param changeSetElement the change set item */ private static void compileNewJoinPoint(final ChangeSet.Element changeSetElement) { final CompilationInfo compilationInfo = changeSetElement.getCompilationInfo(); final MatchingJoinPointInfo joinPointInfo = changeSetElement.getJoinPointInfo(); final ClassLoader loader = joinPointInfo.getJoinPointClass().getClassLoader(); final AdviceInfoContainer newAdviceContainer = JoinPointManager.getAdviceInfoContainerForJoinPoint( joinPointInfo.getExpressionContext(), loader ); final CompilationInfo.Model redefinedModel = new CompilationInfo.Model( compilationInfo.getInitialModel().getEmittedJoinPoint(), // copy the reference since it is the same newAdviceContainer, compilationInfo.getRedefinitionCounter(), compilationInfo.getInitialModel().getThisClassInfo() ); JoinPointFactory.compileJoinPointAndAttachToClassLoader(redefinedModel, loader); compilationInfo.setRedefinedModel(redefinedModel); JoinPointFactory.addCompilationInfo(joinPointInfo.getJoinPointClass(), compilationInfo); } /** * Redefines the intial (weaved in) join point to delegate to the newly compiled "real" join point which is * based on the new redefined model. * * @param changeSet the change set */ private static void redefineInitialJoinPoints(final ChangeSet changeSet) { // TODO type should be pluggable RedefinerFactory.newRedefiner(RedefinerFactory.Type.HOTSWAP).redefine(changeSet); } /** * Returns a set with the new expressions for the advice in the aspect to deploy. * * @param aspectClass s * @param newAspectDef * @param systemDef * @param deploymentScope * @param deploymentHandle * @return a set with the new expressions */ private static Set getNewExpressionsForAspect(final Class aspectClass, final AspectDefinition newAspectDef, final SystemDefinition systemDef, final DeploymentScope deploymentScope, final DeploymentHandle deploymentHandle) { final ClassLoader aspectLoader = aspectClass.getClassLoader(); final String aspectName = aspectClass.getName(); // keep XML settings so that they don't get changed when we read the annotations String keptContainerClassName = newAspectDef.getContainerClassName(); DeploymentModel keptModel = newAspectDef.getDeploymentModel(); final ClassInfo classInfo = AsmClassInfo.getClassInfo(aspectName, aspectLoader); AspectModelManager.defineAspect(classInfo, newAspectDef, aspectLoader); AspectAnnotationParser.parse(classInfo, newAspectDef, aspectLoader); AspectDefinition aspectDef = systemDef.getAspectDefinition(aspectName); if (aspectDef != null) { // if in def already reuse some of the settings that can have been overridded by XML def //AW-461 newAspectDef.setContainerClassName(keptContainerClassName);//aspectDef.getContainerClassName()); newAspectDef.setDeploymentModel(keptModel);//aspectDef.getDeploymentModel()); } systemDef.addAspectOverwriteIfExists(newAspectDef); final Set newExpressions = new HashSet(); for (Iterator it2 = newAspectDef.getAdviceDefinitions().iterator(); it2.hasNext();) { AdviceDefinition adviceDef = (AdviceDefinition) it2.next(); ExpressionInfo oldExpression = adviceDef.getExpressionInfo(); if (oldExpression == null) { continue; } deploymentHandle.registerDefinitionChange(adviceDef, oldExpression); final ExpressionInfo newExpression = deploymentScope.newExpressionInfo(oldExpression); adviceDef.setExpressionInfo(newExpression); newExpressions.add(newExpression); } return newExpressions; } /** * Imports a class from one class loader to another one. * * @param clazz the class to import * @param toLoader the loader to import to */ private static void importClassIntoLoader(final Class clazz, final ClassLoader toLoader) { final ClassLoader fromLoader = clazz.getClassLoader(); if (toLoader == fromLoader) { return; } final String className = clazz.getName(); try { Class.forName(className, false, toLoader); } catch (ClassNotFoundException cnfe) { try { InputStream stream = null; byte[] bytes; try { stream = fromLoader.getResourceAsStream(className.replace('.', '/') + ".class"); bytes = new ClassReader(stream).b; } finally { try { stream.close(); } catch (Exception e) { ; } } Class klass = Class.forName("java.lang.ClassLoader", false, toLoader); Method method = klass.getDeclaredMethod( "defineClass", new Class[]{String.class, byte[].class, int.class, int.class} ); method.setAccessible(true); Object[] args = new Object[]{ clazz.getName(), bytes, new Integer(0), new Integer(bytes.length) }; method.invoke(toLoader, args); method.setAccessible(false); } catch (Exception e) { throw new RuntimeException( new StringBuffer().append("could not deploy aspect ["). append(className).append("] in class loader [").append(toLoader) .append(']').toString() ); } } } /** * Logs undeployment. * <p/> * TODO unified way or at least format for logging * * @param className * @param loader */ private static void logUndeployment(final String className, final ClassLoader loader) { System.out.println( new StringBuffer().append("Deployer::INFO - undeploying aspect ["). append(className).append("] from class loader ["). append(loader).append(']').toString() ); } /** * Logs deployment. * <p/> * TODO unified way or at least format for logging * * @param className * @param loader */ private static void logDeployment(final String className, final ClassLoader loader) { System.out.println( new StringBuffer().append("Deployer::INFO - deploying aspect ["). append(className).append("] in class loader ["). append(loader).append(']').toString() ); } }