/*
* 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: AbstractResponseGenerator.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/28 13:19:36 drmlipp
* Fixed charset problem.
*
* Revision 1.8 2007/02/16 21:00:21 mlipp
* Fixed some null pointer problems.
*
* Revision 1.7 2007/02/01 10:08:36 drmlipp
* Removed no longer used observer resource.
*
* 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 13:42:02 schnelle
* Added convenient method to retrieve a boolean parameter.
*
* Revision 1.3 2007/01/31 12:24:04 drmlipp
* Design revisited.
*
* Revision 1.2 2007/01/30 11:56:14 drmlipp
* Merged Wf-XML branch.
*
* Revision 1.1.2.26 2007/01/29 15:04:20 schnelle
* Renaming of Observer to ObserverRegistry and URIDecoder to ResourceReference.
*
* Revision 1.1.2.25 2007/01/29 13:50:35 drmlipp
* Minor cleanup.
*
* Revision 1.1.2.24 2007/01/29 13:40:32 schnelle
* Storing of the sender base in the servlet context.
*
* Revision 1.1.2.23 2007/01/29 13:10:26 drmlipp
* Using utilities method for timestamp convertion.
*
* Revision 1.1.2.22 2007/01/26 15:50:28 schnelle
* Added encoding for process id and package id.
*
* Revision 1.1.2.21 2007/01/24 10:56:50 schnelle
* Prepared return of a result for aobservers.
*
* Revision 1.1.2.20 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.19 2007/01/19 07:59:35 schnelle
* Corrected return value for factory list instances.
*
* Revision 1.1.2.18 2007/01/16 11:05:42 schnelle
* Refactoring: Moved subscription handling methods to own class.
*
* Revision 1.1.2.17 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.16 2007/01/11 10:23:52 schnelle
* Creation of StateChanged notifications.
*
* Revision 1.1.2.15 2006/12/20 14:37:59 schnelle
* Implemented Factory GetDefinition.
*
* Revision 1.1.2.14 2006/12/20 13:32:25 schnelle
* Basic implementato of GetProperties for Instance and Activity.
*
* Revision 1.1.2.13 2006/12/19 14:43:29 schnelle
* Implementation of GetProperties for ServiceRegistry and Factory.
*
* Revision 1.1.2.12 2006/12/18 14:41:03 schnelle
* Preparatation for individual schema definition for each getproperties request.
*
* Revision 1.1.2.11 2006/12/18 11:56:51 schnelle
* Returning the XPDL node after a NewDefiniton to the ServiceRegistry.
*
* Revision 1.1.2.10 2006/12/14 08:50:21 schnelle
* Implemented CompleteActivity.
*
* Revision 1.1.2.9 2006/12/13 11:23:48 schnelle
* Implemented instance ListActivities.
*
* Revision 1.1.2.8 2006/12/12 13:24:38 schnelle
* Introduction of ASAPException to provide a detailed mesage.
*
* Revision 1.1.2.7 2006/12/11 11:05:34 schnelle
* Added template methods for all requests.
*
* Revision 1.1.2.6 2006/12/01 12:49:54 schnelle
* Basic import of context data for process creation.
*
* Revision 1.1.2.5 2006/11/30 12:45:08 schnelle
* Basic implementation of Factory CreateInstance.
*
* Revision 1.1.2.4 2006/11/29 14:12:37 schnelle
* Take respect to namespaces of asap requests and responses.
*
* Revision 1.1.2.3 2006/11/29 11:05:22 schnelle
* Full implementation of the request and response headers.
*
* Revision 1.1.2.2 2006/11/28 15:31:51 schnelle
* Proper selection of the response generator.
*
* Revision 1.1.2.1 2006/11/28 12:20:09 schnelle
* Creation of a separate class to handle the issues for a specific resource.
*
*/
package de.danet.an.workflow.clients.wfxml;
import java.io.ByteArrayOutputStream;
import java.rmi.RemoteException;
import java.util.Iterator;
import javax.xml.soap.Name;
import javax.xml.soap.Node;
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.SOAPFactory;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPHeaderElement;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.soap.Text;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.xml.sax.SAXException;
import de.danet.an.util.XMLUtil;
import de.danet.an.util.sax.HandlerStack;
import de.danet.an.util.soap.SOAPBuilder;
import de.danet.an.workflow.api.SAXEventBuffer;
import de.danet.an.workflow.api.WorkflowService;
/**
* This class provides the basic functionality of a SOAP based interface between
* the workflow engine (i.e. its processes) and any SOAP based client.
*
* @author Dirk Schnelle
*/
abstract class AbstractResponseGenerator {
/** Connection to the workflow service. */
private WorkflowService wfs = null;
/** The URI decoder. */
private ResourceReference resourceReference;
public static final String RESOURCE_SERVICE_REGISTRY = "ServiceRegistry";
public static final String RESOURCE_FACTORY = "Factory";
public static final String RESOURCE_INSTANCE = "Instance";
public static final String RESOURCE_ACTIVITY = "Activity";
private final ObserverRegistry observerRegistry;
/**
* Constructs a new object.
*
* @param observerRegistry the observer registry
* @param wfs Reference to the workflow engine.
* @param resRef the resource reference.
*/
public AbstractResponseGenerator
(ObserverRegistry observerRegistry, WorkflowService wfs,
ResourceReference resRef){
this.observerRegistry = observerRegistry;
this.wfs = wfs;
this.resourceReference = resRef;
}
/**
* Retrieves the observer registry.
* @return the observer registry.
*/
protected ObserverRegistry getObserverRegistry() {
return observerRegistry;
}
/**
* @return Returns the resourceReference.
*/
public ResourceReference getResourceReference() {
return resourceReference;
}
/**
* @return Returns the workflow service.
*/
public WorkflowService getWorkflowService() {
return wfs;
}
/**
* Retrieves the text content of the child node with the specified name.
* @param message the message to inspect.
* @param element the parent node.
* @param prefix prefix of the searched child node.
* @param name name of the searched child node.
* @return text content, or <code>null</code> if there is no such child
* node.
* @throws SOAPException
* error inspecting the SOAP message.
*/
protected String getChildTextContent
(SOAPMessage message, SOAPElement element, String prefix, String name)
throws SOAPException {
SOAPPart part = message.getSOAPPart();
SOAPEnvelope envelope = part.getEnvelope();
Name nm = envelope.createName(name, prefix, Consts.ASAP_NS);
Iterator i = element.getChildElements(nm);
if (!i.hasNext()) {
return null;
}
SOAPElement child = (SOAPElement) i.next();
return XMLUtil.getFirstLevelTextContent(child);
}
/**
* Retrieves the first element of the given request body that contains the
* requested action to perform. It is guaranteed that the element has the
* correct namespace.
* @param request the current request.
* @return action element
* @throws SOAPException
* error evaluating the request.
*/
protected SOAPBodyElement getActionElement(SOAPMessage request)
throws SOAPException {
SOAPBody body = request.getSOAPBody();
for (Iterator i = body.getChildElements(); i.hasNext();) {
Node node = (Node)i.next();
if (node instanceof SOAPBodyElement) {
SOAPBodyElement bodyElement = (SOAPBodyElement) node;
Name bodyElementName = bodyElement.getElementName();
String headerUri = bodyElementName.getURI();
if (headerUri.equals(Consts.ASAP_NS)
|| headerUri.equals(Consts.WFXML_NS)) {
return bodyElement;
}
}
}
throw new SOAPException("Missing body.");
}
/**
* Handles the given request.
* @param reqMsg the request message.
* @param respMsg the response message.
* @throws SOAPException
* Error in the underlying SOAP protocol.
* @throws RemoteException
* Error accessing the workflow engine.
*/
public abstract void evaluate(SOAPMessage reqMsg, SOAPMessage respMsg)
throws SOAPException, RemoteException;
/**
* Retrieves the name of this resource.
* @return Name of the sender instance.
*/
protected abstract String getResourceName();
/**
* Retrieves the ASAP <code>key</code> from the header of the message.
* @param message the message to inspect.
* @param key the key to find.
* @return value for the <code>key</code>, <code>null</code> if the key
* could not be found.
* @throws SOAPException
* error inspecting the SOAP message.
*/
protected String getHeaderValue(SOAPMessage message, String key)
throws SOAPException {
SOAPHeaderElement headerElement = null;
SOAPHeader header = message.getSOAPHeader();
for (Iterator i = header.getChildElements(); i.hasNext();) {
Node node = (Node)i.next();
if (node instanceof SOAPHeaderElement) {
headerElement = (SOAPHeaderElement) node;
Name headerElementName = headerElement.getElementName();
String localName = headerElementName.getLocalName();
if (localName.equals(Consts.REQUEST_HEADER)
|| localName.equals(Consts.RESPONSE_HEADER)) {
String headerUri = headerElementName.getURI();
if (headerUri.equals(Consts.ASAP_NS)
|| headerUri.equals(Consts.WFXML_NS)) {
break;
}
}
}
}
if (headerElement == null) {
throw new SOAPException("ASAP request header not found.");
}
return getChildsTextContent(headerElement, key);
}
/**
* Tries to find a child node with the given name. If there is such a child
* node, the first child is returned.
* @param element the parent element
* @param key the name of the tag to find
* @return first child, if the tag could be found, <code>null</code> else.
*/
protected SOAPElement findChildNode(SOAPElement element, String key) {
for (Iterator i = element.getChildElements(); i.hasNext();) {
Node node = (Node)i.next();
if (node instanceof SOAPElement) {
SOAPElement child = (SOAPElement) node;
String name = child.getElementName().getLocalName();
if (name.equals(key)) {
return child;
}
}
}
return null;
}
/**
* Retrieves the contents of the child node with the given name as text.
* @param element the parent node.
* @param key name of the child node.
* @return Contents of the child node, or <code>null</code>, if the
* key is no child of element.
*/
protected String getChildsTextContent(SOAPElement element, String key) {
for (Iterator i = element.getChildElements(); i.hasNext();) {
Node node = (Node)i.next();
if (node instanceof SOAPElement) {
SOAPElement child = (SOAPElement) node;
String name = child.getElementName().getLocalName();
if (name.equals(key)) {
return XMLUtil.getFirstLevelTextContent(child);
}
}
}
return null;
}
/**
* Fills the response header.
* @param reqMsg the request message.
* @param respMsg the response message.
* @exception SOAPException
* Error in the SOAP message.
*/
void fillResponseHeader(SOAPMessage reqMsg, SOAPMessage respMsg)
throws SOAPException {
SOAPEnvelope respEnv = respMsg.getSOAPPart().getEnvelope();
SOAPHeader respHeader = respEnv.getHeader();
SOAPHeaderElement respNode
= respHeader.addHeaderElement
(respEnv.createName(Consts.RESPONSE_HEADER, Consts.ASAP_PREFIX,
Consts.ASAP_NS));
// Set the sender to me. This might differ from the value, given in
// the request as ReceiverKey. We set it to the URL under which we were
// called.
SOAPElement skNode = respNode.addChildElement(Consts.SENDER_KEY,
Consts.ASAP_PREFIX);
skNode.addTextNode(resourceReference.getResourceKey());
// Set the receiver if it was specified in the request.
String receiver = getHeaderValue(reqMsg, Consts.SENDER_KEY);
if (receiver != null) {
SOAPElement node = respNode.addChildElement(Consts.RECEIVER_KEY,
Consts.ASAP_PREFIX, Consts.ASAP_NS);
node.addTextNode(receiver);
}
// Retain the RequestId if given.
String requestId = getHeaderValue(reqMsg, Consts.REQUEST_ID);
if (requestId != null) {
SOAPElement node = respNode.addChildElement(Consts.REQUEST_ID,
Consts.ASAP_PREFIX, Consts.ASAP_NS);
node.addTextNode(requestId);
}
}
/**
* Adds the text as a child node, if it is neither <code>null</code> nor
* empty.
* @param element the parent node.
* @param text the text to add.
* @throws SOAPException
* error appending the text node.
*/
protected void maybeAddTextNode(SOAPElement element, String text)
throws SOAPException {
if ((text == null) || (text.length() == 0)) {
return;
}
element.addTextNode(text);
}
/**
* Convenience method to create response node for WfXML requests.
* @param respMsg the response message.
* @param nodeName name of the response node.
* @return created response node.
* @throws SOAPException
* error creating the SOAP nodes.
*/
protected SOAPBodyElement createWfxmlResponseNode(SOAPMessage respMsg,
String nodeName)
throws SOAPException {
SOAPEnvelope respEnv = respMsg.getSOAPPart().getEnvelope();
SOAPBody respBody = respEnv.getBody();
Name respName = respEnv.createName(nodeName, Consts.WFXML_PREFIX,
Consts.WFXML_NS);
SOAPBodyElement node = respBody.addBodyElement(respName);
node.addNamespaceDeclaration(Consts.ASAP_PREFIX, Consts.ASAP_NS);
return node;
}
/**
* Convenience method to create response node for WfXML requests.
* @param respMsg the repsonse message.
* @param nodeName name of the response node.
* @return created response node.
* @throws SOAPException
* error creating the SOAP nodes.
*/
protected SOAPBodyElement createAsapResponseNode(SOAPMessage respMsg,
String nodeName)
throws SOAPException {
SOAPEnvelope respEnv = respMsg.getSOAPPart().getEnvelope();
SOAPBody respBody = respEnv.getBody();
Name respName = respEnv.createName(nodeName, Consts.ASAP_PREFIX,
Consts.ASAP_NS);
SOAPBodyElement node = respBody.addBodyElement(respName);
return node;
}
/**
* Creates a byte array representation for the given SOAP element.
* @param node the node to transform
* @return bytes for the given SOAP element.
* @throws TransformerException
* error transforming the node into a string.
*/
protected byte[] nodeToBytes(SOAPElement node) throws TransformerException {
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer();
DOMSource source = new DOMSource(node);
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamResult result = new StreamResult(out);
transformer.transform(source, result);
return out.toByteArray();
}
/**
* Imports a SOAP node as a child of another node in another document.
*
* <p>
* Note that this might be not namespace aware. make sure to have all
* namespaces resolved before calling this method.
* </p>
*
* @param parent the parent node
* @param imported the node to import
* @return the imported node
* @throws SOAPException
* error navigating through the SOAP stack.
*/
protected SOAPElement importAsChild(SOAPElement parent,
SOAPElement imported) throws SOAPException {
SOAPFactory factory = SOAPFactory.newInstance();
Name name = imported.getElementName();
SOAPElement newChild = parent.addChildElement(name.getLocalName(),
name.getPrefix(), name.getURI());
Iterator attributes = imported.getAllAttributes();
while (attributes.hasNext()) {
Name attribute = (Name) attributes.next();
String value = imported.getAttributeValue(attribute);
Name newName = factory.createName(attribute.getLocalName(),
attribute.getPrefix(), attribute.getURI());
newChild.addAttribute(newName, value);
}
Iterator children = imported.getChildElements();
while (children.hasNext()) {
Object o = children.next();
if (o instanceof Text) {
String text = imported.getNodeValue();
if (text != null) {
newChild.addTextNode(text);
}
} else {
importAsChild(newChild, (SOAPElement) o);
}
}
return newChild;
}
/**
* Imports a SOAP node as a child of another node in another document.
*
* <p>
* Note that this might be not namespace aware. make sure to have all
* namespaces resolved before calling this method.
* </p>
*
* @param parent the parent node
* @param imported the node to import
* @return the imported node
* @throws SOAPException
* error navigating through the SOAP stack.
*/
protected static SOAPElement importW3CNodeAsChild(SOAPElement parent,
org.w3c.dom.Node imported) throws SOAPException {
SOAPFactory factory = SOAPFactory.newInstance();
SOAPElement newChild = parent.addChildElement(imported.getLocalName(),
imported.getPrefix(), imported.getNamespaceURI());
org.w3c.dom.NamedNodeMap attributes = imported.getAttributes();
for(int i=0; i<attributes.getLength(); i++) {
org.w3c.dom.Node attribute = attributes.item(i);
Name newName = factory.createName(attribute.getLocalName(),
attribute.getPrefix(), attribute.getNamespaceURI());
newChild.addAttribute(newName, attribute.getNodeValue());
}
org.w3c.dom.NodeList children = imported.getChildNodes();
for(int i=0; i<children.getLength(); i++) {
Object o = children.item(i);
if (o instanceof org.w3c.dom.Text) {
String text = imported.getNodeValue();
newChild.addTextNode(text);
} else {
importW3CNodeAsChild(newChild, (org.w3c.dom.Node) o);
}
}
return newChild;
}
/**
* Append the given SAX event buffer as a child to the parent SOAP element.
* @param message the message containing the parent
* @param parent the parent SOAP element
* @param sax the sax buffer to import.
* @throws TransformerException
* @throws SOAPException
* Error in the SOAP message.
*/
protected static void importSAXAsChild
(SOAPMessage message, SOAPElement parent, SAXEventBuffer sax)
throws SOAPException {
SOAPEnvelope env = message.getSOAPPart().getEnvelope();
HandlerStack hs = new HandlerStack (new SOAPBuilder(parent));
hs.setContextData ("envelope", env);
try {
sax.emit(hs.contentHandler());
} catch (SAXException e) {
throw (IllegalArgumentException)
(new IllegalArgumentException(e.getMessage()).initCause(e));
}
}
}