Alternative WS client action contribution
mikaeljl Dec 17, 2008 11:04 AMHi!
Don't know if this is the correct place to place this info but I just wanted to share an alternative ESB action class that we have created for calling web services.
It is based on the JAX-WS Dispatch interface for calling a WS without any generated stub classes where you can work directly on the XML message data.
The advantage with this class is that it operates directly on the XML message that you typically have in your ESB message. With this action there is no need to format the request data paramters in an OGNL encoded map like you have to do with the SOAPClient action.
Problems:
I've seen some problems with the request timeout with old jboss WS releases.
You may want to clean up the hard coded SOAP element strings.
Usage of this code:
JBoss: Please feel free to incorporate this into the ESB if you like it.
Others: Copy-paste as you see fit.
Code provided by SJ:
http://www.sj.se/sj/jsp/polopoly.jsp?d=10&l=en
package se.sj.ipl.esb.actions.soap; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.StringWriter; import java.io.UnsupportedEncodingException; import javax.xml.namespace.QName; import javax.xml.soap.MessageFactory; import javax.xml.soap.SOAPConstants; import javax.xml.soap.SOAPException; import javax.xml.soap.SOAPMessage; import javax.xml.soap.SOAPPart; import javax.xml.transform.OutputKeys; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.TransformerFactoryConfigurationError; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import javax.xml.ws.BindingProvider; import javax.xml.ws.Dispatch; import javax.xml.ws.Service; import javax.xml.ws.Service.Mode; import javax.xml.ws.soap.SOAPBinding; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathFactory; import org.apache.log4j.Logger; import org.jboss.soa.esb.actions.AbstractActionPipelineProcessor; import org.jboss.soa.esb.actions.ActionProcessingException; import org.jboss.soa.esb.actions.BeanConfiguredAction; import org.jboss.soa.esb.message.Body; import org.jboss.soa.esb.message.Message; import org.jboss.soa.esb.util.XPathNamespaceContext; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; /** * @author Micke. */ public class WSDispatchClientAction extends AbstractActionPipelineProcessor implements BeanConfiguredAction { Logger LOG = Logger.getLogger(WSDispatchClientAction.class); // Mandatory explicitly defined attributes private String serviceName = null; private String portName = null; private String soapAction = null; private String targetNS = null; private String endpointAddress = null; // Optional implicitly defined attributes private String requestDataLocation = Body.DEFAULT_LOCATION; private String responseDataLocation = Body.DEFAULT_LOCATION; /**Default timeout is 60000 milliseconds*/ private String timeout = "60000"; // If null no header data will be sent private String requestHeaderDataLocation = null; private static final String SOAP_START = "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\"><soapenv:Header>"; private static final String SOAP_MIDDLE = "</soapenv:Header><soapenv:Body>"; private static final String SOAP_END = "</soapenv:Body></soapenv:Envelope>"; /** * */ public Message process(Message message) throws ActionProcessingException { if (message == null) throw new ActionProcessingException( "Message was null in WSDispatchClientAction"); if (message.getBody().get(requestDataLocation) == null) throw new ActionProcessingException( "Message request data at location: " + requestDataLocation + " was null in WSDispatchClientAction"); String messageBodyContents = null; if (message.getBody().get(requestDataLocation) instanceof String) { messageBodyContents = (String) message.getBody().get( requestDataLocation); } else if (message.getBody().get(requestDataLocation) instanceof byte[]) { messageBodyContents = new String((byte[]) message.getBody().get( requestDataLocation)); } else { throw new ActionProcessingException("Unsupported message type: " + message.getBody().get(requestDataLocation).getClass()); } String messageHeaderContents = null; if (requestHeaderDataLocation != null) { if (message.getBody().get(requestHeaderDataLocation) instanceof String) { messageHeaderContents = (String) message.getBody().get( requestHeaderDataLocation); } else if (message.getBody().get(requestHeaderDataLocation) instanceof byte[]) { messageHeaderContents = new String((byte[]) message.getBody() .get(requestHeaderDataLocation)); } else { throw new ActionProcessingException( "Unsupported message header data type: " + message.getBody().get( requestHeaderDataLocation).getClass()); } } Mode mode = null; if (messageHeaderContents != null) { mode = Mode.MESSAGE; } else { mode = Mode.PAYLOAD; } Service wsService = createService(targetNS, serviceName); QName portQName = new QName(targetNS, portName); if (mode.equals(Mode.MESSAGE)) { wsService.addPort(portQName, SOAPBinding.SOAP11HTTP_BINDING, endpointAddress); Dispatch<SOAPMessage> dispatch = wsService.createDispatch( portQName, SOAPMessage.class, mode); dispatch.getRequestContext().put(org.jboss.ws.core.StubExt.PROPERTY_CLIENT_TIMEOUT, new Integer(timeout)); if (soapAction != null) { dispatch.getRequestContext().put(BindingProvider.SOAPACTION_USE_PROPERTY, Boolean.TRUE); dispatch.getRequestContext().put(BindingProvider.SOAPACTION_URI_PROPERTY, soapAction); } SOAPMessage soapMessage = createMessageRequestMessage( messageBodyContents, messageHeaderContents); SOAPMessage responseMessage = dispatch.invoke(soapMessage); String response = handleResponseMessage(responseMessage); message.getBody().add(responseDataLocation, response); } else { wsService.addPort(portQName, SOAPBinding.SOAP11HTTP_BINDING, endpointAddress); Dispatch<Source> dispatch = wsService.createDispatch(portQName, Source.class, mode); dispatch.getRequestContext().put(org.jboss.ws.core.StubExt.PROPERTY_CLIENT_TIMEOUT, new Integer(timeout)); if (soapAction != null) { dispatch.getRequestContext().put(BindingProvider.SOAPACTION_USE_PROPERTY, Boolean.TRUE); dispatch.getRequestContext().put(BindingProvider.SOAPACTION_URI_PROPERTY, soapAction); } Source requestSource = createPayloadRequestSource(messageBodyContents); Source responseSoure = dispatch.invoke(requestSource); String response = handleResponseSource(responseSoure); message.getBody().add(responseDataLocation, response); } return message; } private Service createService(String targetNS, String serviceName) { QName serviceQName = new QName(targetNS, serviceName); Service theService = Service.create(serviceQName); return theService; } protected Source createPayloadRequestSource(String requestData) { LOG.debug("Request data: " + requestData); ByteArrayInputStream byteInStream = new ByteArrayInputStream( requestData.getBytes()); BufferedInputStream buffStream = new BufferedInputStream(byteInStream); Source requestSource = new StreamSource(buffStream); return requestSource; } protected SOAPMessage createMessageRequestMessage(String requestBodyData, String headerData) throws ActionProcessingException { StringBuffer strBuff = new StringBuffer(SOAP_START); strBuff.append(headerData); strBuff.append(SOAP_MIDDLE); strBuff.append(requestBodyData); strBuff.append(SOAP_END); String requestData = strBuff.toString(); LOG.debug("Request data: " + requestData); SOAPMessage soapMessage = null; try { ByteArrayInputStream byteInStream = new ByteArrayInputStream( requestData.getBytes("UTF-8")); BufferedInputStream buffStream = new BufferedInputStream(byteInStream); MessageFactory mf; mf = MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL); soapMessage = mf.createMessage(); SOAPPart part = soapMessage.getSOAPPart(); part.setContent((Source) new StreamSource(buffStream)); soapMessage.saveChanges(); } catch (SOAPException e) { throw new ActionProcessingException("Error during creation of request message", e); } catch (UnsupportedEncodingException e) { throw new ActionProcessingException("Error during creation of request message", e); } return soapMessage; } protected String handleResponseSource(Source responseSoure) throws ActionProcessingException { ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream(); Result result = new StreamResult(byteOutStream); Transformer xformer; try { xformer = TransformerFactory.newInstance().newTransformer(); xformer.setOutputProperty(OutputKeys.INDENT, "yes"); xformer.transform(responseSoure, result); // FIXME do we need some specific encoding support? String resultString = new String(byteOutStream.toByteArray()); LOG.debug("Response: " + resultString); return resultString; } catch (TransformerConfigurationException e) { throw new ActionProcessingException( "TransformerConfigurationException caught when transforming WS response message: " + e.getMessage(), e); } catch (TransformerFactoryConfigurationError e) { throw new ActionProcessingException( "TransformerFactoryConfigurationError caught when transforming WS response message: " + e.getMessage(), e); } catch (TransformerException e) { throw new ActionProcessingException( "TransformerException caught when transforming WS response message: " + e.getMessage(), e); } } protected String handleResponseMessage(SOAPMessage responseMessage) throws ActionProcessingException { ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream(); try { responseMessage.writeTo(byteOutStream); String response = new String(byteOutStream.toByteArray()); LOG.debug("Response: " + response); //Note that this response string contains the full SOAP envelope. //Remove SOAPwrapping and only return contents of the SOAPbody response = getContentsOfSOAPBody(response); return response; } catch (SOAPException e) { throw new ActionProcessingException( "SOAPException caught when transforming WS response message: " + e.getMessage(), e); } catch (IOException e) { throw new ActionProcessingException( "IOException caught when transforming WS response message: " + e.getMessage(), e); } } public String getServiceName() { return serviceName; } public void setServiceName(String serviceName) { this.serviceName = serviceName; } public String getPortName() { return portName; } public void setPortName(String portName) { this.portName = portName; } public String getSoapAction() { return soapAction; } public void setSoapAction(String soapAction) { this.soapAction = soapAction; } public String getTargetNS() { return targetNS; } public void setTargetNS(String targetNS) { this.targetNS = targetNS; } public String getEndpointAddress() { return endpointAddress; } public void setEndpointAddress(String endpointAddress) { this.endpointAddress = endpointAddress; } public String getRequestDataLocation() { return requestDataLocation; } public void setRequestDataLocation(String requestDataLocation) { this.requestDataLocation = requestDataLocation; } public String getResponseDataLocation() { return responseDataLocation; } public void setResponseDataLocation(String responseDataLocation) { this.responseDataLocation = responseDataLocation; } public String getRequestHeaderDataLocation() { return requestHeaderDataLocation; } public void setRequestHeaderDataLocation(String requestHeaderDataLocation) { this.requestHeaderDataLocation = requestHeaderDataLocation; } protected String getContentsOfSOAPBody(String soapMessage) throws ActionProcessingException{ XPathNamespaceContext namespaceContext = new XPathNamespaceContext(); namespaceContext.setMapping("soapenv", "http://schemas.xmlsoap.org/soap/envelope/"); XPath xpath = XPathFactory.newInstance().newXPath(); xpath.setNamespaceContext(namespaceContext); ByteArrayInputStream messageInStream = new ByteArrayInputStream( soapMessage.getBytes()); String expression = "/soapenv:Envelope/soapenv:Body"; InputSource inputSource = new InputSource(messageInStream); try { Object result = xpath.evaluate(expression, inputSource, XPathConstants.NODESET); NodeList nodeList = (NodeList) result; nodeList = nodeList.item(0).getChildNodes(); TransformerFactory tf = TransformerFactory.newInstance(); Transformer transformer = tf.newTransformer(); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); StringWriter writer = new StringWriter(); for (int i=0; i<nodeList.getLength(); i++) { Node n = nodeList.item(i); transformer.transform( new DOMSource(n), new StreamResult(writer)); } return writer.toString(); } catch (Exception ex) { LOG.error( "Error during xpath evaluation trying to extract SOAP Body from: " + soapMessage, ex); throw new ActionProcessingException("Error during xpath evaluation trying to extract SOAP Body", ex); } } public String getTimeout() { return timeout; } public void setTimeout(String timeout) { this.timeout = timeout; } }
Here is a usage example of what you might use in jboss-esb.xml
<action name="call-ws" class="se.sj.ipl.esb.actions.soap.WSDispatchClientAction"> <property name="serviceName" value="SomeServiceName" /> <property name="portName" value="SomePortName" /> <property name="soapAction" value="http://some.hos/SomeActionIfExplicitlyRequired" /> <property name="targetNS" value="http://some.host/namespace/of/service" /> <property name="endpointAddress" value="http://some.host/someTargetWS" /> <property name="requestHeaderDataLocation" value="some.location.where.header.data.is.located" /> <property name="timeout" value="60000" /> </action>[/url]