/*
* This file is part of the WfMOpen project.
* Copyright (C) 2001-2004 Danet GmbH (www.danet.de), GS-AN.
* 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: GenericSoapClient.java 2904 2009-01-28 22:21:36Z mlipp $
*
* $Log$
* Revision 1.8 2007/01/30 11:56:15 drmlipp
* Merged Wf-XML branch.
*
* Revision 1.7.4.1 2006/12/20 14:37:59 schnelle
* Implemented Factory GetDefinition.
*
* Revision 1.7 2006/10/17 08:43:42 drmlipp
* Added support for JBoss connection exception detection.
*
* Revision 1.6 2006/09/29 12:32:09 drmlipp
* Consistently using WfMOpen as projct name now.
*
* Revision 1.5 2005/09/05 14:03:44 drmlipp
* Adapted to ToolAgent API update.
*
* Revision 1.4 2005/08/29 14:22:32 drmlipp
* Added ConnectException handling for generic soap client.
*
* Revision 1.3 2005/08/04 07:02:37 drmlipp
* Rmoved not needed code.
*
* Revision 1.2 2005/08/03 15:35:35 drmlipp
* Back-ported generic SOAP tool from wfmopen-2 branch.
*
*/
package de.danet.an.workflow.tools.soap;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
import java.rmi.ConnectException;
import java.rmi.RemoteException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.MimeHeaders;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPConnection;
import javax.xml.soap.SOAPConnectionFactory;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
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.sax.SAXResult;
import org.jaxen.JaxenException;
import org.jaxen.XPath;
import org.jaxen.dom.DOMXPath;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import de.danet.an.util.sax.HandlerStack;
import de.danet.an.util.soap.SOAPBuilder;
import de.danet.an.util.web.W3CDomUtil;
import de.danet.an.workflow.api.Activity;
import de.danet.an.workflow.api.FormalParameter;
import de.danet.an.workflow.api.SAXEventBuffer;
import de.danet.an.workflow.spis.aii.ApplicationNotStoppedException;
import de.danet.an.workflow.spis.aii.CannotExecuteException;
import de.danet.an.workflow.spis.aii.ResultProvider;
import de.danet.an.workflow.spis.aii.ToolAgent;
import de.danet.an.workflow.spis.aii.XMLArgumentTypeProvider;
import de.danet.an.workflow.util.SAXEventBufferImpl;
import de.danet.an.workflow.util.XPDLUtil;
/**
* This class provides a generic SOAP client.
*
* @author <a href="mailto:lipp@danet.de">Michael Lipp</a>
* @version $Revision: 2904 $
*/
public class GenericSoapClient
implements ToolAgent, XMLArgumentTypeProvider, ResultProvider,
Serializable {
private static final org.apache.commons.logging.Log logger
= org.apache.commons.logging.LogFactory.getLog
(GenericSoapClient.class);
// hold the mapping of return parameter name and eventually the XPath
// expression
private Map returnParamInfo = new HashMap();
private String endpoint;
/** The result container. */
private ThreadLocal result = new ThreadLocal ();
/**
* Creates an instance of <code>GenericSoapClient</code>
* with all attributes initialized to default values.
*/
public GenericSoapClient () {
}
/**
* Get the value of endpoint.
* @return value of endpoint.
* @see #setEndpoint
*/
public String getEndpoint() {
return endpoint;
}
/**
* Set the value of endpoint.
* @param newEndpoint value to assign to endpoint.
* @see #getEndpoint
*/
public void setEndpoint(String newEndpoint) {
this.endpoint = newEndpoint;
}
/**
* Set the xml definition of output mappings. It is used to convert the
* WSIF invocation response.
*
* @param outputMappings the given xml as W3C Element.
*/
public void setMappings(Element outputMappings) {
try {
// retrieve namespaces
NodeList namespacesNodeList = outputMappings.getElementsByTagNameNS
(XPDLUtil.XPDL_EXTN_NS, "Namespaces");
if (namespacesNodeList.getLength() == 0) {
namespacesNodeList = outputMappings.getElementsByTagNameNS
(XPDLUtil.XPDL_EXTN_V1_1_NS, "Namespaces");
}
NodeList nsNodeList = null;
if (namespacesNodeList.getLength() > 0) {
Element namespacesNode = (Element)namespacesNodeList.item(0);
nsNodeList = namespacesNode.getElementsByTagNameNS
(namespacesNode.getNamespaceURI(), "Namespace");
}
NodeList paramNodeList = outputMappings.getElementsByTagNameNS
(XPDLUtil.XPDL_EXTN_NS, "Parameter");
if (paramNodeList.getLength() == 0) {
paramNodeList = outputMappings.getElementsByTagNameNS
(XPDLUtil.XPDL_EXTN_V1_1_NS, "Parameter");
}
for (int i = 0; i < paramNodeList.getLength(); i++) {
Element param = (Element)paramNodeList.item(i);
String name = param.getAttribute("Name");
XPath xpath = new DOMXPath (param.getAttribute("Select"));
if (nsNodeList != null) {
for (int j = 0; j < nsNodeList.getLength(); j++) {
Element ns = (Element)nsNodeList.item(j);
String prefix = ns.getAttribute("Prefix");
String uri = ns.getAttribute("Uri");
xpath.addNamespace(prefix, uri);
}
}
returnParamInfo.put(name, xpath);
}
} catch (Exception e) {
// if any error ocurred, outputMappings is still null.
logger.error("error in setting XML for output mappings!", e);
}
}
// Implementation of de.danet.an.workflow.spis.aii.ToolAgent
/* Comment copied from interface. */
public void invoke(Activity activity, FormalParameter[] formPars, Map map)
throws RemoteException, CannotExecuteException {
try {
String httpHeaders = (String)map.get("httpHeaders");
SAXEventBuffer headerContent = (SAXEventBuffer)map.get("header");
SAXEventBuffer bodyContent = (SAXEventBuffer)map.get("body");
SOAPConnectionFactory factory = SOAPConnectionFactory.newInstance();
SOAPConnection connection = factory.createConnection();
MessageFactory messageFactory = MessageFactory.newInstance();
SOAPMessage message = messageFactory.createMessage();
MimeHeaders mimeHeaders = message.getMimeHeaders();
SOAPEnvelope env = message.getSOAPPart().getEnvelope();
// Add headers, if any
if (httpHeaders != null) {
for (StringTokenizer st
= new StringTokenizer(httpHeaders, "\n");
st.hasMoreElements();) {
String hdr = st.nextToken();
int sep = hdr.indexOf(':');
if (sep < 0) {
throw new CannotExecuteException
("Invalid header (missing ':'): " + hdr);
}
mimeHeaders.addHeader
(hdr.substring(0, sep),
hdr.substring(sep + 1).trim());
}
}
// Add header content, if any
if (headerContent != null) {
SOAPHeader hdr = env.getHeader();
appendNodes (env, hdr, headerContent);
}
// Add body content
SOAPBody body = env.getBody();
appendNodes (env, body, bodyContent);
// Now invoke with the created message
SOAPMessage response = null;
try {
response = connection.call(message, new URL(endpoint));
} catch (SOAPException e) {
if (e.getMessage().indexOf("ConnectException") >= 0) {
// Axis way of reporting this
if (logger.isDebugEnabled()) {
logger.debug
("Cannot invoke (signalled ConnectException): "
+ e.getMessage (), e);
}
throw new CannotExecuteException
("Assuming connection failure: " + e.getMessage(),
new ConnectException (e.getMessage(), e));
} else if (e.getCause() != null
&& e.getCause().getCause() != null
&& (e.getCause().getCause()
instanceof java.net.ConnectException)) {
// JBossWS way of reporting this
throw new CannotExecuteException
("Assuming connection failure: " + e.getMessage(),
new ConnectException (e.getMessage(), e));
}
throw e;
} finally {
connection.close ();
}
SOAPPart respPart = response.getSOAPPart ();
Map resData = new HashMap ();
for (int i = 0; i < formPars.length; i++) {
if (formPars[i].mode() == FormalParameter.Mode.IN) {
continue;
}
XPath path = (XPath)returnParamInfo.get(formPars[i].id());
if (path == null) {
resData.put(formPars[i].id(),
convertToSax(respPart.getDocumentElement()));
} else {
if (formPars[i].type().equals (String.class)) {
resData.put(formPars[i].id(),
path.stringValueOf(respPart));
} else if ((formPars[i].type() instanceof Class)
&& Number.class.isAssignableFrom
((Class)formPars[i].type())) {
Number n = path.numberValueOf(respPart);
if (formPars[i].type().equals (Long.class)
&& !(n instanceof Long)) {
n = new Long (n.longValue());
}
resData.put(formPars[i].id(), n);
} else {
resData.put(formPars[i].id(),
convertToSax(path.selectNodes(respPart)));
}
}
}
result.set (resData);
} catch (CannotExecuteException e) {
throw e;
} catch (MalformedURLException e) {
throw new CannotExecuteException (e.getMessage(), e);
} catch (SOAPException e) {
throw new CannotExecuteException (e.getMessage(), e);
} catch (SAXException e) {
throw new CannotExecuteException (e.getMessage(), e);
} catch (JaxenException e) {
throw new CannotExecuteException (e.getMessage(), e);
} catch (TransformerException e) {
throw new CannotExecuteException (e.getMessage(), e);
}
}
private SAXEventBuffer convertToSax (Node value)
throws TransformerException {
TransformerFactory tf = TransformerFactory.newInstance();
Transformer t = tf.newTransformer();
SAXEventBufferImpl res = new SAXEventBufferImpl ();
t.transform (new DOMSource(value), new SAXResult (res));
return res;
}
private SAXEventBuffer convertToSax(List nodes)
throws TransformerException {
Document doc = W3CDomUtil.createNewDocument();
DocumentFragment frag = doc.createDocumentFragment();
for (Iterator i = nodes.iterator(); i.hasNext();) {
Element e = (Element) i.next();
frag.appendChild(doc.importNode(e, true));
}
return convertToSax(frag);
}
private void appendNodes
(SOAPEnvelope env, SOAPElement root, SAXEventBuffer data)
throws SAXException {
HandlerStack hs = new HandlerStack (new SOAPBuilder (root));
hs.setContextData ("envelope", env);
data.emit(hs.contentHandler());
}
/**
* Return the requested type for XML arguments.
* @return one of <code>XML_AS_W3C_DOM</code>,
* <code>XML_AS_JDOM</code> or <code>XML_AS_SAX</code>
*/
public int requestedXMLArgumentType () {
return XMLArgumentTypeProvider.XML_AS_SAX;
}
/**
* Return the result evaluated during {@link ToolAgent#invoke
* <code>invoke</code>}. The method will only be called once after
* each invoke, i.e. the attribute holding the result be be
* cleared in this method.
*
* @return the result data or <code>null</code> if the invocation
* does not return any data.
*/
public Object result () {
Object res = result.get();
result.set (null);
return res;
}
/* Comment copied from interface. */
public void terminate(Activity activity)
throws ApplicationNotStoppedException {
throw new ApplicationNotStoppedException
("Terminate not implemented for GenericSoapClient.");
}
}