7 Replies Latest reply on Jun 15, 2009 5:15 AM by mikaeljl

    Alternative WS client action contribution

      Hi!

      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]

        • 1. Re: Alternative WS client action contribution
          beve

          Hi Micke,

          sorry for not responding earlier. Thanks for posting your action.

          This has got me thinking that it would be nice to have a way for us all to share actions that are useful but without the overhead of perhaps always adding them to the code base.
          At the moment we have two actions that do the same and I'm just thinking that adding another might confuse people. Just to note I know that when you wrote this there was only one.
          It would be nice to have a svn repo where users could add actions but I'm not sure if that is possible. I'll try to find out about it this is doable.

          One option that we could start with is creating a space on the wiki for donated actions. This way they it should be easy to browse them and use.
          What do you think about that?

          regards,

          /Daniel

          • 2. Re: Alternative WS client action contribution
            camunda

            Would be great!
            +1

            • 3. Re: Alternative WS client action contribution
              marklittle

              There's a place on the ESB wiki for such things already. It's the sandbox location and anyone with wiki access should feel free to update it with their donations, which may not be limited to just actions of course.

              • 4. Re: Alternative WS client action contribution
                kconner

                Also, one of the eventual aims with the ESB 5 codebase is to provide support for a services repository. You would then be able to download and install specific services from remote locations, one of which could be a community resource.

                We have a lot of work to do before we get as far as that though.

                • 5. Re: Alternative WS client action contribution

                  I've updated the wiki sandbox and uploaded the action class to the new article.

                  /Mikael

                  • 6. Re: Alternative WS client action contribution
                    nico_ale

                    hi, I try to use this code to invoke a web service whit an https endpoint and the server answer this error,

                    Unable to create SSL Socket Factory for client invoker: Error initializing socket factory SSL context: Can not find truststore url.

                    any idea.

                    • 7. Re: Alternative WS client action contribution

                      You need to make sure that you have configured the security trust store properties "accordingly".

                      E.g. update run.conf with something like:
                      JAVA_OPTS="$JAVA_OPTS -Djavax.net.ssl.trustStore=../server/default/conf/my.truststore -Djavax.net.ssl.trustStorePassword=mynotsosecretpassword"

                      Note that this depends on if you are using the JDK bundled "cacerts" file or if you've created your own trust store.