Which got me thinking about how useful it is when a tool works "right of of the box" and how disappointing it can be when it doesn't.
In his book "Diary of a Wimpy Kid, The Last Straw,"[1] author Jeff Kinney has the main character, angst-ridden middle school student Greg Heffley, finds an ad for "personal hovercraft" in a comic book. Greg imagines himself as the coolest kid in school if he could get his own hovercraft, so he sends all his saved-up money away for one. He is, of course, disappointed when he opens the box and finds not a functioning personal hovercraft, but the plan to build one.
Step one in the plan reads: "Acquire an industrial twin-turbine engine."
In contrast, the SOA Platform provides an extensive set of actions[2] that work right out of the box. The SOA Platform also supports the creation of custom actions, but, when you are designing how your application will make use of the Platform, the place to start is the out-of-the-box actions. These have all been tested so you can depend on the results they will achieve, and they've also been documented and illustrated in the quickstart sample programs. (On a personal note, my introduction to the SOA Platform included my spending several hours building an action, when an out of the box action for the same task already existed!)
The out-of-the-box actions implemented in the SOA Platform are divided into the following functional groups:
- Transformers and Converters - converting message data from one form to another
- Business Process Management - integrating with JBoss jBPM
- Scripting - automating tasks in supported scripting languages
- Services - integration with EJBs
- Routing - moving message data to the correct services
- Notifier - sending data to ESB unaware destinations
- Webservices/SOAP - the name says it all - support for Webservices
Let's take a quick look at all of these actions, and then a more in-depth look at a couple of them.
Transformers and Converters
These actions are needed to enable services that expect data in different forms to communicate with each other. What transformers and converters do is to translate message payloads into different forms. Let's stop for a minute and review the contents of messages as they are passed over the JBossESB in the SOA Platform. Messages implement the org.jboss.soa.esb.message.Message interface. From the point of view of a service, the data in a message, in other words, the message "payload" consists of the message body plus attachments and properties. The default way for an action to get or set a message payload is the MessagePayloadProxy utility class. This class is important as it provides a standard way to get the message payload.
The actions in the Transformers and Converters actions group (yes, the names are pretty self-explanatory) are:
- ByteArrayToString (org.jboss.soa.esb.actions.converters.ByteArrayToString) - This action converts a byte[] message payload into a Java String
- LongToDateConverter (org.jboss.soa.esb.actions.converters.LongToDateConverter) - And this action converts a Java long message payload into a java.util.Date
- ObjectInvoke (org.jboss.soa.esb.actions.converters.ObjectInvoke) - Things start to get a little more interesting with the ObjectInvoke converter. This action takes a serialized object, which constitutes the entire Message payload, and passes it to a configured processer class. The processed results become the new message payload. The action lets you use your own POJOs as the message payloads.
- ObjectToCSVString (org.jboss.soa.esb.actions.converters.ObjectToCSVString) - This action converts a message into a comma separated string based on a list of property names that you supply.
- ObjectToXStream (org.jboss.soa.esb.actions.converters.ObjectToXStream) - This action is similar to the ObjectInvoke action, except that it converts the object to XML using XStream[3]. You've probably already guessed that the XStreamToObject action converts XML to an object using XStream.
- SmooksAction (org.jboss.soa.esb.smooks.SmooksAction) - This action enables you to use a powerful set of Smooks[4] operations within the SOA Platform. Transformations are probably the first type of operation that you think of with Smooks, but with the SmooksAction you can also make use of Smooks operations such as splitting and routing message payloads[5]. We'll take a closer look at this action later in the post. (Note that the SmooksTransformer action is being deprecated in favor of the SmooksAction action.)
- PersistAction (org.jboss.soa.esb.actions.MessagePersister) - This action doesn't really transform or convert a message, it writes it to the persistent message store. The most common use of the action is that it is how the SOA Platform itself writes messages to the dead letter queue (DLQ), but it can be used in any situation when you want to store a message.
This action group contains only one out of the box action (org.jboss.soa.esb.services.jbpm.actions.BpmProcessor), but that one action supports the JBossESB - jBPM integration in the SOA Platform. This integration enables your services to make calls into a jBPM process through the jBPM Command API. Of the commands in the command API, the following (3) are available for use from ESB:
- NewProcessInstanceCommand creates a new ProcessInstance using a process definition that has already been deployed to jBPM. The process instance is left in the start state so that tasks referenced by start node are executed.
- StartProcessInstanceCommand is the same as NewProcessInstanceCommand, except that the process instance that is created is moved from the start position to the first node in the process graph.
- As its name implies, CancelProcessInstanceCommand cancels a process instance.
Scripting
This group of actions makes it possible for you to create custom actions using scripting languages. As of release 4.3 of the SOA Platform, scripting actions in Groovy and Bean Scripting Framework (BSF) are supported.
- GroovyActionProcessor (org.jboss.soa.esb.actions.scripting.GroovyActionProcessor) - which uses Groovyscripts
- ScriptingAction (org.jboss.soa.esb.actions.scripting.ScriptingAction) - which uses the Bean Scripting Framework and supports BeanShell, Jython and JRuby scripts
Services
There's only one action in this group, but it's an important one.
- The EJBProcessor (org.jboss.soa.esb.actions.EJBProcessor) action accepts a message and uses it to locate and invoke an EJB. (The SOA Platform supports both EJB2 and EJB3 through this action.) In order to find the correct EJB and invoke the correct method, you specify information such as the jndi-name, initial-context-factory, provider-url, method name and and other configuration specific to the EJB as message properties
One of the major tasks that the JBoss ESB in the SOA Platform performs is the routing of messages to services. (Remember that in an ESB, everything is either a message or a service.) In an earlier post, I described how to use JBoss Rules to perform content based routing (CBR), where the content of an incoming message determines its routing. The full set of routing actions are:
- Aggregator (org.jboss.soa.esb.actions.Aggregator) - This is an interesting one. The Aggregator, as its name indicates, aggregates information together. This action enables you to combine information from multiple messages into one message. The Aggregator action is an implementation of the Aggregator Enterprise Integration Pattern[6].
- EchoRouter (org.jboss.soa.esb.actions.routing.EchoRouter) - In contrast, the EchoRuter just logs and returns the incoming message.
- HttpRouter (org.jboss.soa.esb.actions.routing.HttpRouter) - The HttpRouter routes the incoming message to a URL that you specify in the action definition.
- JMSRouter (org.jboss.soa.esb.actions.routing.JMSRouter) - This action routes the incoming message to JMS. For example, to a JMS queue where a service can then retrieve the message asynchronously. In order to find the correct JMS queue or topic, you specify values for properties such as jndi-name, initial-context-factory, and jndi-URL in the action definition.
- ContentBasedRouter (org.jboss.soa.esb.actions.ContentBasedRouter) - I described content based routing with JBoss Rules in an earlier post to this blog http://jboss-soa-p.blogspot.com/2009/07/when-content-knows-way-content-based.html). Content based routing is a flexible approach to message routing in that the content in a message is actually the determining factor in determining the route a message takes.
- StaticRouter (org.jboss.soa.esb.actions.StaticRouter) - As its name implies, this router establishes static routes that do not change based on the content of the messages or a set of routing rules.
- StaticWiretap (org.jboss.soa.esb.actions.StaticWiretap) - Maybe it's the comic book fan in me, but this is my favorite name for an action. There's something film noir-ish about a "wiretap." You can almost imagine Humphrey Bogart sitting it the back room of a bar listening in on a wiretapped SOA action. (On black and white film, of course.) In practice, it's not all that exciting. This action implements the Enterprise Integration Pattern for a wiretap[7]. The goal of a wiretap is to inspect each message, without affecting the operation of the action chain. This can be a useful action to use to aid in debugging a service.
Just as a gateway listener enables you to move ESB-unaware messages (in other words, messages in a format other than (org.jboss.soa.esb.message.Message) onto the SOA Platform's ESB, notifiers (org.jboss.soa.esb.actions.Notifier) enable you to move ESB-aware messages from the ESB to ESB-unaware services. The notifiers convert the ESB-aware messages into data into formats that these services can understand.
One thing to keep in mind is that the action pipeline has two stages, first normal processing and then outcome processing. Notifiers do not perform any processing of messages during that first stage. They send notifications during the second stage. The notification occurs after the processing of the action pipeline. This means that you cannot use notifiers to alter the results of any action processing. The data sent by the notifier is the ESB message that is processed by the action pipeline. The following notification targets (the names are very self-explanatory) are supported by the SOA Platform:
- NotifyConsole - Prints the contents of the ESB message to the server log
- NotifyFiles - Prints the message contents to a file or files
- NotifySQLTable - Inserts the message contents into an existing database table
- NotifyFTP - Prints the message contents to a file and then sends the file to a server via FTP
- NotifyQueues and NotifyTopics - Writes the message contents into a JMS message and adds the JMS message to a specified JMS queue or topic
- NotifyEmail - Sends the message contents in a mail message
When you're considering web services and the SOA Platform, you actually have two separate goals to achieve; you want to be able to expose webservice endpoints as services and you also want to be able to invoke web services from actions defined in a service.
There are (3) out of the box actions that support web services:
- SOAPProcessor (org.jboss.soa.esb.actions.soap.SOAPProcessor) - This action is only available for use on the SOA Platform embedded server. You use this action to expose webservice endpoints. To do this, you write a "service wrapper webservice" that conforms to the JSR 181[8] ("Web Services Metadata for the Java Platform") standard. JSR 181 enables you to create webservices from a POJO by adding annotations such as @WebService and @WebMethod to it. This service wrapper webservice wraps calls to your service and exposes that service through JBossESB listeners. Your service can also be accessed through other transports (FTP, JMS, etc.) supported by the JBossESB in the SOA Platform. These 3 types of JBossWS webservice endpoints can be exposed through JBoss ESB listeners using this action:
- Webservice bundled into a JBossESB .esb archive - When the webservice .war is bundled into a deployed .esb archive
- Webservice bundled into an .ear - When the .war is bundled into a deployed .ear
- Standalone webservice - When the webservice .war is deployed
- Webservice bundled into a JBossESB .esb archive - When the webservice .war is bundled into a deployed .esb archive
- WISE SOAPClient (org.jboss.soa.esb.actions.soap.wise.SOAPClient) - This action uses the WISE[9] client service (org.jboss.wise.core.client.WSService) to create a JAX-WS (Java API for XML Web Services) client class and then call the target service. WISE stands for "Wise Invokes Services Easily" and enables the dynamic generation of a client to access web services, without your having to write the code for the client. (This is referred to as "zero-code web service invocation").
- SOAPClient (org.jboss.soa.esb.actions.soap.SOAPClient) - This action, uses the soapUI Client Service (org.jboss.soa.esb.services.soapui.SoapUIClientService) to create a message and then route that message to the target service.
We've already walked through how to use some of the out of the box actions (Business Process Management, Routing, Notifiers) in earlier posts to this blog. Now, let's look at some examples of other types of out of the box actions. As always, the "quickstart" programs in the SOA Platform provide the example code.
Transformation
We'll start by looking at transformations and the SmooksAction out of the box action. But first, let's take a look at Smooks. It's common to refer to Smooks as a transformation engine, but that's not the full story. Smooks is really a more general purpose processing framework that is capable of dealing with with fragments of a message. Smooks accomplishes this with "visitor logic", where a "visitor" is Java code that performs a specific action on a specific fragment of a message. This enables Smooks to perform different actions on different fragments of messages. As is stated by the Smooks project[10]:
Smooks supports these types of message fragment processing:
- Templating: Transform message fragments with XSLT or FreeMarker
- Java Binding: Bind message fragment data into Java objects
- Splitting: Split messages fragments and rout the split fragments over multiple transports and destinations
- Enrichment: "Enrich" message fragments with data from databases
- Persistence: Persist message fragment data to databases
- Validation: Perform basic or complex validation on message fragment data
Let's look at a very simple example, the aptly named "transform_XML2XML_simple" quickstart. This quickstart performs a message transformation by applying an XSLT (EXtensible Stylesheet Language Transformations) to an XML message. The message is transformed into XML in a different form. The interesting parts of the quickstart's jboss-esb.xml file are:
25 <actions mep="OneWay">
26 <action name="print-before" class="org.jboss.soa.esb.actions.SystemPrintln">
27 <property name="message" value="[transform_XML2XML_simple] Message before transformation" />
28 </action>
29 <action name="simple-transform" class="org.jboss.soa.esb.smooks.SmooksAction">
30 <property name="smooksConfig" value="/smooks-res.xml" />
31 <property name="reportPath" value="/tmp/smooks_report.html" />
32 </action>
33 <action name="print-after" class="org.jboss.soa.esb.actions.SystemPrintln">
34 <property name="message" value="[transform_XML2XML_simple] Message after transformation" />
35 </action>
- Line 25: This line is not specific to transformations, but it's worth mentioning. "mep" stands for "message exchange pattern." The pattern used by this quickstart is "one-way" in that the requester invokes a service (by sending it a message) and then does not wait for a response.
- Lines 26-28, 33-35: These lines simply cause the message to be written to the server log before and after its transformation. (org.jboss.soa.esb.actions.SystemPrintln, is, incidentally, the only out-of-the-box action in the Miscellaneous action group.)
- Line 29: Here's where we specify that we want to invoke a SmooksAction
- Line 30: And here is the XSLT that will be executed. We'll examine this file in a moment.
- Line 31: This line is actually not in the quickstart. I've added the reportPath property it so that we can review the resulting report. Note that generating this report does require some processing resources, so it should not be used in production environments. See below for screen-shots of this report.
Now, let's look at smooks-res.xml:
1 <?xml version='1.0' encoding='UTF-8'?>
2 <smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.0.xsd">
3
4 <resource-config selector="OrderLine">
5 <resource type="xsl">
6 <![CDATA[<line-item>
7 <product><xsl:value-of select="./Product/@productId" /></product>
8 <price><xsl:value-of select="./Product/@price" /></price>
9 <quantity><xsl:value-of select="@quantity" /></quantity>
10 </line-item>]]>
11 </resource>
12 <param name="is-xslt-templatelet">true</param>
13 </resource-config>
14 </smooks-resource-list>
- Line 2: The namespace referenced here is the Smooks XML Schema Definition
- Line 4: The resource-config element corresponds to an org.milyn.cdr.SmooksResourceConfiguration object[11]
- Lines 7-9: These XPath (XML Path Language) [12] statements locate the Product element's productId and price attributes and the OrderLine element's quantity attributes. XPath is used by XSLT to find or reference data in XML documents.
Web Services
Another important feature of the SOA Platform is its ability to publish web services as services. Remember how we talked about how the JBossESB included in the SOA Platform made it possible to deploy web service endpoints as endpoints on the ESB? The services can then also be accessed through other transports (FTP, JMS, etc.) supported by the JBossESB in the SOA Platform.
We'll look first at the webservice_producer quickstart. This quickstart demonstrates how to expose a JSR181 web service endpoint on the JBossESB within the SOA Platform by using the org.jboss.soa.esb.actions.soap.SOAPProcessor action.
Let's begin by looking at these lines in jboss-esb.xml:
37 <actions>
38 <action name="print-before" class="org.jboss.soa.esb.actions.SystemPrintln">
39 <property name="message"
40 value="[Quickstart_webservice_producer] BEFORE invoking jbossws endpoint"/>
41 </action>
42 <action name="JBossWSAdapter" class="org.jboss.soa.esb.actions.soap.SOAPProcessor">
43 <property name="jbossws-endpoint" value="GoodbyeWorldWS"/>
44 </action>
45 <action name="print-after" class="org.jboss.soa.esb.actions.SystemPrintln">
46 <property name="message"
47 value="[Quickstart_webservice_producer] AFTER invoking jbossws endpoint"/>
48 </action>
49 <action name="testStore" class="org.jboss.soa.esb.actions.TestMessageStore"/>
50 </actions>
- Lines 38 and 45: As we discussed earlier, the SystemPrintln action displays the message to the server.log
- Lines 42,43: Here is where we invoke the SOAPProcessor action to generate the JSR181 web service from the specified class, GoodbyeWorldWS
17 @WebService(name = "GoodbyeWorldWS", targetNamespace="http://webservice_producer/goodbyeworld")(Remember how we talked about having to write a "service wrapper webservice" that conforms to the JSR 181 ("Web Services Metadata for the Java Platform") standard? You do this by adding @WebService and @WebMethod annotations.)
21 @WebMethod
22 public String sayGoodbye(@WebParam(name="message") String message) {
23
24 Message esbMessage = SOAPProcessor.getMessage();
25 if(esbMessage != null) {
26 System.out.println("**** SOAPRequest perhaps mediated by ESB:\n" + esbMessage.getBody().get());
28 }
29 System.out.println("Web Service Parameter - message=" + message);
30 return "... Ah Goodbye then!!!! - " + message;
31 }
- Line 17: The definition of the web service.
- Lines 24-31: The definition of the method in the web service to be invoked when the action is triggered.
6 <providers>The last quickstart we'll look at is webservice_consumer1 - as you can guess by its name, this quickstart demonstrates how to access (or "consume") a JSR181 Web Service in an ESB action by using the org.jboss.soa.esb.actions.soap.SOAPClient action.
7 <jms-provider name="JBossMQ" connection-factory="ConnectionFactory">
8 <jms-bus busid="quickstartGwChannel">
9 <jms-message-filter dest-type="QUEUE" dest-name="queue/quickstart_webservice_producer_gw"/>
10 </jms-bus>
11 <jms-bus busid="quickstartEsbChannel">
12 <jms-message-filter dest-type="QUEUE" dest-name="queue/quickstart_webservice_producer_esb"/>
13 </jms-bus>
14 </jms-provider>
15
16 <jbr-provider name="JBR-Http" protocol="http" host="localhost">
17 <jbr-bus busid="Http-1" port="8765" />
18 </jbr-provider>
19
20 <jbr-provider name="JBR-Socket" protocol="socket" host="localhost">
21 <jbr-bus busid="Socket-1" port="8888" />
22 </jbr-provider>
23 </providers>
30 <listeners>
31 <jms-listener name="JMS-Gateway" busidref="quickstartGwChannel" is-gateway="true"/>
32 <jbr-listener name="Http-Gateway" busidref="Http-1" is-gateway="true"/>
33 <jbr-listener name="Socket-Gateway" busidref="Socket-1" is-gateway="true"/>
34 <jms-listener name="JMS-ESBListener" busidref="quickstartEsbChannel"/>
35 </listeners>
This quickstart requires that a web service be deployed for the SOAPClient to access. The webservice is very simple and is deployed in a .war. In HelloWorldWS.java we see the definition of the web service:
28 @WebService(name = "HelloWorld", targetNamespace = "http://webservice_consumer1/helloworld")
29 public class HelloWorldWS
30 {
31 @WebMethod
32 public String sayHello(@WebParam(name = "toWhom")
33 String toWhom)
34 {
35
36 String greeting = "Hello World Greeting for '" + toWhom + "' on " + new java.util.Date();
37
38 return greeting;
39
40 }
41 }
Pay attention to the "toWhome" parameter. We'll look for this in the web service request.
In MyRequestAction.java, we see that toWhome parameter added as the key in a hashmap for the msg body:
44 /*
45 * Convert the message into a webservice request map.
46 */
47 public Message process(Message message) throws Exception
48 {
49 logHeader();
50 String msgBody = (String) message.getBody().get();
51 HashMap requestMap = new HashMap();
52
53 // add parameters to the web service request map
54 requestMap.put("sayHello.toWhom", msgBody);
55
56 // The "paramsLocation" property was set in jboss-esb.xml to
57 // "helloworld-request-parameters"
58 message.getBody().add(requestMap);
59 System.out.println("Request map is: " + requestMap.toString());
60
61 logFooter();
62 return message;
63 }
- Line 54 - The towhome parameter added as the key in a hashmap for the msg body. The key of the HashMap is an Object-Graph Navigation Language (OGNL)[13] expression that identifies the SOAP parameter to be populated with the key's corresponding value. OGNL is an expression language for getting and setting Java objects properties.
44 /*
45 * Retrieve and output the webservice response.
46 */
47 public Message process(Message message) throws Exception
48 {
49
50 logHeader();
51
52 // The "responseLocation" property was set in jboss-esb.xml to
53 // "helloworld-response"
54 Map responseMsg = (Map) message.getBody().get(Body.DEFAULT_LOCATION);
55 System.out.println("Response Map is: " + responseMsg);
56
57 logFooter();
58 return message;
59 }
In jboss-esb.xml:
36 <actions mep="OneWay">
37 <action name="request-mapper"
38 class="org.jboss.soa.esb.samples.quickstart.webservice_consumer1.MyRequestAction">
39 </action>
40 <action name="soapui-client-action"
41 class="org.jboss.soa.esb.actions.soap.SOAPClient">
42 <property name="wsdl"
43 value="http://127.0.0.1:8080/Quickstart_webservice_consumer1/HelloWorldWS?wsdl" />
44 <property name="responseAsOgnlMap" value="true" />
45 <property name="SOAPAction" value="sayHello"/>
46 </action>
47 <action name="response-mapper"
48 class="org.jboss.soa.esb.samples.quickstart.webservice_consumer1.MyResponseAction">
49 </action>
50 <action name="testStore" class="org.jboss.soa.esb.actions.TestMessageStore"/>
51 </actions>
- Line 41: The SOAPClient class makes the call to the webservice. This is the "zero-code web service invocation" that we mentioned earlier in the post.
- Line 43: The reference to the web service's wsdl
- Line 44: The parameter responseAsOgnlMap tells the JBossESB move the SOAP response data into that OGNL-based map and attach it to the message.
Line 45: The reference to the method to be invoked in the web service.
It can be daunting to attempt to create a new service based application from scratch. The SOA Platform's combination of out-of-the-box actions and quickstart can make this task much easier. Even without a jet turbine engine. ;-)
Resources
[1] Kinney, Jeff, Diary of a Wimpy Kid, The Last Straw, Amulet Books; 1st edition (January 2009), ISBN-10: 0810970686, ISBN-13: 978-0810970687
[2] http://www.redhat.com/docs/en-US/JBoss_SOA_Platform/4.3.GA/html/Programmers_Guide/chap-SOA_ESB_Programmers_Guide-Out-of-the-box_Actions.html
[3] http://xstream.codehaus.org/
[4] http://www.smooks.org/mediawiki/index.php?title=Main_Page
[5] http://www.milyn.org/javadoc/v1.0/smooks/org/milyn/container/plugin/PayloadProcessor.html
[6] http://www.enterpriseintegrationpatterns.com/Aggregator.html The patterns described at this site and in the corresponding book were created to assist designers and implementers in creating enterprise software solutions. The patterns are platform and language agnostic, so they can be very helpful across many systems.
[7] http://www.enterpriseintegrationpatterns.com/WireTap.html
[8] http://jcp.org/en/jsr/summary?id=181
[9] http://jboss.org/wise
[10] http://www.smooks.org/mediawiki/index.php?title=Why_Smooks_was_Created
[11] http://www.milyn.org/javadoc/v1.0/smooks/org/milyn/cdr/SmooksResourceConfiguration.html
[12] http://www.w3.org/TR/xpath
[13] http://www.opensymphony.com/ognl/
Acknowledgments
As always, I'd like to thank the members of the SOA Platform project for their help and timely review comments! Also, this post relies heavily on the extensive SOA Platform documents and the quickstarts.
2 comments:
Really great article!
Nice article, and love the first book in your reference list! .. but what does it have to do with JBoss?
Post a Comment