Package org.jboss.soa.esb.actions.soap

Source Code of org.jboss.soa.esb.actions.soap.SOAPClient$Response

/*
* JBoss, Home of Professional Open Source
* Copyright 2006, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/

package org.jboss.soa.esb.actions.soap;

import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;

import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.apache.log4j.Logger;
import org.jboss.internal.soa.esb.publish.Publish;
import org.jboss.internal.soa.esb.soap.OGNLUtils;
import org.jboss.internal.soa.esb.util.StreamUtils;
import org.jboss.soa.esb.ConfigurationException;
import org.jboss.soa.esb.actions.AbstractActionPipelineProcessor;
import org.jboss.soa.esb.actions.ActionLifecycleException;
import org.jboss.soa.esb.actions.ActionProcessingException;
import org.jboss.soa.esb.actions.ActionUtils;
import org.jboss.soa.esb.helpers.ConfigTree;
import org.jboss.soa.esb.http.HttpClientFactory;
import org.jboss.soa.esb.http.configurators.Connection;
import org.jboss.soa.esb.listeners.ListenerTagNames;
import org.jboss.soa.esb.listeners.message.MessageDeliverException;
import org.jboss.soa.esb.message.Message;
import org.jboss.soa.esb.message.MessagePayloadProxy;
import org.jboss.soa.esb.message.ResponseHeader;
import org.jboss.soa.esb.message.ResponseStatus;
import org.jboss.soa.esb.message.body.content.BytesBody;
import org.jboss.soa.esb.util.ClassUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.annotations.Annotations;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.xml.QNameMap;
import com.thoughtworks.xstream.io.xml.StaxDriver;

/**
* SOAP Client action processor.
* <p/>
* Uses the soapUI Client Service to construct and populate a message
* for the target service.  This action then routes that message to that service.
*
* <h2>Endpoint Operation Specification</h2>
* Specifying the endpoint operation is a straightforward task.  Simply specify the "wsdl" and
* "SOAPAction" properties on the SOAPClient action as follows:
* <pre>
*   &lt;action name="soapui-client-action" class="org.jboss.soa.esb.actions.soap.SOAPClient"&gt;
*       &lt;property name="wsdl" value="http://localhost:18080/acme/services/OrderManagement?wsdl"/&gt;
*       &lt;property name="SOAPAction" value="http://www.acme.com/OrderManagement/SendSalesOrderNotification"/&gt;
*   &lt;/action&gt;</pre>
* The SOAP operation is derived from the SOAPAction.
*
* <h2 id="request-construction">SOAP Request Message Construction</h2>
* The SOAP operation parameters are supplied in one of 2 ways:
* <ol>
*      <li>As a {@link Map} instance set on the <i>default body location</i> (Message.getBody().add(Map))</li>
*      <li>As a {@link Map} instance set on in a <i>named body location</i> (Message.getBody().add(String, Map)),
*          where the name of that body location is specified as the value of the "paramsLocation" action property.
*      </li>
* </ol>
* The parameter {@link Map} itself can also be populated in one of 2 ways:
* <ol>
*      <li><b>Option 1</b>: With a set of Objects that are accessed (for SOAP message parameters) using the
*          <a href="http://www.ognl.org/">OGNL</a></li> framework.  More on the use of OGNL below.
*      <li><b>Option 2</b>: With a set of String based key-value pairs(&lt;String, Object&gt;), where the
*          key is an OGNL expression identifying the SOAP parameter to be populated with
*          the key's value. More on the use of OGNL below.
*      </li>
* </ol>
* As stated above, <a href="http://www.ognl.org/">OGNL</a> is the mechanism we use for selecting
* the SOAP parameter values to be injected into the SOAP message from the supplied parameter
* {@link Map}.  The OGNL expression for a specific parameter within the SOAP message depends on that
* the position of that parameter within the SOAP body.  In the following message:
* <pre>
*    &lt;soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
*                        xmlns:cus="http://schemas.acme.com"&gt;
*      &lt;soapenv:Header/&gt;
*      &lt;soapenv:Body&gt;
*     &lt;cus:<b color="red">customerOrder</b>&gt;
*       &lt;cus:<b color="red">header</b>&gt;
*         &lt;cus:<b color="red">customerNumber</b>&gt;123456&lt;/cus:customerNumber&gt;
*       &lt;/cus:header&gt;
*     &lt;/cus:customerOrder&gt;
*      &lt;/soapenv:Body&gt;
*   &lt;/soapenv:Envelope&gt
* </pre>
*
* The OGNL expression representing the customerNumber parameter is "<b color="red">customerOrder.header.customerNumber</b>".
* <p/>
* Once the OGNL expression has been calculated for a parameter, this class will check the supplied parameter
* map for an Object keyed off the full OGNL expression (Option 1 above).  If no such parameter Object is present on the map,
* this class will then attempt to load the parameter by supplying the map and OGNL expression instances to the
* OGNL toolkit (Option 2 above).  If this doesn't yield a value, this parameter location within the SOAP message will
* remain blank.
* <p/>
* Taking the sample message above and using the "Option 1" approach to populating the "customerNumber" requires an
* object instance (e.g. an "Order" object instance) to be set on the parameters map under the key "customerOrder".  The "customerOrder"
* object instance needs to contain a "header" property (e.g. a "Header" object instance).  The object instance
* behind the "header" property (e.g. a "Header" object instance) should have a "customerNumber" property.
* <p/>
* OGNL expressions associated with Collections are constructed in a slightly different way.  This is easiest explained
* through an example:
* <pre>
* &lt;soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
*               xmlns:cus="http://schemas.active-endpoints.com/sample/customerorder/2006/04/CustomerOrder.xsd"
*               xmlns:stan="http://schemas.active-endpoints.com/sample/standardtypes/2006/04/StandardTypes.xsd"&gt;
*   &lt;soapenv:Header/&gt;
*   &lt;soapenv:Body&gt;
*     &lt;cus:<b color="red">customerOrder</b>&gt;
*       &lt;cus:<b color="red">items</b>&gt;
*         &lt;cus:item&gt;
*           &lt;cus:<b color="red">partNumber</b>&gt;FLT16100&lt;/cus:partNumber&gt;
*           &lt;cus:description&gt;Flat 16 feet 100 count&lt;/cus:description&gt;
*           &lt;cus:quantity&gt;50&lt;/cus:quantity&gt;
*           &lt;cus:price&gt;490.00&lt;/cus:price&gt;
*           &lt;cus:extensionAmount&gt;24500.00&lt;/cus:extensionAmount&gt;
*         &lt;/cus:item&gt;
*         &lt;cus:item&gt;
*           &lt;cus:<b color="red">partNumber</b>&gt;RND08065&lt;/cus:partNumber&gt;
*           &lt;cus:description&gt;Round 8 feet 65 count&lt;/cus:description&gt;
*           &lt;cus:quantity&gt;9&lt;/cus:quantity&gt;
*           &lt;cus:price&gt;178.00&lt;/cus:price&gt;
*           &lt;cus:extensionAmount&gt;7852.00&lt;/cus:extensionAmount&gt;
*         &lt;/cus:item&gt;
*       &lt;/cus:items&gt;
*     &lt;/cus:customerOrder&gt;
*   &lt;/soapenv:Body&gt;
* &lt;/soapenv:Envelope&gt;
* </pre>
* The above order message contains a collection of order "items".  Each entry in the collection is represented
* by an "item" element.  The OGNL expressions for the order item "partNumber" is constructed as
* "<b color="red">customerOrder.items[0].partnumber</b>" and "<b color="red">customerOrder.items[1].partnumber</b>".
* As you can see from this, the collection entry element (the "item" element) makes no explicit appearance in the OGNL
* expression.  It is represented implicitly by the indexing notation.  In terms of an Object Graph (Option 1 above),
* this could be represented by an Order object instance (keyed on the map as "customerOrder") containing an "items"
* list ({@link java.util.List} or array), with the list entries being "OrderItem" instances, which in turn contains
* "partNumber" etc properties.
* <p/>
* Option 2 (above) provides a quick-and-dirty way to populate a SOAP message without having to create an Object
* model ala Option 1.  The OGNL expressions that correspond with the SOAP operation parameters are exactly the same
* as for Option 1, except that there's not Object Graph Navigation involved.  The OGNL expression is simply used as
* the key into the {@link Map}, with the corresponding key-value being the parameter.
*
* <h2>SOAP Response Message Consumption</h2>
* The SOAP response object instance can be is attached to the ESB {@link Message} instance in one of the
* following ways:
* <ol>
*      <li>On the <i>default body location</i> (Message.getBody().add(Map))</li>
*      <li>On in a <i>named body location</i> (Message.getBody().add(String, Map)),
*          where the name of that body location is specified as the value of the "responseLocation" action property.
*      </li>
* </ol>
* The response object instance can also be populated (from the SOAP response) in one of 3 ways:
* <ol>
*      <li><b>Option 1</b>: As an Object Graph created and populated by the
*          <a href="http://xstream.codehaus.org">XStream</a> toolkit.
*      <li><b>Option 2</b>: As a set of String based key-value pairs(&lt;String, String&gt;), where the
*          key is an OGNL expression identifying the SOAP response element and the value is a String
*          representing the value from the SOAP message.   This option should only be used for simple
*          responses.  If your response contains collections etc, this option is not viable.  You need
*          to use option 1.
*      </li>
*      <li><b>Option 3</b>: If Options 1 or 2 are not specified in the action configuration, the raw SOAP
*          response message (String) is attached to the message.
*      </li>
* </ol>
* Using <a href="http://xstream.codehaus.org">XStream</a> as a mechanism for populating an Object Graph
* (Option 1 above) is straightforward and works well, as long as the XML and Java object models are in line with
* each other.
* <p/>
* The XStream approach (Option 1) is configured on the action as follows:
* <pre>
*  &lt;action name="soapui-client-action" class="org.jboss.soa.esb.actions.soap.SOAPClient"&gt;
*      &lt;property name="wsdl" value="http://localhost:18080/acme/services/OrderManagement?wsdl"/&gt;
*      &lt;property name="SOAPAction" value="http://www.acme.com/OrderManagement/GetOrder"/&gt;
*      &lt;property name="paramsLocation" value="get-order-params" /&gt;
*      &lt;property name="responseLocation" value="get-order-response" /&gt;
*      &lt;property name="responseXStreamConfig"&gt;
*          &lt;alias name="customerOrder" class="com.acme.order.Order" namespace="http://schemas.acme.com/services/CustomerOrder.xsd" /&gt;
*          &lt;alias name="orderheader" class="com.acme.order.Header" namespace="http://schemas.acme.com/services/CustomerOrder.xsd" /&gt;
*          &lt;alias name="item" class="com.acme.order.OrderItem" namespace="http://schemas.acme.com/services/CustomerOrder.xsd" /&gt;
*      &lt;/property&gt;
*  &lt;/action&gt;
* </pre>
* In the above example, we also include an example of how to specify non-default named locations for the request
* parameters {@link Map} and response object instance.
* <p/>
* We also provide, in addition to the above <a href="http://xstream.codehaus.org">XStream</a> configuration options,
* the ability to specify field name mappings and <a href="http://xstream.codehaus.org">XStream</a> annotated classes.
* <pre>
*      &lt;property name="responseXStreamConfig"&gt;
*          &lt;fieldAlias name="header" class="com.acme.order.Order" fieldName="headerFieldName" /&gt;
*          &lt;annotation class="com.acme.order.Order" /&gt;
*      &lt;/property&gt;
* </pre>
* Field mappings can be used to map XML elements onto Java fields on those occasions when the local name of the element
* does not correspond to the field name in the Java class.
* <p/>
* To have the SOAP response data extracted into an OGNL keyed map (Option 2 above) and attached to the ESB
* {@link Message}, simply replace the "responseXStreamConfig" property with the "responseAsOgnlMap" property
* having a value of "true" as follows:
* <pre>
*  &lt;action name="soapui-client-action" class="org.jboss.soa.esb.actions.soap.SOAPClient"&gt;
*      &lt;property name="wsdl" value="http://localhost:18080/acme/services/OrderManagement?wsdl"/&gt;
*      &lt;property name="SOAPAction" value="http://www.acme.com/OrderManagement/GetOrder"/&gt;
*      &lt;property name="paramsLocation" value="get-order-params" /&gt;
*      &lt;property name="responseLocation" value="get-order-response" /&gt;
*      &lt;property name="responseAsOgnlMap" value="true" /&gt;
*  &lt;/action&gt;
* </pre>
* To return the raw SOAP message as a String (Option 3), simply omit both the "responseXStreamConfig"
* and "responseAsOgnlMap" properties.
*
* <h2>Transforming the SOAP Request Message</h2>
* It's often necessary to be able to transform the SOAP request before sending it. This may be to simply
* add some SOAP headers.
* <p/>
* Transformation of the SOAP request (before sending) is supported by configuring the SOAPClient action
* with a Smooks transformation configuration property as follows:
* <pre>
*     &lt;property name="smooksTransform" value="/transforms/order-transform.xml" /&gt;</pre>
*
* The value of the "smooksTransform" property is resolved by first checking it as a filesystem based resource.
* Failing that, it's checked as a classpath resource and failing that, as a URI based resource.
*
* <h3>Specifying a different SOAP schema</h3>
* <pre>
*    &lt;property name="SOAPNS" value="http://www.w3.org/2009/09/soap-envelope"/&gt;
* </pre>
* This is an optional property and can be used to specify the SOAP schema that should be
* used by OGNL.
*
* @author <a href="mailto:tom.fennelly@jboss.com">tom.fennelly@jboss.com</a>
*/
@Publish(WsdlContractPublisher.class)
public class SOAPClient extends AbstractActionPipelineProcessor {

    private Logger logger = Logger.getLogger(SOAPClient.class);

    private String wsdl;
    private String soapAction;
  private String soapServiceName;
    private String soapNs;
    private String paramsLocation;
    private String responseLocation;
    private boolean responseAsOgnlMap;
    private String smooksTransform;
    private SoapUIInvoker soapUIInvoker;
    private static DocumentBuilderFactory docBuilderFactory = createDocumentBuilderFactory();
    private XStream responseXStreamDeserialzer;
    private QNameMap responseXStreamQNameMap = new QNameMap();
    private Properties httpClientProps = new Properties();
    private HttpClient httpclient;
    private String endpoint;
    private String endpointUrl;
    private String contentType;
    private boolean endpointInitialized = false;
    private MessagePayloadProxy payloadProxy;
    private boolean httpResponseStatusEnabled;

    public SOAPClient(ConfigTree config) throws ConfigurationException {
        wsdl = config.getRequiredAttribute("wsdl");
        soapAction = config.getRequiredAttribute("SOAPAction");
        soapServiceName = config.getAttribute("SOAPService");
       
        createPayloadProxy(config);
        responseAsOgnlMap = "true".equalsIgnoreCase(config.getAttribute("responseAsOgnlMap"));
        smooksTransform = config.getAttribute("smooksTransform");
        if(smooksTransform != null) {
            try {
                smooksTransform = StreamUtils.getResourceAsString(smooksTransform, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                logger.error("Unable to read Smooks resource '" + smooksTransform + "'. Must be UTF-8 encoded.");
            }
        }
        ConfigTree[] xstreamAliases = config.getChildren("alias");
        ConfigTree[] xstreamFieldAliases = config.getChildren("fieldAlias");
        ConfigTree[] xstreamAnnotations = config.getChildren("annotation");
        if ((xstreamAliases.length != 0) || (xstreamFieldAliases.length != 0) ||
            (xstreamAnnotations.length != 0))
        {
            configureXStreamDeserializer(xstreamAliases, xstreamFieldAliases, xstreamAnnotations);
        }
        soapNs = config.getAttribute("SOAPNS");

        // Extract the HttpClient creation properties from the ConfigTree.  These are passed
        // to the HttpClientFacatory...
        extractHttpClientProps(config);
        httpclient = HttpClientFactory.createHttpClient(httpClientProps);

        endpointUrl = config.getAttribute("endpointUrl");

        httpResponseStatusEnabled = ResponseStatus.isHttpEnabled(config);
    }

    private void createPayloadProxy(ConfigTree config) {
        paramsLocation = config.getAttribute("paramsLocation");
        responseLocation = config.getAttribute("responseLocation");

        String[] legacyGetLocs;
        String[] legacySetLocs;
        if(paramsLocation != null) {
            legacyGetLocs = new String[] {paramsLocation, BytesBody.BYTES_LOCATION, ActionUtils.POST_ACTION_DATA};
        } else {
            legacyGetLocs = new String[] {BytesBody.BYTES_LOCATION, ActionUtils.POST_ACTION_DATA};
        }
        if(responseLocation != null) {
            legacySetLocs = new String[] {responseLocation, ActionUtils.POST_ACTION_DATA};
        } else {
            legacySetLocs = new String[] {ActionUtils.POST_ACTION_DATA};
        }

        payloadProxy = new MessagePayloadProxy(config, legacyGetLocs, legacySetLocs);
    }


    public void initialise() throws ActionLifecycleException {
        super.initialise();
        // Create the SoapUIInvoker instance for this SOAPClient...
        soapUIInvoker = new MBeanSoapUIInvoker();
    }

    public SoapUIInvoker getSoapUIInvoker() {
        return soapUIInvoker;
    }

    public void setSoapUIInvoker(SoapUIInvoker soapUIInvoker) {
        this.soapUIInvoker = soapUIInvoker;
    }

    @Override
    public void destroy() throws ActionLifecycleException {
        if (httpclient != null) {
            HttpClientFactory.shutdown(httpclient);
        }
        super.destroy();
    }

    private void extractHttpClientProps(ConfigTree config) {
        ConfigTree[] httpClientConfigTrees = config.getChildren(HttpClientFactory.HTTP_CLIENT_PROPERTY);

        httpClientProps.setProperty(HttpClientFactory.TARGET_HOST_URL, wsdl);
        final ConfigTree parent = config.getParent();
        if (parent != null) {
            final String maxThreads = parent.getAttribute(ListenerTagNames.MAX_THREADS_TAG);
            if (maxThreads != null) {
                httpClientProps.setProperty(Connection.MAX_TOTAL_CONNECTIONS, maxThreads);
                httpClientProps.setProperty(Connection.MAX_CONNECTIONS_PER_HOST, maxThreads);
            }
        }

        // The HttpClient properties are attached under the factory class/impl property as <http-client-property name="x" value="y" /> nodes
        for(ConfigTree httpClientProp : httpClientConfigTrees) {
            String propName = httpClientProp.getAttribute("name");
            String propValue = httpClientProp.getAttribute("value");

            if(propName != null && propValue != null) {
                httpClientProps.setProperty(propName, propValue);
            }
        }
    }

    public Message process(final Message message) throws ActionProcessingException {
        Object payload;

        try {
            payload = payloadProxy.getPayload(message);
        } catch (MessageDeliverException e) {
            throw new ActionProcessingException("Error getting SOAP message parameters from payload.", e);
        }
        catch (final ClassCastException ex)
        {
            throw new ActionProcessingException("Required a Map in the payload, but got something else!");
        }

        if (!(payload instanceof Map)) {
            throw new ActionProcessingException("Invalid payload type in message body location '" + payloadProxy.getGetPayloadLocation() + "'.  Expected 'java.util.Map', was '" + payload.getClass().getName() + "'.");
        }

        Map params = (Map) payload;
        if (params.isEmpty()) {
            logger.warn("Params Map found in message, but the map is empty.");
        }

        String request;
        try {
            request = soapUIInvoker.buildRequest(wsdl, getEndpointOperation(), soapServiceName, params, httpClientProps, smooksTransform, soapNs);

        } catch (IOException e) {
            throw new ActionProcessingException("soapUI Client Service invocation failed.", e);
        } catch (SAXException e) {
            throw new ActionProcessingException("soapUI Client Service invocation failed.", e);
        }
        Response response = invokeEndpoint(request);

        if(responseAsOgnlMap) {
            try {
                String mergedResponse = soapUIInvoker.mergeResponseTemplate(wsdl, getEndpointOperation(), soapServiceName, response.getBody(), httpClientProps, null, soapNs);
                response.setBody(mergedResponse);
            } catch (IOException e) {
                throw new ActionProcessingException("soapUI Client Service invocation failed.", e);
            } catch (SAXException e) {
                throw new ActionProcessingException("soapUI Client Service invocation failed.", e);
            }
        }

        // And process the response into the message...
        processResponse(message, response);

        return message;
    }

    public String getSoapNS()
  {
    return soapNs;
  }

    protected String getEndpointOperation() {
        URI soapActionURI;

        try {
            soapActionURI = new URI(soapAction);
            return new File(soapActionURI.getPath()).getName();
        } catch (URISyntaxException e) {
            return soapAction;
        }
    }
   
    protected static class Response {
      private Header[] headers;
      private String body;
      private int statusCode;
      private String statusMessage;
      protected Response(String body) {
        this(body, new Header[0], 0, null);
      }
      protected Response(String body, Header[] headers, int statusCode, String statusMessage) {
        this.headers = headers;
        this.body = body;
        this.statusCode = statusCode;
        this.statusMessage = statusMessage;
      }
      protected Header[] getHeaders() {
        return headers;
      }
      protected String getBody() {
        return body;
      }
      protected void setBody(String body) {
        this.body = body;
      }
      protected int getStatusCode() {
        return statusCode;
      }
      protected String getStatusMessage() {
        return statusMessage;
      }
    }

    private Response invokeEndpoint(String request) throws ActionProcessingException {
        if (!endpointInitialized) {
            try {
                if(endpointUrl != null) {
                    endpoint = endpointUrl;
                } else {
                    endpoint = soapUIInvoker.getEndpoint(wsdl, soapServiceName, httpClientProps);
                }
                contentType = soapUIInvoker.getContentType(wsdl, soapServiceName, httpClientProps) + ";charset=UTF-8";

            } catch (IOException e) {
                throw new ActionProcessingException("soapUI Client Service invocation failed.", e);
            }
            endpointInitialized = true;
        }
        PostMethod post = new PostMethod(endpoint);

        post.setRequestHeader("Content-Type", contentType);
        post.setRequestHeader("Connection", "keep-alive");
        post.setRequestHeader("SOAPAction", "\"" + soapAction + "\"")/// Customization to add quotes to Soap action
        try {
          post.setRequestEntity(new StringRequestEntity(request, "text/xml", "UTF-8"));
            int result = httpclient.executeMethod(post);
            if(result != HttpStatus.SC_OK) {
                // TODO: We need to do more here!!

                logger.warn("Received status code '" + result + "' on HTTP SOAP (POST) request to '" + endpoint + "'.");
            }
            return new Response(post.getResponseBodyAsString(), post.getResponseHeaders(), result, post.getStatusLine().getReasonPhrase());
        } catch (IOException e) {
            throw new ActionProcessingException("Failed to invoke SOAP Endpoint: '" + endpoint + " ' - '" + soapAction + "'.", e);
        } finally {
            post.releaseConnection();
        }
    }

    /**
     * Process the SOAP response into the ESB Message instance.
     * @param message The ESB Message.
     * @param response The SOAP response.
     * @throws ActionProcessingException Error processing the response.
     */
    protected void processResponse(Message message, Response response) throws ActionProcessingException {
        Object responseObject;

        if(responseXStreamDeserialzer != null) {
            responseObject = applyXStreamResponseDeserializer(response.getBody());
        } else if(responseAsOgnlMap) {
            responseObject = populateResponseOgnlMap(response.getBody());
        } else {
            // Otherwise, the response Object is the raw SOAP String...
            responseObject = response.getBody();
        }

        try {
            payloadProxy.setPayload(message, responseObject);
        } catch (MessageDeliverException e) {
            throw new ActionProcessingException(e);
        }
       
        // JBESB-2511
        org.jboss.soa.esb.message.Properties properties = message.getProperties();
        for (Header header : response.getHeaders()) {
          new ResponseHeader(header.getName(), header.getValue()).setPropertyNameThis(properties);
        }
        // JBESB-2761
        if (httpResponseStatusEnabled) {
          ResponseStatus.setHttpProperties(properties, response.getStatusCode(), response.getStatusMessage());
        }
    }

    private Object applyXStreamResponseDeserializer(String response) {
        StaxDriver driver = new StaxDriver(responseXStreamQNameMap);
        HierarchicalStreamReader responseReader = driver.createReader(new StringReader(response));

        // Move inside the soap body element...
        responseReader.moveDown();
        while(!responseReader.getNodeName().toLowerCase().endsWith("body")) {
            responseReader.moveUp();
            responseReader.moveDown();
        }
        responseReader.moveDown();

        return responseXStreamDeserialzer.unmarshal(responseReader);
    }

    private Map<String, String> populateResponseOgnlMap(String response) throws ActionProcessingException {
        Map<String, String> map = new LinkedHashMap<String, String>();

        try {
            DocumentBuilder docBuilder = getDocBuilder() ;
            Document doc = docBuilder.parse(new InputSource(new StringReader(response)));
            Element graphRootElement = getGraphRootElement(doc.getDocumentElement());

            populateResponseOgnlMap(map, graphRootElement);
        } catch (ParserConfigurationException e) {
            throw new ActionProcessingException("Unexpected DOM Parser configuration error.", e);
        } catch (SAXException e) {
            throw new ActionProcessingException("Error parsing SOAP response.", e);
        } catch (IOException e) {
            throw new ActionProcessingException("Unexpected error reading SOAP response.", e);
        }

        return map;
    }
   
    private void populateResponseOgnlMap(Map<String, String> map, Element element) {
        NodeList children = element.getChildNodes();
        int childCount = children.getLength();

        // If the element has a solitary TEXT child, add the text value
        // against a map key of the elements OGNL expression value.
        if(childCount == 1) {
            Node childNode = children.item(0);
            if(childNode.getNodeType() == Node.TEXT_NODE) {
                String ognl = OGNLUtils.getOGNLExpression(element);
                map.put(ognl, childNode.getTextContent());
                return;
            }
        }

        // So the element doesn't contain a solitary TEXT node.  Drill down...
        for(int i = 0; i < childCount; i++) {
            Node childNode = children.item(i);
            if(childNode.getNodeType() == Node.ELEMENT_NODE) {
                populateResponseOgnlMap(map, (Element)childNode);
            }
        }
    }

    private Element getGraphRootElement(Element element) {
        String ognl = OGNLUtils.getOGNLExpression(element);

        if(ognl != null && !ognl.equals("")) {
            return element;
        }

        NodeList children = element.getChildNodes();
        int childCount = children.getLength();
        for(int i = 0; i < childCount; i++) {
            Node node = children.item(i);
            if(node.getNodeType() == Node.ELEMENT_NODE) {
                Element graphRootElement = getGraphRootElement((Element)node);
                if(graphRootElement != null) {
                    return graphRootElement;
                }
            }
        }

        return null;
    }

    private static DocumentBuilderFactory createDocumentBuilderFactory() {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

        factory.setNamespaceAware(true);
        factory.setExpandEntityReferences(true);

        return factory;
    }

    private static synchronized DocumentBuilder getDocBuilder() throws ParserConfigurationException {
        return docBuilderFactory.newDocumentBuilder();
    }

    private void configureXStreamDeserializer(ConfigTree[] xstreamAliases, ConfigTree[] xstreamFieldAliases, ConfigTree[] xstreamAnnotations) throws ConfigurationException {
        responseXStreamDeserialzer = new XStream();
        for(ConfigTree xstreamAlias : xstreamAliases) {
            String aliasName = xstreamAlias.getRequiredAttribute("name");
            String aliasTypeName = xstreamAlias.getRequiredAttribute("class");
            String namespace = xstreamAlias.getRequiredAttribute("namespace");

            try {
                Class aliasType = ClassUtil.forName(aliasTypeName, getClass());
                responseXStreamQNameMap.registerMapping(new QName(namespace, aliasName), aliasType);
            } catch (ClassNotFoundException e) {
                throw new ConfigurationException("Invalid SOAP response deserializer config. XStream alias type '" + aliasTypeName + "' not found.");
            }
        }
        for(ConfigTree xstreamFieldAlias : xstreamFieldAliases) {
            final String alias = xstreamFieldAlias.getRequiredAttribute("name");
            final String typeName = xstreamFieldAlias.getRequiredAttribute("class");
            final String fieldName = xstreamFieldAlias.getRequiredAttribute("fieldName");
            try {
                final Class type = ClassUtil.forName(typeName, getClass());
                responseXStreamDeserialzer.aliasField(alias, type, fieldName);
            } catch (final ClassNotFoundException cnfe) {
                throw new ConfigurationException("Invalid SOAP response deserializer config. XStream alias type '" + typeName + "' not found.");
            }
        }
        for(ConfigTree xstreamAnnotation : xstreamAnnotations) {
            final String typeName = xstreamAnnotation.getRequiredAttribute("class");
            try {
                final Class type = ClassUtil.forName(typeName, getClass());
                Annotations.configureAliases(responseXStreamDeserialzer, type) ;
            } catch (final ClassNotFoundException cnfe) {
                throw new ConfigurationException("Invalid SOAP response deserializer config. XStream alias type '" + typeName + "' not found.");
            }
        }
    }
}
TOP

Related Classes of org.jboss.soa.esb.actions.soap.SOAPClient$Response

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.