/*
* This file is part of the WfMOpen project.
* Copyright (C) 2001-2006 Danet GmbH (www.danet.de), BU BTS.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* $Id: InstanceResponseGenerator.java 2331 2007-03-29 11:46:54Z schnelle $
*
* $Log$
* Revision 1.11 2007/03/27 21:59:42 mlipp
* Fixed lots of checkstyle warnings.
*
* Revision 1.10 2007/03/01 12:32:57 schnelle
* Enhanced Instance.SetProperties to process ContextData.
*
* Revision 1.9 2007/02/16 21:00:21 mlipp
* Fixed some null pointer problems.
*
* Revision 1.8 2007/02/01 13:44:43 schnelle
* Using namespace for factory schemas that do not contain '&'.
*
* Revision 1.7 2007/02/01 12:38:36 schnelle
* Use of encoded key for properties.
*
* Revision 1.6 2007/01/31 22:55:36 mlipp
* Some more refactoring and fixes of problems introduced by refactoring.
*
* Revision 1.5 2007/01/31 14:53:06 schnelle
* Small corrections wvaluating the resource reference.
*
* Revision 1.4 2007/01/31 12:24:06 drmlipp
* Design revisited.
*
* Revision 1.3 2007/01/30 15:07:37 schnelle
* Filtering of all instances for the current factory in a list instances request.
*
* Revision 1.2 2007/01/30 11:56:14 drmlipp
* Merged Wf-XML branch.
*
* Revision 1.1.2.23 2007/01/29 15:04:19 schnelle
* Renaming of Observer to ObserverRegistry and URIDecoder to ResourceReference.
*
* Revision 1.1.2.22 2007/01/29 13:40:30 schnelle
* Storing of the sender base in the servlet context.
*
* Revision 1.1.2.21 2007/01/29 13:10:27 drmlipp
* Using utilities method for timestamp convertion.
*
* Revision 1.1.2.20 2007/01/24 11:46:35 schnelle
* Moved wsdl files and xsd files intot resources subdirectory.
*
* Revision 1.1.2.19 2007/01/24 10:56:50 schnelle
* Prepared return of a result for aobservers.
*
* Revision 1.1.2.18 2007/01/24 09:28:53 schnelle
* Returning result data for the instance.
*
* Revision 1.1.2.17 2007/01/19 15:01:59 schnelle
* Returning context data at instance getproperties.
*
* Revision 1.1.2.16 2007/01/19 12:34:56 schnelle
* Moved generation and decoding of the URI that is used as the receiver key to new class URIDecoder.
*
* Revision 1.1.2.15 2007/01/16 11:05:42 schnelle
* Refactoring: Moved subscription handling methods to own class.
*
* Revision 1.1.2.14 2007/01/11 11:37:10 schnelle
* Added subscription if an oberver key is given in the creation of a process.
*
* Revision 1.1.2.13 2007/01/10 14:48:18 schnelle
* Checked if a subscription already exists, before storing the observer in the database.
*
* Revision 1.1.2.12 2007/01/10 14:16:52 schnelle
* Implemented unsubscribe.
*
* Revision 1.1.2.11 2007/01/10 13:41:27 schnelle
* Implemented subscribe.
*
* Revision 1.1.2.10 2007/01/10 09:03:43 schnelle
* Implemented set properties methods.
*
* Revision 1.1.2.9 2006/12/20 13:32:24 schnelle
* Basic implementato of GetProperties for Instance and Activity.
*
* Revision 1.1.2.8 2006/12/18 14:41:02 schnelle
* Preparatation for individual schema definition for each getproperties request.
*
* Revision 1.1.2.7 2006/12/14 08:50:21 schnelle
* Implemented CompleteActivity.
*
* Revision 1.1.2.6 2006/12/13 11:23:48 schnelle
* Implemented instance ListActivities.
*
* Revision 1.1.2.5 2006/12/12 13:24:38 schnelle
* Introduction of ASAPException to provide a detailed mesage.
*
* Revision 1.1.2.4 2006/12/12 11:41:21 schnelle
* Implemented state mapping of omg states to asap states.
*
* Revision 1.1.2.3 2006/12/12 09:34:35 schnelle
* Implemented ChangeState for Instance.
*
* Revision 1.1.2.2 2006/12/11 11:05:34 schnelle
* Added template methods for all requests.
*
* Revision 1.1.2.1 2006/11/28 12:20:10 schnelle
* Creation of a separate class to handle the issues for a specific resource.
*
* Revision 1.1.2.2 2006/11/27 15:41:55 schnelle
* Introducing some constants for request and response identification.
*
* Revision 1.1.2.1 2006/11/24 12:19:13 schnelle
* Separtion of response generation into ResponseGenerator class.
*
*/
package de.danet.an.workflow.clients.wfxml;
import java.rmi.RemoteException;
import java.sql.SQLException;
import java.text.ParseException;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.NoSuchElementException;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.Name;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPBodyElement;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPHeaderElement;
import javax.xml.soap.SOAPMessage;
import javax.xml.transform.TransformerException;
import org.xml.sax.SAXException;
import de.danet.an.util.XMLUtil;
import de.danet.an.workflow.api.Activity;
import de.danet.an.workflow.api.DefaultProcessData;
import de.danet.an.workflow.api.InvalidKeyException;
import de.danet.an.workflow.api.Process;
import de.danet.an.workflow.api.ProcessDefinition;
import de.danet.an.workflow.api.ProcessDirectory;
import de.danet.an.workflow.api.SAXEventBuffer;
import de.danet.an.workflow.api.WorkflowService;
import de.danet.an.workflow.assignment.Assignment;
import de.danet.an.workflow.clients.wfxml.ObserverRegistry.ObserverInfo;
import de.danet.an.workflow.omgcore.HistoryNotAvailableException;
import de.danet.an.workflow.omgcore.InvalidDataException;
import de.danet.an.workflow.omgcore.InvalidStateException;
import de.danet.an.workflow.omgcore.ProcessData;
import de.danet.an.workflow.omgcore.ProcessDataInfo;
import de.danet.an.workflow.omgcore.ResultNotAvailableException;
import de.danet.an.workflow.omgcore.TransitionNotAllowedException;
import de.danet.an.workflow.omgcore.UpdateNotAllowedException;
import de.danet.an.workflow.omgcore.WfAuditEvent;
import de.danet.an.workflow.omgcore.WfCreateProcessAuditEvent;
import de.danet.an.workflow.omgcore.WfProcess;
import de.danet.an.workflow.omgcore.WfResource;
import de.danet.an.workflow.omgcore.WfStateAuditEvent;
/**
* This class provides the methods of an {@link AbstractResponseGenerator}
* that are relevant for the instance.
*
* <p>
* The <em>Instance</em> resource is the actual "performance of work"; it is the
* process instance. It embodies the context information that distinguishes one
* process instance from another. Some people call this a "case". A process
* instance resource can be used only once: it is created, then it can be
* started, it can be paused, resumed, terminated. If things go normally, it
* will eventually complete.
* </p>
*
* @author Dirk Schnelle
*
*/
class InstanceResponseGenerator extends AbstractResponseGenerator {
/** Logger instance. */
private static final org.apache.commons.logging.Log logger
= org.apache.commons.logging.LogFactory.getLog
(InstanceResponseGenerator.class);
/**
* Constructs a new object.
*
* @param observerRegistry the observer registry.
* @param wfs Reference to the workflow engine.
* @param resRef the resource reference.
*/
public InstanceResponseGenerator
(ObserverRegistry observerRegistry,
WorkflowService wfs, ResourceReference resRef){
super(observerRegistry, wfs, resRef);
}
/* (non-Javadoc)
* @see de.danet.an.workflow.clients.wfxml.AbstractResponseGenerator
*/
public void evaluate(SOAPMessage reqMsg, SOAPMessage respMsg)
throws SOAPException, RemoteException {
SOAPBodyElement actionElement = getActionElement(reqMsg);
String actName = actionElement.getElementName().getLocalName();
if (actName.equals (Consts.GET_PROPERTIES_REQUEST)) {
getInstanceProperties(reqMsg, respMsg);
} else if (actName.equals (Consts.SET_PROPERTIES_REQUEST)) {
setInstanceProperties(actionElement, reqMsg, respMsg);
} else if (actName.equals (Consts.SUBSCRIBE_REQUEST)) {
subscribe(actionElement, reqMsg, respMsg);
} else if (actName.equals (Consts.UNSUBSCRIBE_REQUEST)) {
unsubscribe(actionElement, reqMsg, respMsg);
} else if (actName.equals (Consts.LIST_ACTIVITIES_REQUEST)) {
listActivities(reqMsg, respMsg);
} else if (actName.equals (Consts.CHANGE_STATE_REQUEST)) {
changeState(actionElement, reqMsg, respMsg);
} else if (actName.equals (Consts.TERMINATE_REQUEST)) {
terminate(reqMsg, respMsg);
} else {
if (logger.isDebugEnabled()) {
logger.debug("unknown action '" + actName + "'");
}
FaultUtils.setFault(respMsg,
ASAPException.ASAP_INVALID_OPERATION_SPECIFICATION,
getResourceName() + ": Unknown action \"" + actName
+ "\".");
}
}
/* (non-Javadoc)
* @see de.danet.an.workflow.clients.wfxml.AbstractResponseGenerator#getSender()
*/
protected String getResourceName() {
return RESOURCE_INSTANCE;
}
/**
* Creates a response that contains properties of the process instance.
*
* @param reqMsg the request message.
* @param respMsg the response message
* @throws SOAPException
* error evaluating the request or constructing the response.
* @throws RemoteException
* error accessing the workflow engine.
*/
private void getInstanceProperties(SOAPMessage reqMsg, SOAPMessage respMsg)
throws SOAPException, RemoteException {
if (logger.isDebugEnabled()) {
logger.debug("get instance properties...");
}
String receiverKey = getHeaderValue(reqMsg, Consts.RECEIVER_KEY);
Process proc = null;
try {
proc = (Process) getProcess(receiverKey);
} catch (InvalidKeyException e) {
FaultUtils.setFault(respMsg,
ASAPException.ASAP_INVALID_INSTANCE_KEY, e.getMessage());
return;
} catch (NoSuchElementException e) {
FaultUtils.setFault(respMsg,
ASAPException.ASAP_INVALID_INSTANCE_KEY, e.getMessage());
return;
}
SOAPBodyElement propsNode
= createAsapResponseNode(respMsg, Consts.GET_PROPERTIES_RESPONSE);
try {
appendInstanceProperties(respMsg, receiverKey, proc, propsNode);
} catch (ResultNotAvailableException e) {
FaultUtils.setFault(respMsg, e);
return;
}
}
/**
* Reads the properties from the process instance and appends them to the
* parent.
* @param respMsg the response message.
* @param receiverKey this activity instance
* @param parent the parent node of the response message.
* @param proc the process
* @throws SOAPException
* error appending to the parent node
* @throws RemoteException
* error accessing the process
* @throws SAXException
* @throws TransformerException
* @throws ResultNotAvailableException
*/
private void appendInstanceProperties(SOAPMessage respMsg,
String receiverKey, Process proc, SOAPBodyElement propsNode)
throws SOAPException, RemoteException, ResultNotAvailableException {
SOAPElement keyNode
= propsNode.addChildElement("Key", Consts.ASAP_PREFIX);
keyNode.addTextNode (getResourceReference().getResourceKey());
SOAPElement state
= propsNode.addChildElement("State", Consts.ASAP_PREFIX);
String procState = proc.state();
state.addTextNode(StateMapper.omg2asapState(procState));
SOAPElement nameNode
= propsNode.addChildElement("Name", Consts.ASAP_PREFIX);
nameNode.addTextNode(proc.name());
SOAPElement subjectNode
= propsNode.addChildElement("Subject", Consts.ASAP_PREFIX);
if (proc.description() != null) {
subjectNode.addTextNode(proc.description());
}
SOAPElement descNode
= propsNode.addChildElement("Description", Consts.ASAP_PREFIX);
if (proc.description() != null) {
descNode.addTextNode(proc.description());
}
SOAPElement factoryKey
= propsNode.addChildElement("FactoryKey", Consts.ASAP_PREFIX);
ResourceReference procDefRes = new ResourceReference
(getResourceReference().getBaseUrl(), proc.processDefinition());
factoryKey.addTextNode(procDefRes.getResourceKey());
appendObservers(respMsg, proc, propsNode);
appendHistory(respMsg, proc, propsNode);
appendContextData(respMsg, proc, propsNode);
appendResultData(respMsg, proc, propsNode);
}
/**
* Appends the list of observers to the given node in a properties request.
* @param respMsg the message to send
* @param proc the related process
* @param propsNode the parent node
* @throws RemoteException
* Error accessing the workflow engine
* @throws SOAPException
* Error creating the message
*/
private void appendObservers(SOAPMessage respMsg, Process proc,
SOAPBodyElement propsNode) throws RemoteException, SOAPException {
Collection observers;
try {
ObserverRegistry obs = getObserverRegistry();
ProcessDefinition procdef = proc.processDefinition();
observers = obs.getObservers(procdef.packageId(),
procdef.processId(), proc.key());
} catch (SQLException e) {
FaultUtils.setFault(respMsg, e);
return;
}
if (observers.size() > 0) {
SOAPElement observersNode
= propsNode.addChildElement("Observers", Consts.ASAP_PREFIX);
Iterator iterator = observers.iterator();
while (iterator.hasNext()) {
ObserverInfo observerInfo = (ObserverInfo) iterator.next();
SOAPElement observerKey
= observersNode.addChildElement("ObserverKey",
Consts.ASAP_PREFIX);
observerKey.addTextNode(observerInfo.getObserverKey());
}
}
}
/**
* Appends the history of the process to the given node in a properties
* request.
* @param respMsg the message to send
* @param proc the related process
* @param propsNode the parent node
* @throws RemoteException
* Error accessing the workflow engine
* @throws SOAPException
* Error creating the message
*/
private void appendHistory(SOAPMessage respMsg, Process proc,
SOAPBodyElement propsNode) throws RemoteException, SOAPException {
SOAPElement historyNode
= propsNode.addChildElement("History", Consts.ASAP_PREFIX);
Collection history;
try {
history = proc.history();
} catch (HistoryNotAvailableException e) {
// well, if there is nothing to append...
return;
}
Iterator iterator = history.iterator();
while (iterator.hasNext()) {
WfAuditEvent event = (WfAuditEvent) iterator.next();
String asapEventType = getAsapEventType(event);
if (asapEventType == null) {
continue;
}
SOAPElement eventNode
= historyNode.addChildElement("Event", Consts.ASAP_PREFIX);
Date date = event.timeStamp();
SOAPElement timeNode
= eventNode.addChildElement("Time", Consts.ASAP_PREFIX);
timeNode.addTextNode(XMLUtil.toXsdGMTDateTime(date));
SOAPElement type
= eventNode.addChildElement("EventType", Consts.ASAP_PREFIX);
type.addTextNode(asapEventType);
SOAPElement sourceKey
= eventNode.addChildElement("SourceKey", Consts.ASAP_PREFIX);
sourceKey.addTextNode("");
SOAPElement details
= eventNode.addChildElement("Details", Consts.ASAP_PREFIX);
details.addTextNode("");
String newState;
String oldState;
if (event instanceof WfCreateProcessAuditEvent) {
// The old state is not defined, and we are not allowed to
// add an empty string.
oldState = "open.notrunning";
newState = "open.notrunning";
} else {
WfStateAuditEvent stateEvent = (WfStateAuditEvent) event;
oldState = StateMapper.omg2asapState(stateEvent.oldState());
newState = StateMapper.omg2asapState(stateEvent.newState());
}
SOAPElement oldStateNode
= eventNode.addChildElement("OldState", Consts.ASAP_PREFIX);
oldStateNode.addTextNode(oldState);
SOAPElement newStateNode
= eventNode.addChildElement("NewState", Consts.ASAP_PREFIX);
newStateNode.addTextNode(newState);
}
}
/**
* Gets the ASAP description for the {@link WfAuditEvent}.
* @param event the event
* @return ASAP representation of the event or <code>null</null>
* if no corresponding ASAP event exists..
*/
private String getAsapEventType(WfAuditEvent event) {
String type = event.eventType();
if (type.equals(WfAuditEvent.PROCESS_CREATED)) {
return "InstanceCreated";
}
if (type.equals(WfAuditEvent.PROCESS_STATE_CHANGED)) {
return "StateChanged";
}
return null;
}
/**
* Appends the context data to the given node in a properties request.
* @param respMsg the message to send
* @param proc the related process
* @param propsNode the parent node
* @throws RemoteException
* Error accessing the workflow engine
* @throws SOAPException
* Error creating the message
* @throws SAXException
* @throws TransformerException
*/
private void appendContextData(SOAPMessage respMsg, Process proc,
SOAPBodyElement propsNode)
throws RemoteException, SOAPException {
SOAPElement contextData
= propsNode.addChildElement("ContextData", Consts.ASAP_PREFIX);
contextData.addNamespaceDeclaration
(Consts.CD_PREFIX, getResourceReference().getNamespace());
ProcessData context = proc.processContext();
Iterator iterator = context.keySet().iterator();
while (iterator.hasNext()) {
String name = (String) iterator.next();
Object value = context.get(name);
SOAPElement dataNode
= contextData.addChildElement(name, Consts.CD_PREFIX);
maybeAddObjectAsTextNode(respMsg, value, dataNode);
}
}
/**
* Appends the result data to the given node in a properties request.
* @param respMsg the message to send
* @param proc the related process
* @param propsNode the parent node
* @throws RemoteException
* Error accessing the workflow engine
* @throws SOAPException
* Error creating the message
* @throws ResultNotAvailableException
* @throws SAXException
* @throws TransformerException
*/
private void appendResultData(SOAPMessage respMsg, Process proc,
SOAPBodyElement propsNode)
throws RemoteException, SOAPException, ResultNotAvailableException {
SOAPElement resultData
= propsNode.addChildElement("ResultData", Consts.ASAP_PREFIX);
ProcessDefinition procdef = proc.processDefinition();
ProcessDataInfo resultSignature = procdef.resultSignature();
resultData.addNamespaceDeclaration
(Consts.RS_PREFIX, getResourceReference().getNamespace());
ProcessData result;
result = proc.result();
Iterator iterator = resultSignature.keySet().iterator();
while (iterator.hasNext()) {
String name = (String) iterator.next();
Object value = result.get(name);
SOAPElement dataNode
= resultData.addChildElement(name, Consts.RS_PREFIX);
maybeAddObjectAsTextNode(respMsg, value, dataNode);
}
}
/**
* Adds the given <code>value</code> as a text node (XSD conform), if it
* not <code>null</code>.
* @param message the current message.
* @param value the object to add.
* @param parent the parent node.
* @throws SOAPException
* @throws TransformerException
* @throws SAXException
*/
private void maybeAddObjectAsTextNode
(SOAPMessage message, Object value, SOAPElement parent)
throws SOAPException {
if (value == null) {
return;
}
if (value instanceof org.w3c.dom.Element) {
importW3CNodeAsChild(parent, (org.w3c.dom.Element) value);
} else if (value instanceof SAXEventBuffer) {
importSAXAsChild(message, parent, (SAXEventBuffer) value);
} else {
String text;
if (value instanceof Date) {
text = XMLUtil.toXsdGMTDateTime((Date) value);
} else {
text = value.toString();
}
parent.addTextNode(text);
}
}
/**
* Sets properties of the process.
* @param action the action element.
* @param reqMsg the request message.
* @param respMsg the response message
* @throws SOAPException
* error evaluating the request or constructing the response.
* @throws RemoteException
* error accessing the workflow engine.
*/
private void setInstanceProperties(SOAPElement action, SOAPMessage reqMsg,
SOAPMessage respMsg)
throws SOAPException, RemoteException {
if (logger.isDebugEnabled()) {
logger.debug("set instance properties...");
}
String receiverKey = getHeaderValue(reqMsg, Consts.RECEIVER_KEY);
Process proc = null;
try {
proc = (Process) getProcess(receiverKey);
} catch (InvalidKeyException e) {
FaultUtils.setFault(respMsg,
ASAPException.ASAP_INVALID_INSTANCE_KEY, e.getMessage());
return;
} catch (NoSuchElementException e) {
FaultUtils.setFault(respMsg,
ASAPException.ASAP_INVALID_INSTANCE_KEY, e.getMessage());
return;
}
String description = getChildsTextContent(action, "Description");
if (description != null) {
proc.setDescription(description);
}
SOAPElement data = findChildNode(action, "Data");
if (data != null) {
ProcessDefinition procdef = proc.processDefinition();
ProcessData procData;
try {
procData = getProcessData(procdef, data);
proc.setProcessContext(procData);
} catch (ParseException e) {
FaultUtils.setFault(respMsg,
ASAPException.ASAP_INVALID_INSTANCE_KEY,
e.getMessage());
return;
} catch (InvalidDataException e) {
FaultUtils.setFault(respMsg,
ASAPException.ASAP_INVALID_INSTANCE_KEY,
e.getMessage());
return;
} catch (UpdateNotAllowedException e) {
FaultUtils.setFault(respMsg,
ASAPException.ASAP_INVALID_INSTANCE_KEY,
e.getMessage());
return;
}
}
SOAPBodyElement propsNode
= createAsapResponseNode(respMsg, Consts.SET_PROPERTIES_RESPONSE);
try {
appendInstanceProperties(respMsg, receiverKey, proc, propsNode);
} catch (ResultNotAvailableException e) {
FaultUtils.setFault(respMsg, e);
return;
}
}
/**
* Extracts the process data from the given context data.
* @param contextData the context data.
* @return process data to set in the the process.
* @throws RemoteException
* @throws SOAPException
* @throws ParseException
*/
private ProcessData getProcessData(ProcessDefinition procdef,
SOAPElement contextData)
throws RemoteException, SOAPException, ParseException {
ProcessDataInfo info = procdef.contextSignature();
ProcessData procData = new DefaultProcessData();
for (Iterator pdi = contextData.getChildElements(); pdi.hasNext();) {
SOAPElement current = (SOAPElement) pdi.next();
String pdname = current.getLocalName();
Object type = info.get(pdname);
if (type == null) {
throw new SOAPException("process does not contain a variable "
+ "with name " + pdname);
}
String pdvalue = XMLUtil.getFirstLevelTextContent(current);
Object value;
if (type.equals(String.class)) {
value = pdvalue;
} else if (type.equals(Long.class)) {
value = new Long(pdvalue);
} else if (type.equals(Double.class)) {
value = new Double(XMLUtil.parseXsdDouble(pdvalue));
} else if (type.equals(Boolean.class)) {
value = new Boolean(XMLUtil.parseXsdBoolean(pdvalue));
} else if (type.equals(Date.class)) {
value = XMLUtil.parseXsdDateTime(pdvalue);
} else if (type.equals(org.w3c.dom.Element.class)) {
// TODO: Check other values for schema types.
value = current.getFirstChild();
} else {
// cannot resolve the type. leave it as a string and pray.
value = pdvalue;
}
procData.put(pdname, value);
}
return procData;
}
/**
* Subscribes to a process instance.
* @param instanceref the resource reference of the instance
* @param action the action element.
* @param reqMsg the request message.
* @param respMsg the response message
* @throws SOAPException
* error evaluating the request or constructing the response.
* @throws RemoteException
* error accessing the workflow engine.
*/
private void subscribe
(SOAPElement action, SOAPMessage reqMsg, SOAPMessage respMsg)
throws SOAPException, RemoteException {
String receiverKey = getHeaderValue(reqMsg, Consts.RECEIVER_KEY);
Process proc = null;
try {
proc = (Process) getProcess(receiverKey);
} catch (InvalidKeyException e) {
FaultUtils.setFault(respMsg,
ASAPException.ASAP_INVALID_INSTANCE_KEY,
e.getMessage());
return;
} catch (NoSuchElementException e) {
FaultUtils.setFault(respMsg,
ASAPException.ASAP_INVALID_INSTANCE_KEY,
e.getMessage());
return;
}
String observer = getChildsTextContent(action, "ObserverKey");
if (observer == null) {
FaultUtils.setFault(respMsg, ASAPException.ASAP_ELEMENT_MISSING,
"Missing observer key!");
return;
}
ProcessDefinition procdef = proc.processDefinition();
try {
ObserverRegistry obs = getObserverRegistry();
obs.subscribe
(observer, procdef.packageId(),
procdef.processId(), proc.key(),
getResourceReference().getBaseUrl());
} catch (SQLException e) {
FaultUtils.setFault(respMsg, e);
return;
}
createAsapResponseNode(respMsg, Consts.SUBSCRIBE_RESPONSE);
}
/**
* Unsubscribes from a process instance.
* @param action the action element.
* @param reqMsg the request message.
* @param respMsg the response message
* @throws SOAPException
* error evaluating the request or constructing the response.
* @throws RemoteException
* error accessing the workflow engine.
*/
private void unsubscribe(SOAPElement action, SOAPMessage reqMsg,
SOAPMessage respMsg)
throws SOAPException, RemoteException {
String receiverKey = getHeaderValue(reqMsg, Consts.RECEIVER_KEY);
Process proc = null;
try {
proc = (Process) getProcess(receiverKey);
} catch (InvalidKeyException e) {
FaultUtils.setFault(respMsg,
ASAPException.ASAP_INVALID_INSTANCE_KEY,
e.getMessage());
return;
} catch (NoSuchElementException e) {
FaultUtils.setFault(respMsg,
ASAPException.ASAP_INVALID_INSTANCE_KEY,
e.getMessage());
return;
}
String observer = getChildsTextContent(action, "ObserverKey");
if (observer == null) {
FaultUtils.setFault(respMsg, ASAPException.ASAP_ELEMENT_MISSING,
"Missing observer key!");
return;
}
ProcessDefinition procdef = proc.processDefinition();
try {
ObserverRegistry obs = getObserverRegistry();
obs.unsubscribe(observer, procdef.packageId(), procdef.processId(),
proc.key());
} catch (SQLException e) {
FaultUtils.setFault(respMsg, e);
return;
}
createAsapResponseNode(respMsg, Consts.UNSUBSCRIBE_RESPONSE);
}
/**
* Returns a list of currently active activities.
* @param reqMsg the request message.
* @param respMsg the response message
* @throws SOAPException
* error evaluating the request or constructing the response.
* @throws RemoteException
* error accessing the workflow engine.
*/
private void listActivities(SOAPMessage reqMsg, SOAPMessage respMsg)
throws SOAPException, RemoteException {
String receiverKey = getHeaderValue(reqMsg, Consts.RECEIVER_KEY);
WfProcess proc = null;
try {
proc = getProcess(receiverKey);
} catch (InvalidKeyException e) {
FaultUtils.setFault(respMsg,
ASAPException.ASAP_INVALID_INSTANCE_KEY, e.getMessage());
return;
} catch (NoSuchElementException e) {
FaultUtils.setFault(respMsg,
ASAPException.ASAP_INVALID_INSTANCE_KEY, e.getMessage());
return;
}
Collection activities;
try {
activities = proc.activitiesInState("open.running");
} catch (InvalidStateException e) {
FaultUtils.setFault(respMsg, ASAPException.ASAP_OPERATION_FAILED,
e.getMessage());
return;
}
SOAPBodyElement activitiesNode = createWfxmlResponseNode(respMsg,
Consts.LIST_ACTIVITIES_RESPONSE);
activitiesNode.addNamespaceDeclaration(Consts.ASAP_PREFIX,
Consts.ASAP_NS);
Iterator iterator = activities.iterator();
while(iterator.hasNext()) {
Activity activity = (Activity) iterator.next();
SOAPElement activityNode
= activitiesNode.addChildElement("ActivityInfo",
Consts.WFXML_PREFIX);
SOAPElement activityKey
= activityNode.addChildElement("ActivityKey",
Consts.WFXML_PREFIX);
ResourceReference actResRef = new ResourceReference
(getResourceReference().getBaseUrl(), activity);
activityKey.addTextNode (actResRef.getResourceKey());
SOAPElement name = activityNode.addChildElement("Name",
Consts.ASAP_PREFIX);
String val = activity.name();
maybeAddTextNode(name, val);
SOAPElement description = activityNode
.addChildElement("Description", Consts.ASAP_PREFIX);
val = activity.description();
maybeAddTextNode(description, val);
Collection assignments = activity.assignments();
Iterator assignmentIterator = assignments.iterator();
while (assignmentIterator.hasNext()) {
Assignment assignment = (Assignment) iterator.next();
WfResource resource = assignment.assignee();
SOAPElement assignee = activityNode.addChildElement("Assignee",
Consts.WFXML_PREFIX);
val = resource.resourceName();
if (val == null) {
assignee.addTextNode(resource.resourceKey());
} else {
assignee.addTextNode(val);
}
}
}
}
/**
* Changes the state of this process.
* @param action the element containing the request.
* @param reqMsg the request message.
* @param respMsg the response message
* @throws SOAPException
* error evaluating the request or constructing the response.
* @throws RemoteException
* error accessing the workflow engine.
*/
private void changeState(SOAPElement action, SOAPMessage reqMsg,
SOAPMessage respMsg)
throws SOAPException, RemoteException {
String receiverKey = getHeaderValue(reqMsg, Consts.RECEIVER_KEY);
WfProcess proc = null;
try {
proc = getProcess(receiverKey);
} catch (InvalidKeyException e) {
FaultUtils.setFault(respMsg,
ASAPException.ASAP_INVALID_INSTANCE_KEY, e.getMessage());
return;
} catch (NoSuchElementException e) {
FaultUtils.setFault(respMsg,
ASAPException.ASAP_INVALID_INSTANCE_KEY, e.getMessage());
return;
}
String requestedAsapState = getChildsTextContent(action, "State");
String newAsapState;
try {
if (logger.isDebugEnabled()) {
logger.debug("changing state of " + proc.name() + "/"
+ proc.key() + " to '" + requestedAsapState + "'...");
}
String requestedOmgState
= StateMapper.asap2omgState(requestedAsapState);
proc.changeState(requestedOmgState);
String newOmgState = proc.state();
newAsapState = StateMapper.omg2asapState(newOmgState);
} catch (InvalidStateException e) {
FaultUtils.setFault(respMsg,
ASAPException.ASAP_INVALID_CONTEXT_DATA, e.getMessage());
return;
} catch (TransitionNotAllowedException e) {
FaultUtils.setFault(respMsg,
ASAPException.ASAP_INVALID_STATE_TRANSITION,
e.getMessage());
return;
}
SOAPBodyElement stateNode = createAsapResponseNode(respMsg,
Consts.CHANGE_STATE_RESPONSE);
stateNode.addNamespaceDeclaration(Consts.ASAP_PREFIX, Consts.ASAP_NS);
SOAPElement newStateElement
= stateNode.addChildElement("State", Consts.ASAP_PREFIX);
newStateElement.addTextNode(newAsapState);
if (logger.isDebugEnabled()) {
logger.debug("changed state of " + proc.name() + "/" + proc.key()
+ " from " + requestedAsapState + " to " + newAsapState);
}
}
/**
* Terminates the process instance.
*
* <p>
* The specification is inconsistent at this point. The WfXML 20
* specification redirects to the ASAP specification where terminate should
* be defined. This specification in the ASAP documentation is missing
* (in the xsd as well). So we leave it unimplemented at this point.
* If the implementation is more clear, this is the point where the
* implementation must go.
* </p>
*
* @param reqMsg the request message.
* @param respMsg the response message
* @throws SOAPException
* error evaluating the request or constructing the response.
* @throws RemoteException
* error accessing the workflow engine.
*/
private void terminate(SOAPMessage reqMsg, SOAPMessage respMsg)
throws SOAPException, RemoteException {
FaultUtils.setFault(respMsg, ASAPException.ASAP_OPERATION_FAILED,
Consts.TERMINATE_REQUEST
+ " is not implemented due to a missing specification!");
}
/**
* Gets the process manager that that can handle the package id, the
* process id, and the process key that are encoded in the given uri.
* @param uri the uri with package id, process id and process key.
* @return process.
* @throws RemoteException
* error accessing the workflow engine
* @throws InvalidKeyException
* process manager or process does not exist
*/
private WfProcess getProcess(String uri)
throws RemoteException, InvalidKeyException {
String packageId = getResourceReference().getPackageId();
String processId = getResourceReference().getProcessId();
String processKey = getResourceReference().getProcessKey();
if (logger.isDebugEnabled()) {
logger.debug("finding process definition'" + packageId + "/"
+ processId + "/" + processKey + "'...");
}
ProcessDirectory pd = getWorkflowService().processDirectory();
WfProcess proc
= pd.lookupProcess(packageId + "/" + processId, processKey);
return proc;
}
/**
* Creates a state changed notification message for the given observer.
* @param observer the observer
* @param newState the new state of the process
* @param oldState the old state of the process
* @return notification message.
* @throws SOAPException
* error creating the message.
*/
public SOAPMessage createStateChangedMessage
(String observer, String newState, String oldState)
throws SOAPException {
MessageFactory messageFactory = MessageFactory.newInstance();
SOAPMessage message = messageFactory.createMessage();
SOAPEnvelope env = message.getSOAPPart().getEnvelope();
SOAPHeader header = env.getHeader ();
SOAPHeaderElement req = header.addHeaderElement
(env.createName("Request", "as", Consts.ASAP_NS));
SOAPElement receiverkey
= req.addChildElement("ReceiverKey", Consts.ASAP_PREFIX);
receiverkey.addTextNode(observer);
SOAPElement senderkey = req.addChildElement("SenderKey",
Consts.ASAP_PREFIX);
senderkey.addTextNode(getResourceReference().getResourceKey());
SOAPElement responseRequired = req.addChildElement("ResponseRequired",
Consts.ASAP_PREFIX);
responseRequired.addTextNode("No");
SOAPBody body = env.getBody();
Name respName = env.createName(Consts.STATE_CHANGE_REQUEST,
Consts.ASAP_PREFIX, Consts.ASAP_NS);
SOAPBodyElement action = body.addBodyElement(respName);
SOAPElement newStateNode
= action.addChildElement("State", Consts.ASAP_PREFIX);
newStateNode.addTextNode(newState);
SOAPElement oldStateNode
= action.addChildElement("PreviousState", Consts.ASAP_PREFIX);
oldStateNode.addTextNode(oldState);
return message;
}
/**
* Creates a completed notification message for a given observer.
* @param observer the observer
* @return notification message.
* @throws SOAPException
* error creating the message.
* @throws SAXException
* @throws TransformerException
* @throws ResultNotAvailableException
* @throws RemoteException
*/
public SOAPMessage createCompletedMessage
(String observer, ProcessData result)
throws SOAPException, RemoteException {
MessageFactory messageFactory = MessageFactory.newInstance();
SOAPMessage message = messageFactory.createMessage();
SOAPEnvelope env = message.getSOAPPart().getEnvelope();
SOAPHeader header = env.getHeader ();
SOAPHeaderElement req = header.addHeaderElement
(env.createName("Request", "as", Consts.ASAP_NS));
SOAPElement receiverKey
= req.addChildElement("ReceiverKey", Consts.ASAP_PREFIX);
receiverKey.addTextNode(observer);
SOAPElement senderKey = req.addChildElement("SenderKey",
Consts.ASAP_PREFIX);
senderKey.addTextNode(getResourceReference().getResourceKey());
SOAPElement responseRequired = req.addChildElement("ResponseRequired",
Consts.ASAP_PREFIX);
responseRequired.addTextNode("No");
SOAPBody body = env.getBody();
Name respName = env.createName(Consts.COMPLETED_REQUEST,
Consts.ASAP_PREFIX, Consts.ASAP_NS);
SOAPBodyElement action = body.addBodyElement(respName);
appendResultData(message, result, action);
return message;
}
/**
* Appends the result data to the given node in a properties request.
* @param respMsg the message to send
* @param result result of the process.
* @param parent the parent node
* @throws RemoteException
* Error accessing the workflow engine
* @throws SOAPException
* Error creating the message
* @throws ResultNotAvailableException
* @throws SAXException
* @throws TransformerException
*/
private void appendResultData(SOAPMessage respMsg, ProcessData result,
SOAPBodyElement parent)
throws RemoteException, SOAPException {
SOAPElement resultData
= parent.addChildElement("ResultData", Consts.ASAP_PREFIX);
if (result == null) {
return;
}
Iterator iterator = result.keySet().iterator();
while (iterator.hasNext()) {
String name = (String) iterator.next();
Object value = result.get(name);
SOAPElement dataNode
= resultData.addChildElement(name, Consts.RS_PREFIX);
maybeAddObjectAsTextNode(respMsg, value, dataNode);
}
}
}