]
Faisal Azizullah updated JBESB-1397:
------------------------------------
Attachment: jbossesb-soap.jar
The SOAPClient.java is updated
SOAP Action in Soap header
--------------------------
Key: JBESB-1397
URL:
http://jira.jboss.com/jira/browse/JBESB-1397
Project: JBoss ESB
Issue Type: Feature Request
Security Level: Public(Everyone can see)
Components: Web Services
Affects Versions: 4.2.1
Environment: JBOSS AS 4.2.0 GA
Reporter: Faisal Azizullah
Attachments: jbossesb-soap.jar
The SOAPAction when sent thru the HTTP header didn't had any quotes around it and it
was needed to call a webservice, I just added quotes to the line and it is working perfect
the code is as below
package org.jboss.soa.esb.actions.soap;
import java.io.*;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import java.net.URI;
import java.net.URISyntaxException;
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.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.soap.OGNLUtils;
import org.jboss.internal.soa.esb.util.StreamUtils;
import org.jboss.soa.esb.ConfigurationException;
import org.jboss.soa.esb.listeners.message.MessageDeliverException;
import org.jboss.soa.esb.http.HttpClientFactory;
import org.jboss.soa.esb.actions.AbstractActionPipelineProcessor;
import org.jboss.soa.esb.actions.ActionProcessingException;
import org.jboss.soa.esb.actions.ActionUtils;
import org.jboss.soa.esb.actions.ActionLifecycleException;
import org.jboss.soa.esb.helpers.ConfigTree;
import org.jboss.soa.esb.message.Message;
import org.jboss.soa.esb.message.MessagePayloadProxy;
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.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>
* <action name="soapui-client-action"
class="org.jboss.soa.esb.actions.soap.SOAPClient">
* <property name="wsdl"
value="http://localhost:18080/acme/services/OrderManagement?wsdl"/>
* <property name="SOAPAction"
value="http://www.acme.com/OrderManagement/SendSalesOrderNotificatio...
* </action></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(<String, Object>), 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>
* <soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
* xmlns:cus="http://schemas.acme.com">
* <soapenv:Header/>
* <soapenv:Body>
* <cus:<b color="red">customerOrder</b>>
* <cus:<b color="red">header</b>>
* <cus:<b
color="red">customerNumber</b>>123456</cus:customerNumber>
* </cus:header>
* </cus:customerOrder>
* </soapenv:Body>
* </soapenv:Envelope>
* </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>
* <soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
*
xmlns:cus="http://schemas.active-endpoints.com/sample/customerorder/...
*
xmlns:stan="http://schemas.active-endpoints.com/sample/standardtypes...
* <soapenv:Header/>
* <soapenv:Body>
* <cus:<b color="red">customerOrder</b>>
* <cus:<b color="red">items</b>>
* <cus:item>
* <cus:<b
color="red">partNumber</b>>FLT16100</cus:partNumber>
* <cus:description>Flat 16 feet 100
count</cus:description>
* <cus:quantity>50</cus:quantity>
* <cus:price>490.00</cus:price>
* <cus:extensionAmount>24500.00</cus:extensionAmount>
* </cus:item>
* <cus:item>
* <cus:<b
color="red">partNumber</b>>RND08065</cus:partNumber>
* <cus:description>Round 8 feet 65
count</cus:description>
* <cus:quantity>9</cus:quantity>
* <cus:price>178.00</cus:price>
* <cus:extensionAmount>7852.00</cus:extensionAmount>
* </cus:item>
* </cus:items>
* </cus:customerOrder>
* </soapenv:Body>
* </soapenv:Envelope>
* </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(<String, String>), 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.
* </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>
* <action name="soapui-client-action"
class="org.jboss.soa.esb.actions.soap.SOAPClient">
* <property name="wsdl"
value="http://localhost:18080/acme/services/OrderManagement?wsdl"/>
* <property name="SOAPAction"
value="http://www.acme.com/OrderManagement/GetOrder"/>
* <property name="paramsLocation"
value="get-order-params" />
* <property name="responseLocation"
value="get-order-response" />
* <property name="responseXStreamConfig">
* <alias name="customerOrder"
class="com.acme.order.Order"
namespace="http://schemas.acme.com/services/CustomerOrder.xsd" />
* <alias name="orderheader"
class="com.acme.order.Header"
namespace="http://schemas.acme.com/services/CustomerOrder.xsd" />
* <alias name="item"
class="com.acme.order.OrderItem"
namespace="http://schemas.acme.com/services/CustomerOrder.xsd" />
* </property>
* </action>
* </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/>
* To have the SOAP reponse 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>
* <action name="soapui-client-action"
class="org.jboss.soa.esb.actions.soap.SOAPClient">
* <property name="wsdl"
value="http://localhost:18080/acme/services/OrderManagement?wsdl"/>
* <property name="SOAPAction"
value="http://www.acme.com/OrderManagement/GetOrder"/>
* <property name="paramsLocation"
value="get-order-params" />
* <property name="responseLocation"
value="get-order-response" />
* <property name="responseAsOgnlMap" value="true"
/>
* </action>
* </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>
* <property name="smooksTransform"
value="/transforms/order-transform.xml" /></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>
* <property name="SOAPNS"
value="http://www.w3.org/2009/09/soap-envelope"/>
* </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>
*/
public class SOAPClient extends AbstractActionPipelineProcessor {
private Logger logger = Logger.getLogger(SOAPClient.class);
private String wsdl;
private String soapAction;
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 MessagePayloadProxy payloadProxy;
public SOAPClient(ConfigTree config) throws ConfigurationException {
wsdl = config.getRequiredAttribute("wsdl");
soapAction = config.getRequiredAttribute("SOAPAction");
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");
if(xstreamAliases != null && xstreamAliases.length != 0) {
configureXStreamDeserializer(xstreamAliases);
}
soapNs = config.getAttribute("SOAPNS");
// Extract the HttpClient creation properties from the ConfigTree. Thesee are
passed
// to the HttpClientFacatory...
extractHttpClientProps(config);
httpclient = HttpClientFactory.createHttpClient(httpClientProps);
}
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 SoapUIInvoker();
}
private void extractHttpClientProps(ConfigTree config) {
ConfigTree[] httpClientConfigTrees =
config.getChildren("http-client-property");
httpClientProps.setProperty(HttpClientFactory.TARGET_HOST_URL, wsdl);
// 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 {
Map params;
try {
params = (Map) payloadProxy.getPayload(message);
} catch (MessageDeliverException e) {
throw new ActionProcessingException("No params. SOAP message parameters
must either be set as the default message body payload, or set on the body under the key
defined in the 'paramsLocation' acton property.");
}
if(params.isEmpty()) {
logger.warn("Params Map found in message, but the map is empty.");
}
String request;
try {
request = soapUIInvoker.buildRequest(wsdl, getEndpointOperation(), 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);
}
String response = invokeEndpoint(request);
// 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 String invokeEndpoint(String request) throws ActionProcessingException {
String endpoint;
try {
endpoint = soapUIInvoker.getEndpoint(wsdl, httpClientProps);
} catch (IOException e) {
throw new ActionProcessingException("soapUI Client Service invocation
failed.", e);
}
PostMethod post = new PostMethod(endpoint);
post.setRequestHeader("Content-Type",
"text/xml;charset=UTF-8");
post.setRequestHeader("SOAPAction", "\"" + soapAction +
"\""); /// Customization to add quotes to Soap action
post.setRequestEntity(new StringRequestEntity(request));
try {
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 post.getResponseBodyAsString();
} 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, String response) throws
ActionProcessingException {
Object responseObject;
if(responseXStreamDeserialzer != null) {
responseObject = applyXStreamResponseDeserializer(response);
} else if(responseAsOgnlMap) {
responseObject = populateResponseOgnlMap(response);
} else {
// Otherwise, the response Object is the raw SOAP String...
responseObject = response;
}
try {
payloadProxy.setPayload(message, responseObject);
} catch (MessageDeliverException e) {
throw new ActionProcessingException(e);
}
}
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 = docBuilderFactory.newDocumentBuilder();
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 void configureXStreamDeserializer(ConfigTree[] xstreamAliases) 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.");
}
}
}
}
--
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators: