Monday, November 30, 2009

Proxying SOAP Web Services in JBossESB 4.7

JBossESB supports SOAP web services via various mechanisms:
  • SOAPClient for invoking external web services.
  • SOAPProducer for invoking internally-deployed JBossWS services.
  • EBWS (ESB-Based Web Services) for decorating service requests/responses with a SOAP Envelope.
  • HttpRouter can be used to manually invoke an HTTP endpoint.
  • Explicit support for web service proxying via the SOAPProxy action.
Why would I want to proxy a web service from the ESB?
  • To provide loose coupling between the client and the proxied service: the client no longer needs to have a direct connection to the remote host.
  • The WSDL contract can be transformed to modify it's parameters or change the service's declared capabilities.
  • A transformation of the SOAP envelope/body can be introduced via the ESB action chain both for the inbound request and outbound response (via XsltAction or SmooksAction). Other custom processing can also be introduced in the chain.
  • Service versioning is possible since clients can connect to 2 or more proxy endpoints on the ESB, each with its own WSDL and/or transformations and routing requirements, and the ESB will send the appropriate message to the appropriate endpoint and provide an ultimate response.
  • Complex context-based routing via ContentBasedRouter.
Here is a figure of a basic web service proxy in the ESB:


As you can see, the ESB action chain can perform any number of out-of-the-box or custom actions before or after the call to the proxied web service. Also, the client of the ESB as well as the proxied web service do not have to be Java-based.

Show me how to configure the ESB to proxy to a .NET-based SOAP web service.

Here it is in 15 short lines:
1:  <?xml version="1.0" encoding="UTF-8"?>
2: <jbossesb xmlns="http://anonsvn.labs.jboss.com/labs/jbossesb/trunk/product/etc/schemas/xml/jbossesb-1.2.0.xsd" parameterReloadSecs="5">
3: <services>
4: <service category="Stock" name="Quote" description="Stock Quote" invmScope="GLOBAL">
5: <listeners>
6: <http-gateway name="StockQuote-GwListener"/>
7: </listeners>
8: <actions mep="RequestResponse">
9: <action name="proxy" class="org.jboss.soa.esb.actions.soap.proxy.SOAPProxy">
10: <property name="wsdl" value="http://www.webservicex.net/stockquote.asmx?WSDL"/>
11: </action>
12: </actions>
13: </service>
14: </services>
15: </jbossesb>

Line 8 specifies a request/response message exchange pattern. Line 9 adds the SOAPProxy action to the processing pipeline. Line 10 is the only required property: the wsdl location. In this example, we are proxying to a .NET-based stock quote service. Line 6 specifies the use of the newly supported HTTPGateway, introduced in JBossESB 4.7.

When a .esb archive is deployed with this configuration, you will see output like this in the server console:
07:17:51,789 INFO [SOAPProxy] mapped soapaction ["http://www.webserviceX.NET/GetQuote"] to binding [{http://www.webserviceX.NET/}StockQuoteSoap]
07:17:51,790 INFO [SOAPProxy] mapped binding [{http://www.webserviceX.NET/}StockQuoteSoap] to transport [org.jboss.soa.esb.actions.soap.proxy.HttpSOAPProxyTransport] with endpoint address: [http://www.webservicex.net/stockquote.asmx]

At this point, the SOAPProxy has transformed the wsdl by rewriting it's soap:address location to the ESB server, so clients of the ESB will invoke it rather than the proxied service itself (line 3 below):
1:  <wsdl:service name="StockQuote">
2: <wsdl:port binding="tns:StockQuoteSoap" name="StockQuoteSoap">
3: <soap:address location="http://192.168.1.103:8080/sample/http/Stock/Quote"/>
4: </wsdl:port>
5: </wsdl:service>

With the introduction of the new HTTPGateway, the wsdl is available at the well-known location of the endpoint URL + "?wsdl" (or "?WSDL"), as shown in the following screenshot of the ESB contract application, available at http://host:port/contract/

In our case, the wsdl URL is http://192.168.1.103:8080/sample/http/Stock/Quote?wsdl
To invoke the ESB, we can use a tool like soapUI to send a test request to http://192.168.1.103:8080/sample/http/Stock/Quote
1:  <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:web="http://www.webserviceX.NET/">
2: <soapenv:Header/>
3: <soapenv:Body>
4: <web:GetQuote>
5: <web:symbol>RHT</web:symbol>
6: </web:GetQuote>
7: </soapenv:Body>
8: </soapenv:Envelope>

The HTTPGateway will receive the request, the SOAPProxy action will invoke the proxied .NET stock quote service, and the following response will be returned:
1:  <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
2: <soap:Body>
3: <GetQuoteResponse xmlns="http://www.webserviceX.NET/">
4: <GetQuoteResult><![CDATA[<StockQuotes><Stock><Symbol>RHT</Symbol><Last>26.80</Last><Date>11/27/2009</Date><Time>1:00pm</Time><Change>0.00</Change><Open>N/A</Open><High>N/A</High><Low>N/A</Low><Volume>0</Volume><MktCap>5.032B</MktCap><PreviousClose>26.80</PreviousClose><PercentageChange>0.00%</PercentageChange><AnnRange>8.30 - 28.94</AnnRange><Earns>0.448</Earns><P-E>59.82</P-E><Name>RED HAT INC</Name></Stock></StockQuotes>]]></GetQuoteResult>
5: </GetQuoteResponse>
6: </soap:Body>
7: </soap:Envelope>

Why can't I just use HttpRouter to do this?
  1. HttpRouter can only talk to one endpoint. One WSDL can specify multiple soap:address locations. With the SOAPProxy, you just config the one location of the WSDL, and it can dynamically route the request to the correct soap:address location.
  2. To use HttpRouter with SOAP, you have to always configure a MappedHeaderList (usually "Content-Type, Accept, Authorization, SOAPAction"). This is unnecessary with the SOAPProxy.
  3. The SOAPProxy allows you to specify a wsdlTransform, which allows you to transform the original WSDL to something else you want the SOAPProxy to expose. (This handles the "versioning" use case, and also usually means you'll want to use XsltAction or SmooksAction in the action pipeline.)
  4. If you are proxying to a BASIC Auth secured web service, the "clientCredentialsRequired" property of the SOAPProxy allows you to specify if the credentials should be passed into the SOAPProxy from the client (the default, in which case the credentials are propagated), or if you want the proxy to be wide-open, but it will handle the authentication to the proxied service for you.
  5. SOAPProxy is designed to talk to more than just HTTP endpoints, although HTTP is all that is implemented right now.
  6. When using the new HTTPGateway to front the SOAPProxy, the ESB will automatically transform and cache the WSDL and make it available at the well-known location of ${endpoint}?wsdl (or ?WSDL).
Tell me more about security.

As mentioned in #4 above, the SOAPProxy can invoke external web services that are protected by BASIC Auth. In addition, these services can also be encrypted using SSL. Here is an example configuration taken from the webservice_proxy_security quickstart which comes bundled with JBossESB 4.7:
1:  <?xml version="1.0" encoding="UTF-8"?>
2: <jbossesb xmlns="http://anonsvn.labs.jboss.com/labs/jbossesb/trunk/product/etc/schemas/xml/jbossesb-1.2.0.xsd" parameterReloadSecs="5">
3: <globals>
4: <war-security method="BASIC" domain="JBossWS"/>
5: </globals>
6: <providers>
7: <http-provider name="HTTP-PROVIDER">
8: <http-bus busid="HTTP-BUS" transportGuarantee="CONFIDENTIAL">
9: <allowed-roles>
10: <role name="friend"/>
11: </allowed-roles>
12: </http-bus>
13: </http-provider>
14: </providers>
15: <services>
16: <service category="Proxy_Security" name="Proxy" description="Security WebService Proxy" invmScope="GLOBAL">
17: <listeners>
18: <http-gateway name="HTTP-GATEWAY" busidref="HTTP-BUS" urlPattern="ProxyWS/*"/>
19: </listeners>
20: <actions mep="RequestResponse">
21: <action name="proxy" class="org.jboss.soa.esb.actions.soap.proxy.SOAPProxy">
22: <!-- property name="wsdl" value="https://localhost:8443/webservice_proxy_security/HelloWorldWS?wsdl"/ -->
23: <property name="wsdl" value="internal://jboss.ws:context=webservice_proxy_security,endpoint=HelloWorldWS"/>
24: <property name="endpointUrl" value="https://localhost:8443/webservice_proxy_security/HelloWorldWS"/>
25: <property name="file" value="/META-INF/httpclient-8443.properties"/>
26: </action>
27: </actions>
28: </service>
29: </services>
30: </jbossesb>
Lines 3-14 configure the HTTPGateway for BASIC Auth and SSL support. Line 24 overrides the endpointUrl. This is an example of an HttpRouter property (Note: the SOAPProxy wraps usage of HttpRouter), a useful property when domain name matching is important for SSL certs. Line 25 specifies a properties file for httpclient configuration.

What's with the "internal" wsdl location?

Line 22 (a "normal" http URL) is commented out and instead, on Line 23, you see an "internal" URL. In JBossESB 4.7, the value can reference a location based on five different schemes: http, https, file, classpath or internal (JBossWS jmx mbean object name). Here are some examples:
http://localhost:8080/Quickstart_webservice_proxy_basic_ws/HelloWorldWS?wsdl
https://localhost:8443/webservice_proxy_security/HelloWorldWS?wsdl
file:///tmp/HelloWorldWS.wsdl
classpath://META-INF/HelloWorldWS.wsdl
internal://jboss.ws:context=Quickstart_webservice_proxy_basic_ws,endpoint=HelloWorldWS

I want to learn more...

Other quickstart samples available are webservice_proxy_basic, webservice_proxy_routed (an example using the ContentBasedRouter to route to one of two proxied endpoints) and webservice_proxy_versioning (an example showing support for old and new versions of the same endpoint via transformation).

Of course, you are always welcome to ask questions in the JBossESB project forum.

Monday, November 23, 2009

MessageAlerts in JBossESB 4.7

Is One of My Services or Actions Clogging Things Up?

JBossESB 4.7 includes some great new features. In this post, I wanted to highlight a feature that can help answer the above question.

The new feature feature is "MessageAlerts" (org.jboss.soa.esb.listeners.message.MessageAlerts). This feature enables you to collect fine grained information on the processing time or number of bytes that a specific service, or a specific action in a service requires to process a single message.

You use MessageAlerts by defining processing time and byte total thresholds on a per service and/or action basis. When the service or action in question takes longer than the defined time threshold to process a message, a WARNING is written to the server log and made available in the JMX console. You define the thresholds with these properties:

alertTimeThreshold="420"
alertLengthThreshold="10"

The time-related threshold is measured in milliseconds. The length-related threshold is measured in bytes.

When would you use the byte total threshold? This can be useful if, for example, the service or action that you are watching performs transformations, and suddenly begins processing way more bytes than you're expecting that it should.

An Example in a Quickstart

The MessageAlerts feature is demonstrated in a quickstart named, appropriately enough, "messagealert." If you've ever run the "helloworld" quickstart, then this quickstart should be familiar. The only changes are the addition of the org.jboss.soa.esb.samples.quickstart.messagealerts.DelayAction action class (as you can guess by the name, this class introduces a delay to cause the alertTimeThreshold to be reached, and the changes in the jboss-esb.xml file listed below:

1:   <services>
2: <service
3: category="FirstServiceESB"
4: name="SimpleListener"
5: description="Hello World"
6: alertTimeThreshold="420"
7: alertLengthThreshold="10"
8: >
9: <listeners>
10: <jms-listener name="JMS-Gateway"
11: busidref="quickstartGwChannel"
12: is-gateway="true"
13: />
14: <jms-listener name="helloWorld"
15: busidref="quickstartEsbChannel"
16: />
17: </listeners>
18: <actions mep="OneWay">
19: <action name="action1"
20: class="org.jboss.soa.esb.samples.quickstart.messagealerts.MyJMSListenerAction"
21: process="displayMessage"
22: />
23: <action name="action2" class="org.jboss.soa.esb.actions.SystemPrintln">
24: <property name="printfull" value="false"/>
25: </action>
26: <action name="action3"
27: class="org.jboss.soa.esb.samples.quickstart.messagealerts.DelayAction"
28: process="delayMessage"
29: alertTimeThreshold="30"
30: alertLengthThreshold="50">
31: </action>
32: </actions>
33: </service>
34: </services>


The changes related to MessageAlerts are:
  • Lines 6-7: These set the service thresholds at 420 milliseconds in time and 10 bytes in length.
  • Lines 29-30: And, these set the action thresholds at 30 milliseconds in time and 50 bytes in length.
When we deploy and run the quickstart, the following is written to the server.log:

 22:00:28,833 INFO [STDOUT] &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
22:00:28,837 INFO [STDOUT] Body: Message Alerts
22:00:28,837 INFO [STDOUT] &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
22:00:28,850 INFO [STDOUT] Message structure:
22:00:28,850 INFO [STDOUT] [Message Alerts].
22:00:31,852 WARN [ServiceMessageCounter] jboss.esb:category=MessageCounter,deployment=Quickstart_messagealerts.esb,service-category=FirstServiceESB,service-name=SimpleListener service, action3 action alert time 3001 took longer than 30 ms
22:00:31,853 WARN [ServiceMessageCounter] jboss.esb:category=MessageCounter,deployment=Quickstart_messagealerts.esb,service-category=FirstServiceESB,service-name=SimpleListener service, action3 action message size 2960 was larger than 50 bytes
22:00:31,853 WARN [ServiceMessageCounter] jboss.esb:category=MessageCounter,deployment=Quickstart_messagealerts.esb,service-category=FirstServiceESB,service-name=SimpleListener service alert time 3007 took longer than 420 ms
22:00:31,854 WARN [ServiceMessageCounter] jboss.esb:category=MessageCounter,deployment=Quickstart_messagealerts.esb,service-category=FirstServiceESB,service-name=SimpleListener service message size 2960 was larger than 10 bytes


And, the same information is available in the JMX log under jboss.esb, service=MessageAlerts:


As you can see in this screenshot, you can reset the counters through the JMX console. (And, yes, as you can also see in this screenshot, I'm staying up way too late at night writing about this stuff.)

You can also access the same information from the jboss.esb mbean directly:
sh ./twiddle.sh get "jboss.esb:service=MessageAlerts"

And, for extra credit, you can access the MessageAlerts service programatically. To do this, first, we'll modify the quickstart's jboss-esb.xml file to move the action that prints information to the server log to happen after the delay (and therefore after the MessageAlerts have been generated).

Then, we'll replace the code in MyJMSListenerAction.java with this:

1:   package org.jboss.soa.esb.samples.quickstart.messagealerts;  
2:
3: import org.jboss.soa.esb.actions.AbstractActionLifecycle;
4: import org.jboss.soa.esb.helpers.ConfigTree;
5: import org.jboss.soa.esb.message.Message;
6:
7: import javax.management.ObjectName;
8: import javax.management.MBeanServer;
9: import javax.management.MalformedObjectNameException;
10:
11: import org.jboss.mx.util.MBeanProxyExt;
12: import org.jboss.mx.util.MBeanServerLocator;
13: import org.jboss.soa.esb.actions.AbstractActionPipelineProcessor;
14: import org.jboss.soa.esb.actions.ActionProcessingException;
15:
16: import java.util.Vector;
17:
18: public class MyJMSListenerAction extends AbstractActionLifecycle
19: {
20:
21: protected ConfigTree _config;
22:
23: public MyJMSListenerAction(ConfigTree config) { _config = config; }
24:
25: public Message displayMessage(Message message) throws Exception{
26:
27: MBeanServer server = org.jboss.mx.util.MBeanServerLocator.locateJBoss();
28: Vector theAlertsVector = (Vector)server.getAttribute(new ObjectName("jboss.esb:service=MessageAlerts"), "Alerts");
29:
30: for (int i = 0; i < theAlertsVector.size(); i++ ) {
31: System.out.println("******Data returned from MessageAlert[" + i + "] = " + theAlertsVector.elementAt(i) + "\n" );
32: }
33:
34: System.out.println("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&");
35: System.out.println("Body: " + message.getBody().get()) ;
36: System.out.println("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&");
37: return message;
38: }
39:
40: }


  • Line 27: Locates the MBeanServer - this is easy as we are running in the same JVM as the server
  • Line 28: Retrieves the Vector of MessageAlerts from the jboss.esb MessageAlerts service
  • Lines 30-33: and prints them out
Closing Thoughts

The ability to building your own services and custom actions with JBossESB is one characteristic of its overall flexibility. With MessageAlerts, you can more easily track the efficiency of those services and actions, at a fine grained level. The message alert feature makes it simple.

Acknowledgements

Thanks to Tom Cunningham for his input to this post and his patient responses to my many questions!