/*
* 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 org.apache.log4j.Logger;
import org.dom4j.tree.DefaultElement;
import org.jboss.soa.esb.ConfigurationException;
import org.jboss.soa.esb.addressing.EPR;
import org.jboss.soa.esb.addressing.PortReference;
import org.jboss.soa.esb.message.Message;
import org.jboss.soa.esb.services.jbpm.Constants;
import org.jboss.soa.esb.services.jbpm.JBpmObjectMapper;
import org.jboss.soa.esb.services.jbpm.Mapping;
import org.jboss.soa.esb.services.jbpm.actionhandlers.EsbActionHandler;
import org.jbpm.JbpmContext;
import org.jbpm.command.Command;
import org.jbpm.context.exe.ContextInstance;
import org.jbpm.graph.def.Action;
import org.jbpm.graph.def.Node;
import org.jbpm.graph.exe.ProcessInstance;
import org.jbpm.graph.exe.Token;
import org.jbpm.instantiation.Delegation;
/**
* @author kstam
*
*/
public class CallbackCommand implements Command {
private static final HashMap<Mapping, Object> EMPTY_VARIABLES_MAP = new HashMap<Mapping, Object>();
private static Logger logger = Logger.getLogger(CallbackCommand.class);
/**
*
*/
private static final long serialVersionUID = 1L;
/**
* The named transition or null if the default is to be used.
*/
private String transitionName ;
/**
* The variable map.
*/
private Map<Mapping, Object> variables ;
/**
* The ESB Message object.
*/
private Message message;
private EPR callbackEpr;
public CallbackCommand() {
super();
}
public EPR getCallbackEpr() {
return callbackEpr;
}
public void setCallbackEpr(EPR callbackEpr) {
this.callbackEpr = callbackEpr;
}
public void setTransitionName(final String transitionName) {
this.transitionName = transitionName ;
}
public void setVariables(final Map<Mapping, Object> variables) {
this.variables = variables ;
}
public void setMessage(final Message message)
{
this.message = message;
}
public Object execute(JbpmContext jbpmContext)
{
final boolean isDebugEnabled = logger.isDebugEnabled() ;
try {
PortReference portRef = callbackEpr.getAddr();
long nodeId = Long.parseLong(portRef.getExtensionValue(Constants.NODE_ID));
long tokenId = Long.parseLong(portRef.getExtensionValue(Constants.TOKEN_ID));
long processInstanceId = Long.parseLong(portRef.getExtensionValue(Constants.PROCESS_INSTANCE_ID));
String counterName = Constants.PROCESS_NODE_VERSION_COUNTER + nodeId + '_' + tokenId;
long processNodeVersion = Long.parseLong(portRef.getExtensionValue(counterName));
if (isDebugEnabled) logger.debug("Expected nodeId=" + nodeId + ", tokenId=" + tokenId + ", processNodeVersion=" + processNodeVersion);
//get update on current state of things.
final Token token = jbpmContext.getToken(tokenId) ;
if (token == null) {
throw new CallbackException("Token id " + tokenId + " from process instance " + processInstanceId + " is no longer active") ;
}
final ProcessInstance instance = token.getProcessInstance() ;
if (instance == null) {
throw new CallbackException("Process instance " + processInstanceId + " is no longer active") ;
}
if (instance.getId() != processInstanceId) {
throw new CallbackException("Token id " + tokenId + " from process instance " + processInstanceId + " now attached to process instance " + instance.getId()) ;
}
if (nodeId != token.getNode().getId()) {
throw new CallbackException("Token id " + tokenId + " from process instance " + processInstanceId + " is no longer on expected node, expected " + nodeId + " but discovered " + token.getNode().getId()) ;
}
if (token.hasEnded()) {
throw new CallbackException("Token id " + tokenId + " from process instance " + processInstanceId + " has already terminated") ;
}
if (token.isLocked()) {
throw new CallbackException("Token id " + tokenId + " from process instance " + processInstanceId + " is already locked, another thread is active") ;
}
final ContextInstance contextInstance = instance.getContextInstance() ;
final long currentProcessNodeVersion = Long.parseLong(String.valueOf(contextInstance.getVariableLocally(counterName, token)));
if (isDebugEnabled) logger.debug("ProcessNodeVersion=" + currentProcessNodeVersion);
if (processNodeVersion!=currentProcessNodeVersion) {
throw new CallbackException("The current processNodeVersion (id=" + currentProcessNodeVersion + ") is not the expected version (version=" + processNodeVersion + ").");
}
final String globalProcessScopeVal = portRef.getExtensionValue(Constants.PROCESS_SCOPE_ATTR) ;
// Default to global scope as that matches the previous functionality
// N.B. This is different from retrieving variables!!
final boolean globalProcessScope = (globalProcessScopeVal == null ? true : Boolean.parseBoolean(globalProcessScopeVal));
Map<Mapping, Object> variablesMap = getVariablesMapFromMessage(token.getNode(), message);
// Try to be backward compatible in case the variable mappings were stored in the epr and set on this CallbackCommand instance.
if (variables != null)
{
variablesMap.putAll(variables);
}
AsyncProcessSignal.createSignalJob(jbpmContext, token, transitionName, jbpmContext.getActorId(), globalProcessScope, variablesMap) ;
} catch (CallbackException jbpmCe) {
//TODO: Why is this only a warning?
logger.warn(jbpmCe.getMessage());
}
return null;
}
/**
* This method uses the information located in the jBPM nodes action definition to find out
* which variables have been defined there.
* <p/>
* The variables that are specified in the element 'esbToBpmVars' will be extracted from the
* ESB Message object and put into the retured map.
*
* For example:
* <pre>{@code
* <node name="node1">
* <action name="first action" class="org.jboss.soa.esb.services.jbpm.actionhandlers.EsbActionHandler">
* <esbCategoryName>MockCategory</esbCategoryName>
* <esbServiceName>MockService</esbServiceName>
* <esbToBpmVars>
* <mapping esb="BODY_CONTENT" bpm="theBody" process-scope="false"/>
* </esbToBpmVars>
* </action>
* </node>
* }</pre>
*
* @param node The jBPM node.
* @param esbMessage The ESB Message object
* @return Map A Map with the 'esbToBpmVars' mapping as the key and the object/value of
* that mapping which was extracted from the ESB Message object.
* @throws CallbackException
*/
HashMap<Mapping, Object> getVariablesMapFromMessage(final Node node, final Message esbMessage) throws CallbackException
{
final Action jbpmAction = node.getAction();
if (jbpmAction != null && esbMessage != null)
{
final Delegation actionDelegation = jbpmAction.getActionDelegation();
if (actionDelegation != null)
{
final Object delegate = actionDelegation.getInstance();
if (delegate instanceof EsbActionHandler)
{
final EsbActionHandler handler = (EsbActionHandler)delegate;
final DefaultElement esbToBpmVars = handler.esbToBpmVars;
if (esbToBpmVars != null)
{
try
{
return new JBpmObjectMapper().mapFromEsbMessageToJBpmMapping(esbMessage, esbToBpmVars.asXML());
}
catch (final ConfigurationException e)
{
throw new CallbackException(e.getMessage(), e);
}
}
}
}
}
return EMPTY_VARIABLES_MAP;
}
}