/*
* Copyright 2010 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.activiti.spring.components.aop;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.logging.Logger;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.spring.annotations.BusinessKey;
import org.activiti.spring.annotations.ProcessVariable;
import org.activiti.spring.annotations.StartProcess;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* {@link org.aopalliance.intercept.MethodInterceptor} that starts a business process
* as a result of a successful method invocation.
*
* @author Josh Long
*/
public class ProcessStartingMethodInterceptor implements MethodInterceptor {
private Logger log = Logger.getLogger(getClass().getName());
/**
* injected reference - can be obtained via a {@link org.activiti.spring.ProcessEngineFactoryBean}
*/
protected ProcessEngine processEngine;
/**
* @param processEngine takes a reference to a {@link org.activiti.engine.ProcessEngine}
*/
public ProcessStartingMethodInterceptor(ProcessEngine processEngine) {
this.processEngine = processEngine;
}
boolean shouldReturnProcessInstance(StartProcess startProcess, MethodInvocation methodInvocation, Object result) {
return (result instanceof ProcessInstance || methodInvocation.getMethod().getReturnType().isAssignableFrom(ProcessInstance.class));
}
boolean shouldReturnProcessInstanceId(StartProcess startProcess, MethodInvocation methodInvocation, Object result) {
return startProcess.returnProcessInstanceId() && (result instanceof String || methodInvocation.getMethod().getReturnType().isAssignableFrom(String.class));
}
@SuppressWarnings("unused")
boolean shouldReturnAsyncResultWithProcessInstance(StartProcess startProcess, MethodInvocation methodInvocation, Object result) {
return (result instanceof Future || methodInvocation.getMethod().getReturnType().isAssignableFrom(Future.class));
}
public Object invoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
StartProcess startProcess = AnnotationUtils.getAnnotation(method, StartProcess.class);
String processKey = startProcess.processKey();
Assert.hasText(processKey, "you must provide the name of process to start");
Object result;
try {
result = invocation.proceed();
Map<String, Object> vars = this.processVariablesFromAnnotations(invocation);
String businessKey = this.processBusinessKey(invocation);
log.info("variables for the started process: " + vars.toString());
RuntimeService runtimeService = this.processEngine.getRuntimeService();
ProcessInstance pi ;
if (null != businessKey && StringUtils.hasText(businessKey)) {
pi = runtimeService.startProcessInstanceByKey(processKey, businessKey, vars);
log.info("the business key for the started process is '" + businessKey + "' ");
} else {
pi = runtimeService.startProcessInstanceByKey(processKey, vars);
}
String pId = pi.getId();
if (invocation.getMethod().getReturnType().equals(void.class))
return null;
if (shouldReturnProcessInstance(startProcess, invocation, result))
return pi;
if (shouldReturnProcessInstanceId(startProcess, invocation, result))
return pId;
if (shouldReturnAsyncResultWithProcessInstance(startProcess, invocation, result)) {
return new AsyncResult<ProcessInstance>(pi);
}
} catch (Throwable th) {
throw new RuntimeException(th);
}
return result;
}
protected String processBusinessKey(MethodInvocation invocation) throws Throwable {
Map<BusinessKey, String> businessKeyAnnotations = this.mapOfAnnotationValues( BusinessKey.class ,invocation);
if (businessKeyAnnotations.size() == 1) {
BusinessKey processId = businessKeyAnnotations.keySet().iterator().next();
return businessKeyAnnotations.get(processId);
}
return null;
}
@SuppressWarnings("unchecked")
private <K extends Annotation, V> Map<K, V> mapOfAnnotationValues(Class<K> annotationType, MethodInvocation invocation) {
Method method = invocation.getMethod();
Annotation[][] annotations = method.getParameterAnnotations();
Map<K, V> vars = new HashMap<K, V>();
int paramIndx = 0;
for (Annotation[] annPerParam : annotations) {
for (Annotation annotation : annPerParam) {
if (!annotationType.isAssignableFrom(annotation.getClass())) {
continue;
}
K pv = (K) annotation;
V v = (V) invocation.getArguments()[paramIndx];
vars.put(pv, v);
}
paramIndx += 1;
}
return vars;
}
/**
* if there any arguments with the {@link org.activiti.engine.annotations.ProcessVariable} annotation,
* then we feed those parameters into the business process
*
* @param invocation the invocation of the method as passed to the {@link org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)} method
* @return returns the map of process variables extracted from the parameters
* @throws Throwable thrown anything goes wrong
*/
protected Map<String, Object> processVariablesFromAnnotations(MethodInvocation invocation) throws Throwable {
Map<ProcessVariable, Object> vars = this.mapOfAnnotationValues(ProcessVariable.class, invocation);
Map<String, Object> varNameToValueMap = new HashMap<String, Object>();
for (ProcessVariable processVariable : vars.keySet()) {
varNameToValueMap.put(processVariable.value(), vars.get(processVariable));
}
return varNameToValueMap;
}
}