/*
* This file is part of the WfMOpen project.
* Copyright (C) 2001-2003 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: SOAPClient.java 2904 2009-01-28 22:21:36Z mlipp $
*
* $Log$
* Revision 1.4 2005/09/05 09:41:49 drmlipp
* Synchronized with 1.3.2.
*
* Revision 1.3 2005/08/25 13:24:22 drmlipp
* Synchronized with 1.3.1p6.
*
* Revision 1.2 2005/04/24 18:59:43 mlipp
* Fixed bug with scalar result.
*
* Revision 1.1.1.4 2004/08/18 15:17:39 drmlipp
* Update to 1.2
*
* Revision 1.12 2004/04/16 07:56:19 lipp
* Make sure that we use Axis (even on WLS).
*
* Revision 1.11 2004/04/01 15:21:34 lipp
* Using new tool helpers.
*
* Revision 1.10 2004/03/21 21:30:22 lipp
* Added Document for absolut paths.
*
* Revision 1.9 2004/03/21 20:35:36 lipp
* Using exception signalling.
*
* Revision 1.8 2004/02/21 21:31:01 lipp
* Some more refactoring to resolve cyclic dependencies.
*
* Revision 1.7 2003/09/09 08:33:33 lipp
* Fixed loop bug.
*
* Revision 1.6 2003/06/27 12:46:06 lipp
* Got SOAPClient running.
*
* Revision 1.5 2003/06/27 08:51:44 lipp
* Fixed copyright/license information.
*
* Revision 1.4 2003/06/25 15:50:29 lipp
* New SOAP client that makes wsif obsolete.
*
* Revision 1.3 2003/06/24 22:41:43 lipp
* Fixed loop bug.
*
* Revision 1.2 2003/06/24 16:08:12 lipp
* Implemented invocation.
*
* Revision 1.1 2003/06/23 22:06:20 lipp
* Started alternate SOAP client.
*
*/
package de.danet.an.workflow.tools.soapclient;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.net.ConnectException;
import java.rmi.RemoteException;
import javax.wsdl.Definition;
import javax.wsdl.Operation;
import javax.wsdl.Part;
import javax.wsdl.Port;
import javax.wsdl.PortType;
import javax.wsdl.Service;
import javax.wsdl.extensions.ExtensibilityElement;
import javax.wsdl.extensions.soap.SOAPAddress;
import javax.wsdl.factory.WSDLFactory;
import javax.wsdl.xml.WSDLReader;
import javax.xml.rpc.Call;
import javax.xml.rpc.ParameterMode;
import javax.xml.rpc.encoding.DeserializerFactory;
import javax.xml.rpc.encoding.SerializerFactory;
import javax.xml.rpc.encoding.TypeMapping;
import javax.xml.rpc.encoding.TypeMappingRegistry;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.jaxen.JaxenException;
import org.jaxen.XPath;
import org.jaxen.jdom.JDOMXPath;
import org.jdom.Document;
import de.danet.an.util.sax.NamespaceAttributesAdder;
import de.danet.an.workflow.util.SAXEventBufferImpl;
import de.danet.an.workflow.util.XPDLUtil;
import de.danet.an.workflow.api.Activity;
import de.danet.an.workflow.api.ActivityUniqueKey;
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.ExceptionMappingProvider;
import de.danet.an.workflow.spis.aii.ResultProvider;
import de.danet.an.workflow.spis.aii.ToolAgent;
import de.danet.an.workflow.spis.aii.XMLArgumentTypeProvider;
/**
* This class provides a client for RPC style SOAP invocations.
*
* @author <a href="mailto:mnl@mnl.de">Michael N. Lipp</a>
* @version $Revision: 2904 $
*/
public class SOAPClient
implements ToolAgent, XMLArgumentTypeProvider, ResultProvider,
ExceptionMappingProvider, Serializable {
private static final org.apache.commons.logging.Log logger
= org.apache.commons.logging.LogFactory.getLog
(SOAPClient.class);
private String method = null;
// hold the mapping of return parameter name and eventually the XPath
// expression
private Map returnParamInfo = new HashMap();
private Definition wsdlDef = null;
private Service service = null;
private Port port = null;
private PortType portType = null;
private String portLocation = null;
private Operation operation = null;
/** The result container. */
private ThreadLocal result = new ThreadLocal ();
/**
* Creates an instance of <code>SOAPClient</code>
* with all attributes initialized to default values.
*/
public SOAPClient () {
}
/**
* 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;
}
/**
* Set the definition of WSDL. It is used to invoke the defined method
* dynamically.
*
* @param wsdlLocation the location of given WSDL definition.
*/
public void setWSDL(String wsdlLocation) {
try {
if ((wsdlLocation == null) || wsdlLocation.equals("")) {
return;
}
WSDLReader rdr = WSDLFactory.newInstance().newWSDLReader();
wsdlDef = rdr.readWSDL(wsdlLocation);
} catch (Exception e) {
logger.error("Error setting WSDL: " + e.getMessage (), e);
}
}
/**
* Set the definition of WSDL. It is used to invoke the defined method
* dynamically.
*
* @param wsdl the given WSDL definition in W3C DOM Element.
*/
public void setWSDL(SAXEventBuffer wevts) {
try {
TransformerFactory tf = TransformerFactory.newInstance();
if (!tf.getFeature(SAXTransformerFactory.FEATURE)) {
String s = "JAXP transformer factory does not"
+ " support a SAX transformer!";
logger.fatal (s);
throw new IllegalStateException (s);
}
TransformerHandler th = ((SAXTransformerFactory)tf)
.newTransformerHandler ();
DOMResult out = new DOMResult ();
th.setResult (out);
wevts.emit(new NamespaceAttributesAdder(th));
org.w3c.dom.Node node = out.getNode();
if (node instanceof org.w3c.dom.Document) {
node = ((org.w3c.dom.Document)node).getDocumentElement();
}
WSDLReader rdr = WSDLFactory.newInstance().newWSDLReader();
wsdlDef = rdr.readWSDL(null, (Element)node);
} catch (Exception e) {
logger.error("Error setting WSDL: " + e.getMessage (), e);
}
}
/**
* Set the xml definition of output mappings. It is used to convert the
* WSIF invocation response.
*
* @param outputMappings the given xml as JDOM 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 JDOMXPath (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);
}
}
/**
* set the SOAP method to be called with WSIF.
*
* @param method the given method.
*/
public void setMethod (String method) {
this.method = method;
}
// 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 {
// Do not attempt to perform any operation on the activity
// except key() and uniqueKey() before WSIF dynamic
// invocation. Else, the activity becomes part of the EJB
// transaction and is locked, i.e. all accesses (even display
// in the management client) are deferred until the invocation
// has completed. The time this takes is not controllable.
ActivityUniqueKey auk = null;
try {
if (wsdlDef == null) {
throw new IllegalStateException("WSDL definition not defined");
}
Operation calledOp = operation;
if (calledOp == null) {
if (method == null) {
calledOp = initOperation
((String)map.get(formPars[0].id()));
FormalParameter[] fps
= new FormalParameter[formPars.length - 1];
System.arraycopy(formPars, 0, fps, 0, fps.length);
formPars = fps;
} else {
calledOp = initOperation (method);
operation = calledOp;
}
}
auk = activity.uniqueKey();
try {
result.set(invokeOperation (calledOp, formPars, map));
} catch (RemoteException e) {
if (logger.isDebugEnabled()) {
logger.debug ("Cannot invoke: " + e.getMessage (), e);
}
throw new CannotExecuteException
("Cannot invoke: " + e.getMessage(), e);
}
} finally {
if (logger.isDebugEnabled()) {
logger.debug ("Finished invocation of " + auk);
}
}
}
private Operation initOperation (String method)
throws IllegalStateException {
if (service == null) {
Collection services = wsdlDef.getServices().values ();
if (services.size () == 0) {
throw new IllegalStateException ("No service defined.");
}
if (services.size () > 1) {
throw new IllegalStateException
("More than one service defined.");
}
service = (Service)services.iterator().next();
Collection ports = service.getPorts().values();
if (ports.size () == 0) {
throw new IllegalStateException ("No ports defined.");
}
if (ports.size () > 1) {
throw new IllegalStateException("More than one port defined.");
}
port = (Port)ports.iterator().next();
for (Iterator i = port.getExtensibilityElements().iterator();
i.hasNext ();) {
ExtensibilityElement ee = (ExtensibilityElement)i.next ();
if (ee instanceof SOAPAddress) {
portLocation = ((SOAPAddress)ee).getLocationURI();
}
}
if (portLocation == null) {
throw new IllegalStateException ("No port location defined.");
}
portType = port.getBinding().getPortType ();
}
String opName = method;
String inName = null;
String outName = null;
int sepCol = opName.indexOf (':');
if (sepCol >= 0) {
inName = opName.substring(sepCol + 1);
opName = opName.substring(0, sepCol);
sepCol = inName.indexOf (':');
if (sepCol >= 0) {
outName = inName.substring(sepCol + 1);
inName = inName.substring(0, sepCol);
}
}
Operation operation = null;
for (Iterator i = portType.getOperations().iterator(); i.hasNext();) {
Operation op = (Operation) i.next();
if (!opName.equals(op.getName())
|| (inName != null && !inName.equals (op.getInput().getName()))
|| (outName != null
&& !outName.equals (op.getOutput().getName()))) {
continue;
}
if (operation != null) {
operation = null;
throw new IllegalStateException
("Operation '" + method + "' is overloaded. "
+ "Please specify the operation in the form "
+ "'operationName:inputMessageName:"
+ "outputMessageName' to distinguish it");
}
operation = op;
}
if (operation == null) {
throw new IllegalStateException
("Operation \"" + method + "\" not found.");
}
return operation;
}
/**
* Invokes the dedicated operation of web services. The Operation return
* value as Map must have a key named <code>Result</code>, its value is
* written in the new constructed process data using the key of the out
* formal parameters.
*
* @return the new process data with the result of web services operation
* included.
* @throws WSIFDynamicInvokerException if any errors in invoking web
* service occurred.
*/
private Map invokeOperation
(Operation operation, FormalParameter[] formPars, Map map)
throws CannotExecuteException, RemoteException {
try {
// javax.xml.rpc.Service svc
// = javax.xml.rpc.ServiceFactory.newInstance()
// .createService (service.getQName());
javax.xml.rpc.Service svc
= (new org.apache.axis.client.ServiceFactory())
.createService (service.getQName());
TypeMappingRegistry tmr = svc.getTypeMappingRegistry();
TypeMapping tm = tmr.getDefaultTypeMapping();
Call call = svc.createCall ();
call.setTargetEndpointAddress(portLocation);
call.setOperationName (new javax.xml.namespace.QName
(wsdlDef.getTargetNamespace(),
operation.getName()));
SerializerFactory mySer = new SAXEventBufferSerializerFactory ();
DeserializerFactory myDes = new JDOMDeserializerFactory ();
Set fpns = new HashSet ();
for (int i = 0; i < formPars.length; i++) {
if (formPars[i].mode() != FormalParameter.Mode.OUT) {
fpns.add (formPars[i].id());
}
}
Map parts = operation.getInput().getMessage().getParts();
List args = new ArrayList ();
for (Iterator pi = parts.keySet().iterator (); pi.hasNext(); ) {
String pn = (String)pi.next ();
if (! fpns.contains(pn)) {
throw new IllegalStateException
("Missing formal parameter for part \"" + pn + "\".");
}
fpns.remove (pn);
Part part = (Part)parts.get (pn);
Object value = map.get (pn);
if (value instanceof SAXEventBufferImpl) {
tm.register(SAXEventBufferImpl.class, part.getTypeName(),
mySer, myDes);
}
call.addParameter (pn, part.getTypeName(), ParameterMode.IN);
args.add (value);
}
if (fpns.size () > 0) {
String param = (String)fpns.iterator().next();
throw new IllegalStateException
("Extra formal parameter \"" + param + "\".");
}
Map outParts = operation.getOutput().getMessage().getParts();
if (outParts.values().size () == 0) {
call.setReturnType (org.apache.axis.encoding.XMLType.AXIS_VOID);
} else {
Part outPart = (Part)outParts.values().iterator().next();
if (tm.getDeserializer(null, outPart.getTypeName()) == null) {
tm.register(SAXEventBufferImpl.class, outPart.getTypeName(),
mySer, myDes);
}
call.setReturnType (outPart.getTypeName());
}
Object res = call.invoke (args.toArray());
// add root; else absolute XPath expressions don't work.
// But leave res assigne to root element for backward
// compatibility of relative paths.
if (res instanceof org.jdom.Element) {
new Document ((org.jdom.Element)res);
}
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(), res);
} else {
if (formPars[i].type().equals (String.class)) {
resData.put(formPars[i].id(), path.stringValueOf(res));
} else if ((formPars[i].type() instanceof Class)
&& Number.class.isAssignableFrom
((Class)formPars[i].type())) {
Number n = path.numberValueOf(res);
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(), path.selectNodes(res));
}
}
}
return resData;
} catch (JaxenException e) {
throw new CannotExecuteException
("Cannot execute: " + e.getMessage (), e);
} catch (javax.xml.rpc.ServiceException e) {
logger.error (e.getMessage (), e);
throw new CannotExecuteException (e.getMessage ());
}
}
/**
* 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;
}
/* (non-Javadoc)
* @see ExceptionMappingProvider#exceptionMappings()
*/
public Collection exceptionMappings() {
Collection mappings = new ArrayList();
mappings.add(new ExceptionMapping
(RemoteException.class, "RemoteException"));
return mappings;
}
/* Comment copied from interface. */
public void terminate(Activity activity)
throws ApplicationNotStoppedException {
throw new ApplicationNotStoppedException
("Terminate not implemented for WSIFDynamicInvocationTool.");
}
}