package org.activiti.spring.components.config; import org.activiti.engine.ProcessEngine; import org.activiti.engine.delegate.DelegateExecution; import org.activiti.engine.delegate.JavaDelegate; import org.activiti.engine.impl.context.Context; import org.activiti.engine.impl.persistence.entity.ExecutionEntity; import org.activiti.engine.runtime.ProcessInstance; import org.activiti.spring.annotations.ProcessVariable; import org.activiti.spring.annotations.State; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.reflect.MethodUtils; import org.springframework.aop.framework.ProxyFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.util.ReflectionUtils; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * This class simply creates proxies that in turn dispatch to methods on the class based on the metadata provided by their annotation * * @author Josh Long */ public class StateAnnotationBeanPostProcessor implements BeanPostProcessor { private ProcessEngine processEngine; private Map<String, HandlerRegistration> mapOfStateHandlers = new ConcurrentHashMap<String, HandlerRegistration>(); public StateAnnotationBeanPostProcessor(ProcessEngine processEngine) { this.processEngine = processEngine; } private void ensureHandlerRegistered(final String serviceTask, final Object o) { if (!mapOfStateHandlers.containsKey(serviceTask)) { ReflectionUtils.doWithMethods( o.getClass(), new ReflectionUtils.MethodCallback() { @Override public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { mapOfStateHandlers.put(serviceTask, new HandlerRegistration(serviceTask, method)); } }, new ReflectionUtils.MethodFilter() { @Override public boolean matches(Method method) { return handlesEvent(serviceTask, method); } private boolean handlesEvent(String e, Method method) { State stateAnnotationForCurrentMethod = null; boolean isStateHandlerMethod = method.getAnnotations().length > 0 && (stateAnnotationForCurrentMethod = method.getAnnotation(State.class)) != null; if (isStateHandlerMethod) { String stateAssignedTo = StringUtils.defaultIfBlank( stateAnnotationForCurrentMethod.state(), stateAnnotationForCurrentMethod.value()); if (e.equals(stateAssignedTo)) { return true; } } return false; } } ); } } protected int positionOfDelegateExecution(Method handlerMethod) { int pos = 0; for (Class<?> argClass : handlerMethod.getParameterTypes()) { if (argClass.isAssignableFrom(DelegateExecution.class)) { return pos; } pos += 1; } return -1; } protected void delegateTo(Object o, DelegateExecution delegateExecution) throws Exception { String serviceTaskIdToWhichWereResponding = delegateExecution.getCurrentActivityId(); ensureHandlerRegistered(serviceTaskIdToWhichWereResponding, o); HandlerRegistration handlerRegistration = this.mapOfStateHandlers.get(serviceTaskIdToWhichWereResponding); Method handlerMethod = handlerRegistration.getMethod(); Map<Integer, String> processVariableArgumentPositions = whatProcessVariablesAreExpectedAndWhere(handlerMethod); int positionCounter = positionOfDelegateExecution(handlerMethod); // todo some sort of validation to check that there are no arguments that we can't handle int argLength = handlerMethod.getParameterTypes().length; Object[] runtimeParameterValues = new Object[argLength]; for (int indx : processVariableArgumentPositions.keySet()) { runtimeParameterValues[indx] = processVariable(processVariableArgumentPositions.get(indx), handlerMethod.getParameterTypes()[indx]); } if (positionCounter != -1) { runtimeParameterValues[positionCounter] = delegateExecution; } handlerMethod.invoke(o, runtimeParameterValues); } private Object processVariable(String variableName, Class<?> typeOfProcessVariable) { ProcessInstance processInstance = Context.getExecutionContext().getProcessInstance(); ExecutionEntity executionEntity = (ExecutionEntity) processInstance; return executionEntity.getVariable(variableName); } protected Map<Integer, String> whatProcessVariablesAreExpectedAndWhere(Method handlerMethod) { Map<Integer, String> whatProcessVariablesAreExpectedAndWhere = new HashMap<Integer, String>(); Annotation[][] paramAnnotations = handlerMethod.getParameterAnnotations(); int offset = 0; for (Annotation[] annotationsForAGivenParameter : paramAnnotations) { for (Annotation a : annotationsForAGivenParameter) { if (a instanceof ProcessVariable) { ProcessVariable processVariable = (ProcessVariable) a; String pvName = processVariable.value(); whatProcessVariablesAreExpectedAndWhere.put(offset, pvName); } } offset += 1; } return whatProcessVariablesAreExpectedAndWhere; } protected JavaDelegate javaDelegateFromExistingBean(final Object o) throws BeansException { // first create a proxy that implements JavaDelegate // then setup logic to dispatch to a cached metadata map containing the methods on the class as well as // the particular runtime arguments those methods require, including access to things like @ProcessVariable annotations if (o instanceof JavaDelegate) { return (JavaDelegate) o; } final Method javaDelegateExecutionMethod = MethodUtils.getAccessibleMethod(JavaDelegate.class, "execute", DelegateExecution.class); ProxyFactory proxyFactory = new ProxyFactory(o); proxyFactory.setProxyTargetClass(true); proxyFactory.addInterface(JavaDelegate.class); proxyFactory.addAdvice(new MethodInterceptor() { @Override public Object invoke(MethodInvocation invocation) throws Throwable { // if its one type of method handle it our selves // by looking up the appropriate handler method and invoking it dynamically Method method = invocation.getMethod(); // either its a request we can handle if (method.equals(javaDelegateExecutionMethod)) { DelegateExecution delegateExecution = (DelegateExecution) invocation.getArguments()[0]; delegateTo(o, delegateExecution); return null; } // otherwise pass thru return invocation.proceed(); } }); return (JavaDelegate) proxyFactory.getProxy(); } @Override public Object postProcessBeforeInitialization(Object o, String s) throws BeansException { return javaDelegateFromExistingBean(o); } @Override public Object postProcessAfterInitialization(Object o, String s) throws BeansException { return o; } public static class HandlerRegistration { private String id; private Method method; public HandlerRegistration(String id, Method toInvoke) { this.id = id; this.method = toInvoke; } public Method getMethod() { return this.method; } public String getId() { return this.id; } } }