From the IBM WebSphere Developer Technical Journal.
If you read the previous articles in this series, on the basics of message mediation (Part 1), routing messages with mediations (Part 2), and modifying messages with mediations (Part 3), then you should be comfortable with creating and using message mediations, a new programmatic extension to the messaging capabilities of the IBM WebSphere Application Server V6 that can simplify connecting systems, services, applications or components that use messaging. These, along with other recent articles by IBM colleagues, have primarily focused on manipulating the contents of the message using only Java.
Many JMS text and byte messages actually contain XML data. One of the more natural and common mechanisms for manipulating and transforming XML data is to use an Extensible Stylesheet Language Transformation (XSLT). In this article, you will see how to manipulate the contents of a message using XSLT.
For the purposes of this article, the example we will look at is an order tracking system. Our example system sends orders (as JMS text messages containing XML) to a fulfilment warehouse. When messages arrive at the warehouse, they are processed by creating a pick list and a shipping instruction that gets the order shipped to the customer. In our scenario, we have now been asked to introduce an order tracking feature. We will show how we can provide this feature by introducing a mediation that sends a simplified copy of the order message to the tracking system -- without changing the existing system. We will use a XSL stylesheet to simplify the copy of the message.
Let's begin by reviewing what we mean by a mediation and a message format in the context of WebSphere Application Server.
A mediation handler is a Java class that is associated with a messaging destination, and its mediate method is invoked whenever a message is sent to that destination. A mediation handler can modify, reroute, or even delete the message.
Several sorts of messages can be given to a mediation handler. The mediation handler can determine what sort of message it has been given by obtaining the message format, using the getFormat method. Possible values of the format include:
| Format | Description |
|---|---|
| JMS:bytes | A JMS message containing a byte array. |
| JMS:text | A JMS message containing a string. |
| JMS:map | A JMS message containing a map. |
| JMS:object | A JMS message containing an object. |
| JMS:stream | A JMS message containing an object. |
| JMS: | A JMS message with no content (created by constructing a javax.jms.Message object rather than any specific JMS message type). |
| A string starting with SOAP: | A SOAP message to or from a Web service. |
| A string starting with Bean: | A message to or from a Web service. |
XSLT is a mechanism for defining a way to transform an XML document. The transform to be applied to the XML document is specified in a second XML document that describes the rules to be applied. The result of the transform is usually (but not always) yet another XML document.
For example, let's consider our order tracking system. The existing system uses a JMS text message to send orders to a fulfilment warehouse where the message is processed to produce a pick list and shipping instructions. The contents of the text message is an XML document, one example of which is shown in Listing 1.
Listing 1. Sample XML document representing an order
<?xml version="1.0"?>
<order number="197392">
<customer>
<name>David Vines</name>
<address>IBM Hursley</address>
</customer>
<item stocknumber="234432" description="Roborally"/>
<item stocknumber="375647"description="Kill Doctor Lucky"/>
</order> |
Now we want to introduce an order tracking system. This new system will be sent messages (amongst other messages) indicating that an order was sent to the fulfilment warehouse. The order tracking system needs a much simpler JMS text message that reads:
Listing 2. Example XML document representing an order tracking record
<?xml version="1.0"?>
<neworder number="197392"/> |
The message in Listing 2 can be generated by transforming the message in Listing 1 with the following XSLT stylesheet:
Listing 3. Example XSLT to convert an order to an order tracking record
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="order">
<neworder number="{@number}"/>
</xsl:template>
</xsl:stylesheet> |
XSLT is supported as a standard by Java (since JDK 1.4.0). This support is provided by the javax.xml.transform package. This support can be used to perform the XSLT transform as shown in Listing 4.
Listing 4. Example Java class to apply an XSLT
public class XSLTExample
{
private static final String stylesheet = "<?xml version=\"1.0\"?>\r\n" +
"<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\r\n" +
" <xsl:template match=\"order\">\r\n" +
" <neworder number=\"{@number}\"/>\r\n" +
" </xsl:template>\r\n" +
"</xsl:stylesheet>\r\n";
private static final String inputString = "<?xml
version=\"1.0\"?>\r\n" +
"<order number=\"197392\">\r\n" +
" <customer>\r\n" +
" <name>David Vines</name>\r\n" +
" <address>IBM Hursley</address>\r\n" +
" </customer>\r\n" +
" <item stocknumber=\"234432\" description=\"Roborally\"/>\r\n" +
" <item stocknumber=\"375647\" description=\"Doctor Lucky\"/>\r\n" +
"</order>\r\n";
public static final void main(String[] args)
{
TransformerFactory factory = TransformerFactory.newInstance(); [1]
try
{
StringReader stylereader = new StringReader(stylesheet);
StreamSource stylesource = new StreamSource(stylereader);
Templates translet = factory.newTemplates(stylesource); [2]
Transformer transformer = translet.newTransformer(); [3]
Source source = new StreamSource(new StringReader(inputString));
Writer writer = new StringWriter();
Result result = new StreamResult(writer);
transformer.transform(source, result); [4]
System.out.println(writer.toString());
}
catch(Exception e)
{
e.printStackTrace();
}
}
} |
If this example seems a little long, know that most of the program is actually the logic to setup our example. The important points to note in this program (indicated by the bold numbers in Listing 4) are:
- The TransformerFactory is used to create the templates object.
- The templates object defines the transform to be used, hence the stylesheet needs to be provided when defining the templates object. Since the stylesheet is an XML document, it can be provided in several different forms; here, it is provided as a string object, so we need to convert the string into a source object.
- Now that we have created the templates object, we need to obtain the transformer object that actually performs the transformation of the XML document. It is important to note that the transformer object is only thread-safe as long as only one thread is executing the transform method at a time.
- The actual transformation is performed here.
The objects used to perform the transformations can occupy significant memory, but otherwise constructing these objects can take a long time. Thus, we have a classic time vs. memory design decision to make. The deciding factor is the frequency with which the transformation needs to be performed. If messages regularly arrive and require the same stylesheet applied to them, we will need to cache the various objects. For the purposes of this article we will make the assumption that such caching is required.
Another design decision that needs to be made (and one required as a result of our deciding to cache the transformer objects) is how to handle the threading requirements of the transformer object. Again, we have two choices.
- We can require that when the mediation is defined to the application server that the Concurrent mediation checkbox is deseleted, indicating that only one message can be mediated by the mediation handler at a time.
- The alternative is to ensure that each thread uses its own transformer object, which can be achieved by using a ThreadLocal object to create and save a transformer object for each thread of execution.
The mediation handler is made up of four classes:
- ThreadLocalTransformer class
- XSLTTransform class
- MediationHandlerBase class
- XSLTMediationHandler class.
The first of these classes is the ThreadLocalTransformer class, which provides each thread with its own transformer object to avoid any potential threading conflicts. This class is based on the standard Java ThreadLocal class, as shown in Listing 5.
Listing 5. ThreadLocalTransformer class
/**
* A ThreadLocalTransformer holds a transformer object for each thread.
*
*/
public class ThreadLocalTransformer extends ThreadLocal
{
/** The translet to be used to obtain transformers */
private Templates _translet;
/**
* Construct a new ThreadLocalTransformer.
*
* @param translet The translet from which to build the transformers
*/
public ThreadLocalTransformer(Templates translet)
{
super();
_translet = translet;
}
/**
* Return the transformer to be used for this thread.
*
* @return Object The transformer to be used for this thread
*/
protected Object initialValue()
{
try
{
return _translet.newTransformer();
}
catch (TransformerConfigurationException e)
{
// Hmm, we have to return something, so null will have to do :-(
return null;
}
}
} |
The next class actually performs the XSL transformation. It is given a byte array, which is the document to be transformed. The XSL transformation class is also given the stylesheet to be applied and returns another byte array, which is the transformed document:
Listing 6. XSLTTransform class
/**
* The XSLTTransform class contains a utility method to perform
* an XSLT transform. It caches the Transformer objects to
* improve performance when the same transform is repeatedly used.
*/
public class XSLTTransform
{
/** The factory used by this class to get transformers constructed */
private static TransformerFactory _transformerFactory
= TransformerFactory.newInstance();
/** A map from String (stylesheet) to ThreadLocalTransformer */
private static Map _stylesheetToThreadLocalTransformer = new HashMap();
/**
* Transform a payload using the specified transform
*
* @param payload The payload to be transformed
* @param transform The transform to be used
* @return byte[] the transformed payload
* @throws TransformerConfigurationException is thrown if
* the stylesheet is not a valid stylesheet
* @throws TransformerException if the transform fails for any reason
*/
public static byte[] transform(byte[] payload
,String transform
) throws TransformerException
{
// Get the XSLT transformer object
// (one that is for use by this thread)
Transformer transformer = transformerFor(transform);
// Build an source for the transform
ByteArrayInputStream inputStream = new ByteArrayInputStream(payload);
InputStreamReader reader = new InputStreamReader(inputStream);
StreamSource source = new StreamSource(reader);
// Build a result for the transform
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(outputStream);
StreamResult result = new StreamResult(writer);
// Do the transform
transformer.transform(source,result);
// And the result is the byte[] from the output stream
return outputStream.toByteArray();
} |
The transform method takes an XML document to be converted and applies the XSLT (obtained via the next method) to that document. The result of the transform is then returned. It may be a surprise that we use a byte array rather than a string to hold the XML document. This is done because the encoding of the XML document need not be a Unicode encoding (for example, if the original document came from a zSeries® system, it is likely that the XML document will use EBCDIC encoding).
Listing 7. Transform method
/**
* Obtain a transformer for the specified transform
* and for use by the current thread
*
* @param stylesheet The XSL stylesheet for which
* a transformer is required
* @return Transformer The transformer object for the
* specified style-sheet and for the current
* thread of execution
* @throws TransformerConfigurationException is thrown
* if the stylesheet is not a valid stylesheet
*/
private static Transformer transformerFor(String stylesheet)
throws TransformerConfigurationException
{
ThreadLocalTransformer threadLocalTransformer;
if (!_stylesheetToThreadLocalTransformer.containsKey(stylesheet))
{
threadLocalTransformer = (ThreadLocalTransformer)createThreadLocalTransformerFor(stylesheet);
_stylesheetToThreadLocalTransformer.put(stylesheet,threadLocalTransformer);
}
else
{
threadLocalTransformer = (ThreadLocalTransformer)_stylesheetToThreadLocalTransformer.get(stylesheet);
}
return (Transformer) threadLocalTransformer.get();
}
|
The transformerForclass method takes a stylesheet and returns a Transformer object dedicated for use by the current thread of execution. The method supports multiple stylesheets by using a map that uses the stylesheet as a key and the ThreadLocalTransformer objects as the values associated with the key.
Listing 8. transformerForclass method
/**
* Create a thread local transformer for the
* specified stylesheet
*
* @param stylesheet The stylesheet
* @return ThreadLocalTransformer The thread local transformer
* for the specified stylesheet
* @throws TransformerConfigurationException is thrown if the
* stylesheet is not a valid stylesheet
*/
private static Object createThreadLocalTransformerFor(String stylesheet)
throws TransformerConfigurationException
{
StringReader reader = new StringReader(stylesheet);
StreamSource source = new StreamSource(reader);
Templates translet = _transformerFactory.newTemplates(source);
return new ThreadLocalTransformer(translet);
}
} |
The createThreadLocalTransformerFor method creates a new ThreadLocalTransformer object for a specific stylesheet.
The third class is a standard base class. Used for most mediation handlers, it provides methods to obtain the values of context properties used to configure the actual mediation handlers:
Listing 9. MediationHandlerBase class
/**
* This is the base class for all mediation handlers
*
*/
public abstract class MediationHandlerBase implements MediationHandler
{
private static final String TRACE_NAME = "trace";
/** The current message context being processed by this instance of the mediation handler */
private MessageContext _context;
/** Cached answer to 'isTraceEnabled'. Set to null if no one has asked for the current trace setting */
private Boolean _traceEnabled = null;
/**
* This method wraps the real handle method, so that we can dump the stack trace
* of exceptions to System.err
*
* @see com.ibm.websphere.sib.mediation.handler.MediationHandler#handle(javax.xml.rpc.handler.MessageContext)
* @param context The message to be transformed
* @return true if the message can continue being processed
* @throws com.ibm.websphere.sib.mediation.handler.MessageContextException if the message cannot be handled
* @throws IllegalArgumentException if the mediation detects that it is not correctly setup.
*/
public boolean handle(MessageContext context) throws MessageContextException
{
_context = context;
_traceEnabled = null;
try
{
return innerHandle();
}
catch(MessageContextException e)
{
e.printStackTrace();
throw e;
}
catch(RuntimeException e)
{
e.printStackTrace();
throw e;
}
catch(Error e)
{
e.printStackTrace();
throw e;
}
} |
The handle method wraps the abstract innerHandler to capture and report on the exceptions thrown by that method and then rethrows the exception.
Listing 10. handle method
/**
* @return the message context passed to this mediation handler
*/
protected SIMessageContext getContext()
{
return (SIMessageContext)_context;
}
/**
* see com.ibm.websphere.sib.mediation.handler.MediationHandler#handle(javax.xml.rpc.handler.MessageContext)
* @return true if the message can continue being processed
* @throws com.ibm.websphere.sib.mediation.handler.MessageContextException if the message cannot be handled
* @throws IllegalArgumentException if the mediation detects that it is not correctly setup.
*/
public abstract boolean innerHandle() throws MessageContextException;
/**
* Return the value of a context property whose value should be a string.
*
* @param propertyName The name of the context property whose value should be returned
* @return String the value of the property
* @throws IllegalArgumentException is thrown if the context property does not exist or is not a string
*/
protected String getStringProperty(String propertyName) throws IllegalArgumentException
{
Object property = getContext().getProperty(propertyName);
if (property instanceof String)
{
return (String)property;
}
else
{
if (property == null)
throw new IllegalArgumentException("Missing context property: "+propertyName);
else
throw new IllegalArgumentException("Context property '"+propertyName+"' not of the expected type.
Expecting String, got "+property.getClass().getName());
}
}
|
The getStringProperty method is used by the subclass to obtain the values of string properties while validating that the property exists and that it does have a string value. This enables us to reduce this base class in other mediation handlers.
Listing 11. getStringProperty method
/**
* @return true if trace is enabled
*/
protected boolean isTraceEnabled()
{
if (_traceEnabled == null)
{
Object traceProperty = getContext().getProperty(TRACE_NAME);
if (traceProperty instanceof Boolean)
_traceEnabled = (Boolean)traceProperty;
else
_traceEnabled = Boolean.FALSE;
}
return _traceEnabled.booleanValue();
}
}
|
The isTraceEnabled method enables the subclasses to determine if they should provide debug information in a standard way. To enable the trace, the trace context property needs to be set to the boolean value of true.
Finally, we have the actual mediation handler itself:
Listing 12. XSLTMediationHandler class
/**
* The XSLTMediationHandler takes a message and converts it into a JMS
* Bytes Message. In addition it passes the message through an XSLT
* Transformation.
*
*/
public class XSLTMediationHandler extends MediationHandlerBase
{
private static final String STYLESHEET_PROPERTY_NAME = "Stylesheet";
private static final String TRACK_DEST_PROPERTY_NAME = "TrackingDestination";
/**
* This method is given the message context to be mediated and sends a new
* message (based on the original message via an XSLT Stylesheet) to the
* logging destination.
*
* @see MediationHandlerBase#innerHandle
* @return true if the message has been converted
* @throws MessageContextException is thrown if the message cannot be converted
* (note that the original message will be rerouted to the exception destination if this
* exception is thrown.)
*/
public boolean innerHandle() throws MessageContextException
{
// Retrieve the message from the context
SIMessage message = getContext().getSIMessage();
// Get the message contents in the form of a JMS Bytes message
try
{
// Get a new datagraph for the new message we're about to build - Point 1
DataGraph graph = message.getNewDataGraph(SIApiConstants.JMS_FORMAT_BYTES);
DataObject body = graph.getRootObject();
if (body.isSet("data"))
{
// Grab the bytes
byte[] payload = body.getBytes("data/value");
// Transform the bytes
payload = XSLTTransform.transform(payload,getStringProperty(STYLESHEET_PROPERTY_NAME));
// Replace the payload in the data graph
body.setBytes("data/value",payload);
// Build a new message to send (based on the original message - Point 2
SIMessage newMsg = SIMessageFactory.getInstance().createSIMessage(graph, SIApiConstants.JMS_FORMAT_BYTES);
newMsg.setApiMessageId(message.getApiMessageId());
newMsg.setCorrelationId(message.getCorrelationId());
newMsg.setDiscriminator(message.getDiscriminator());
newMsg.setPriority(message.getPriority());
newMsg.setReliability(message.getReliability());
newMsg.setRemainingTimeToLive(message.getRemainingTimeToLive());
newMsg.setTimeToLive(message.getTimeToLive());
newMsg.setUserId(message.getUserId());
for(Iterator it = newMsg.getUserPropertyNames().iterator(); it.hasNext(); )
{
String propertyName = (String)it.next();
newMsg.setUserProperty(propertyName, newMsg.getUserProperty(propertyName));
}
// Send the new message to the logging destination - Point 3
String trackDest = getStringProperty(TRACK_DEST_PROPERTY_NAME);
SIDestinationAddress target = SIDestinationAddressFactory
.getInstance()
.createSIDestinationAddress(trackDest, false);
List forwardRoutingPath = new ArrayList();
forwardRoutingPath.add(target);
newMsg.setForwardRoutingPath(forwardRoutingPath);
getContext().getSession().send(newMsg, true);
}
else
{
// No bytes sent, so throw an IllegalArgument so that the message
// will be sent to the exception destination
IllegalArgumentException e = new IllegalArgumentException("No body to the message");
throw e;
}
}
catch(Exception e)
{
// Hmm, we had a problem somewhere doing the transformation or maybe the send
e.printStackTrace();
throw new MessageContextException(e);
}
// If we get here, everything worked and the tracking message has been sent
return true;
}
} |
This mediation handler uses two context properties, Stylesheet to specify the stylesheet to be used, and TrackingDestination to specify the destination to which the new tracking message should be sent.
There are several interesting aspects about the operation of this mediation handler.
- We always obtain the body of the message that is being mediated as a JMS byte array, which means that we can accept almost any form of message without any danger of the conversion modifying the actual message body; it merely returns a different view of the message.
- We build a completely new message, based on the results of the XSLT transformation. We then copy all the interesting aspects from the original message's header to the header of the new message.
- We set the forward routing path of the new message to contain one destination: the destination to which the tracking messages should be sent. The second parameter on the construction of the SIDestinationAddress indicates that we don't need the message to be sent to the same message engine as where this mediation is executing. The second parameter on the send method indicates that the message should only be sent if the transaction used to mediate the message is committed.
Using XSLT to transform a message that contains an XML document is relatively straightforward. In this article we have demonstrated how to put together the various building blocks to take standard Java code, perform an XSL transformation, and provide that functionality as a mediation handler.
I would like to thank Alasdair Nottingham and Daniel Murphy for their insightful and constructive comments, and for shaming me into writing this article.
| Description | Name | Size | Download method |
|---|---|---|---|
| Code sample | mediatingMessages4.zip | 7 KB | FTP |
Information about download methods
Learn
-
Part 1 introduces the message mediation capabilities of IBM WebSphere Application Server V6.
-
Part 2 shows how to perform message routing using the message mediation capabilities of IBM WebSphere Application Server V6.
-
Part 3 shows how to modify messages using the message mediation capabilities of IBM WebSphere Application Server V6.
-
Introduction to Service Data Objects for more about Service Data Objects.
Get products and technologies
-
IBM Rational
Application Developer -- Download a trial version.
-
WebSphere
Application Developer v6 -- Download a trial version.




