/*
* JBoss, Home of Professional Open Source
* Copyright 2006, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.soa.esb.services.jbpm.cmd;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import org.apache.log4j.Logger;
import org.jboss.soa.esb.ConfigurationException;
import org.jboss.soa.esb.addressing.EPR;
import org.jboss.soa.esb.message.Message;
import org.jboss.soa.esb.message.mapping.ObjectMapper;
import org.jboss.soa.esb.message.mapping.ObjectMappingException;
import org.jboss.soa.esb.services.jbpm.Constants;
import org.jboss.soa.esb.services.jbpm.Mapping;
import org.jbpm.JbpmConfiguration;
import org.jbpm.JbpmContext;
import org.jbpm.JbpmException;
import org.jbpm.command.*;
import org.jbpm.command.impl.CommandServiceImpl;
import org.jbpm.graph.exe.ProcessInstance;
/**
* Executes jBPM commands.
*
* @author <a href="mailto:schifest@heuristica.com.ar">schifest@heuristica.com.ar</a>
* @author <a href="mailto:kurt.stam@jboss.com">Kurt Stam</a>
*
*/
public class CommandExecutor
{
private static CommandExecutor commandExecutor = new CommandExecutor();
private static Logger logger = Logger.getLogger(CommandExecutor.class);
private static JbpmConfiguration jbpmConfig;
private static CommandService jbpmService;
public static CommandExecutor getInstance()
{
return commandExecutor;
}
public Command getCommand(String commandString) throws ConfigurationException
{
Constants.OpCode opCode = Constants.OpCode.valueOf(commandString);
Command command = _values.get(opCode);
if (null==command)
throw new ConfigurationException(opCode.toString()+" not implemented,");
return command;
}
protected static final Command CANCEL_PROCESS_INSTANCE_EXECUTOR = new Command()
{
public void execute(Message message) throws JbpmException
{
long processId = MessageHelper.getLongValue(message,Constants.PROCESS_INSTANCE_ID);
logger.debug("Cancel Process Instance Command for ProcessId=" + processId);
executeJbpmCommand(new CancelProcessInstanceCommand(processId));
}
};
protected static final Command SIGNAL_EXECUTOR=new Command()
{
public void execute(Message message) throws JbpmException
{
SignalCommand command = new SignalCommand();
Long tokenId = MessageHelper.getLongValue(message,Constants.TOKEN_ID);
if (null== tokenId)
{
Long processInstanceId = MessageHelper.getLongValue(message,Constants.PROCESS_INSTANCE_ID);
logger.debug("TokenId was not found, so try to obtain the root token from this process definition, " +
" with ProcessId=" + processInstanceId);
// now try with the process id (use root token)
if (processInstanceId==null) {
throw new JbpmException("Either <"+Constants.TOKEN_ID+"> or <"+Constants.PROCESS_INSTANCE_ID
+"> object must be specified in Message body to know who to signal");
}
ProcessInstance inst = (ProcessInstance)getJbpmCommandService()
.execute(new GetProcessInstanceCommand(processInstanceId));
tokenId = inst.getRootToken().getId();
}
command.setTokenId(tokenId);
String transition = MessageHelper.getStringValue(message, Constants.TRANSITION_NAME);
if (null!=transition)
command.setTransitionName(transition);
//TODO allow for precise setting
Map map = (Map)MessageHelper.getObjectValue(message, Constants.VARIABLE_VALUES);
if (null!=map) command.setVariables(map);
logger.debug("Signaling Process with TokenId=" + tokenId + ", Transition=" + transition + ", VariableMap=" + map);
executeJbpmCommand(command);
}
};
protected static final Command CALLBACK_EXECUTOR=new Command()
{
public void execute(Message message) throws JbpmException
{
CallbackCommand command = new CallbackCommand();
EPR toEpr = message.getHeader().getCall().getTo();
command.setCallbackEpr(toEpr);
command.setMessage(message);
String transition = (toEpr.getAddr().getExtensionValue(Constants.TRANSITION_NAME));
//The transition can be overriden if needed, by setting the following variable.
String overriddenTransition = MessageHelper.getStringValue(message, Constants.TRANSITION_NAME);
if (overriddenTransition!=null) {
if (transition!=null) logger.debug("Overriding the transition to " + overriddenTransition);
transition = overriddenTransition;
}
if (null!=transition) command.setTransitionName(transition);
logger.debug("Signaling Process with Transition=" + transition);
executeJbpmCommand(command);
}
};
protected static final Map<Constants.OpCode,Command> _values = new HashMap<Constants.OpCode,Command>();
static
{
_values.put(Constants.OpCode.CallbackCommand ,CALLBACK_EXECUTOR);
_values.put(Constants.OpCode.CancelProcessInstanceCommand,CANCEL_PROCESS_INSTANCE_EXECUTOR);
_values.put(Constants.OpCode.NewProcessInstanceCommand
,new CommandExecutor.NewProcessInstancePerformer(false));
_values.put(Constants.OpCode.StartProcessInstanceCommand
,new CommandExecutor.NewProcessInstancePerformer(true));
_values.put(Constants.OpCode.GetProcessInstanceVariablesCommand, new GetProcessVariablesPerformer());
}
// this class is used both for NewProcessInstance and for StartProcessInstance
protected static final class NewProcessInstancePerformer implements Command
{
private boolean _start;
public NewProcessInstancePerformer(boolean start) { _start = start; }
public void execute(Message request) throws JbpmException
{
perform(request,_start);
}
private void perform(Message esbMessage, boolean start)
{
ObjectMapper objectMapper = new ObjectMapper();
final NewProcessInstanceCommand command ;
if (start) {
final String transition = MessageHelper.getStringValue(esbMessage, Constants.TRANSITION_NAME);
command = new AsyncStartProcessInstanceCommand(transition);
} else {
command = new NewProcessInstanceCommand();
}
Long processDefId = MessageHelper.getLongValue(esbMessage,Constants.PROCESS_DEFINITION_ID);
if (null!=processDefId) {
command.setProcessId(processDefId);
} else {
String processName = MessageHelper.getStringValue(esbMessage,Constants.PROCESS_DEFINITION_NAME);
if (null!=processName) {
command.setProcessName(processName);
} else {
throw new JbpmException("At least one of "+Constants.PROCESS_DEFINITION_NAME
+" or "+Constants.PROCESS_DEFINITION_ID+" must have a valid value");
}
}
String keyPath = MessageHelper.getStringValue(esbMessage, Constants.KEYPATH);
if (keyPath!=null) {
try {
String key = String.valueOf(objectMapper.getObjectFromMessage(esbMessage, keyPath));
command.setKey(key);
} catch (ObjectMappingException e) {
logger.error("Could not locate key " + e.getMessage(), e);
}
}
String actorId=MessageHelper.getStringValue(esbMessage, Constants.ACTOR_ID);
if (null!=actorId) command.setActorId(actorId);
Boolean createStartTask = MessageHelper.getBooleanValue(esbMessage, Constants.CREATE_START_TASK);
if (null!=createStartTask) command.setCreateStartTask(createStartTask);
Map<String, Object> variables = MessageHelper.getVariablesMap(esbMessage, Constants.VARIABLE_VALUES);
final String replyTo = MessageHelper.getStringValue(esbMessage, Constants.REPLY_TO) ;
final String faultTo = MessageHelper.getStringValue(esbMessage, Constants.FAULT_TO) ;
if ((replyTo != null) || (faultTo != null)) {
final Map<String, Object> newVariables = (variables == null ? new HashMap<String, Object>() : new HashMap<String, Object>(variables)) ;
if (replyTo != null) {
newVariables.put(Constants.REPLY_TO, replyTo) ;
}
if (faultTo != null) {
newVariables.put(Constants.FAULT_TO, faultTo) ;
}
newVariables.put(Constants.ESB_MESSAGE_ID, esbMessage.getHeader().getCall().getMessageID());
variables = newVariables ;
}
if (null!=variables) command.setVariables(variables);
logger.debug("New process instance with command=" + command);
Object result = executeJbpmCommand(command);
if(result instanceof ProcessInstance) {
ProcessInstance processInstance = (ProcessInstance) result;
MessageHelper.setLongValue(esbMessage, Constants.PROCESS_INSTANCE_ID, processInstance.getId());
}
}
}
private static class GetProcessVariablesPerformer implements Command {
public void execute(Message message) throws JbpmException {
Long processInstanceId = MessageHelper.getLongValue(message,Constants.PROCESS_INSTANCE_ID);
if (processInstanceId == null) {
throw new JbpmException("'" + Constants.PROCESS_INSTANCE_ID + "' must be specified in Message body. Cannot retrieve process variables.");
}
ProcessInstance processInstance = (ProcessInstance)getJbpmCommandService().execute(new GetProcessInstanceCommand(processInstanceId));
if(processInstance == null) {
throw new JbpmException("Unknown jBPM process instance ID '" + processInstanceId + "'.");
}
VariablesCommand command = new HungryVariablesCommand();
command.setTokenId(processInstance.getRootToken().getId());
Object result = getJbpmCommandService().execute(command);
if(result instanceof Map) {
Map processVariables = (Map) result;
List<Mapping> variableMappings = GetProcessVariablesFacade.getMappings(message);
for (Mapping mapping : variableMappings) {
Object value = processVariables.get(mapping.getBpm());
ObjectMapper mapper = new ObjectMapper();
try {
if(value != null) {
mapper.setObjectOnMessage(message, mapping.getEsb(), value);
} else {
mapper.setObjectOnMessage(message, mapping.getEsb(), mapping.getDefaultValue());
}
} catch (ObjectMappingException e) {
throw new JbpmException("Failed to apply jBPM->ESB mapping for process instance ID '" + processInstanceId + "' (root token ID '" + processInstance.getRootToken().getId() + "'). Mapping: " + mapping);
}
}
} else if(result == null) {
throw new JbpmException("Invalid 'null' jBPM VariablesCommand result for process instance ID '" + processInstanceId + "' (root token ID '" + processInstance.getRootToken().getId() + "').");
} else {
throw new JbpmException("Invalid jBPM VariablesCommand result type '" + result.getClass().getName() + "' for process instance ID '" + processInstanceId + "' (root token ID '" + processInstance.getRootToken().getId() + "').. Expected instanceof '" + Map.class.getName() + "'.");
}
}
}
private static Object executeJbpmCommand(org.jbpm.command.Command command)
{
if (logger.isDebugEnabled()) {
logger.debug(command);
}
return getJbpmCommandService().execute(command);
}
/**
* Encapsulate obtention of jBPM CommandService.
* @return CommandService
*/
public static CommandService getJbpmCommandService()
{
if (null== jbpmService) {
jbpmConfig = JbpmConfiguration.getInstance();
jbpmService = new CommandServiceImpl(jbpmConfig);
}
return jbpmService;
}
/**
* Command responsible for creating a process instance and firing an asynchronous signal to the root node.
*
* @author <a href='kevin.conner@jboss.com'>Kevin Conner</a>
*/
private static class AsyncStartProcessInstanceCommand extends NewProcessInstanceCommand
{
/**
* The serial version UID for this class.
*/
private static final long serialVersionUID = 7917063133912138584L;
/**
* The name of the transition to signal.
*/
private final String transitionName ;
/**
* Create the asynchronous process instance command with the specified transition.
* @param transitionName The transition to signal or null if the default transition should be used.
*/
AsyncStartProcessInstanceCommand(final String transitionName)
{
this.transitionName = transitionName ;
}
/**
* Execute the command.
* @param jbpmContext The current jBPM context.
* @throws Exception for any errors.
*/
@Override
public Object execute(final JbpmContext jbpmContext)
throws Exception
{
final Object result = super.execute(jbpmContext) ;
if (result instanceof ProcessInstance)
{
final ProcessInstance processInstance = (ProcessInstance)result ;
AsyncProcessSignal.createSignalJob(jbpmContext, processInstance.getRootToken(), transitionName, getActorId(), false, null) ;
}
return result ;
}
}
}