消息中介的实用介绍——第 4 部分:使用中介和 XSLT 修改消息
2009-10-21 00:00:00 来源:WEB开发网引言
如果您读过本系列的前三篇文章,消息中介基础(第 1 部分)、使用中介路由消息(第 2 部分)和使用中介修改消息(第 3 部分),那么创建和使用消息中介对您来说已不再陌生,消息中介是 IBM WebSphere Application Server V6 的消息传递功能的新的可编程扩展,可以用于简化使用消息传递的连接系统、服务、应用程序或组件。这三篇文章,连同 IBM 同事最近发表的其他文章,主要介绍了只使用 Java 处理消息的内容。
许多 JMS 文本和字节消息实际上包含 XML 数据。用于处理和转换 XML 数据的一种更成熟且更常用的机制是使用可扩展样式表语言转换(Extensible Stylesheet Language Transformation,XSLT)。在本文中,您将了解如何使用 XSLT 处理消息的内容。
场景
在本文中,我们将看到的示例是一个订单跟踪系统。我们的示例系统将订单(如包含 XML 的 JMS 文本消息)发送到实现仓库。当消息到达仓库时,通过创建选择清单和将订单传送到客户的传送指令来处理消息。在我们的场景中,现在要求我们引入订单跟踪功能。我们将展示在没有更改现有系统的情况下,如何通过引入将订单消息的简化副本发送到跟踪系统的中介来提供该功能。我们将使用 XSL 样式表简化消息的副本。
中介回顾
我们首先回顾我们讲的 WebSphere Application Server 上下文中的中介和消息格式。
中介
中介处理程序是与消息传递目的地相关联的 Java 类,只要消息发送到该目的地,就将调用它的中介方法。中介处理程序可以修改、重新路由消息,甚至删除消息。
消息格式
可以向中介处理程序提供多种消息。通过使用 getFormat 方法获得消息格式,中介处理程序可以确定向其提供的消息的类别。格式的可能值包括:
格式 | 描述 |
JMS:bytes | 包含字节数组的 JMS 消息。 |
JMS:text | 包含字符串的 JMS 消息。 |
JMS:map | 包含映射的 JMS 消息。 |
JMS:object | 包含对象的 JMS 消息。 |
JMS:stream | 包含对象的 JMS 消息。 |
JMS: | 没有内容的 JMS 消息(通过构造 javax.jms.Message 对象而非任何特定的 JMS 消息类型创建的)。 |
以 SOAP: 开头的字符串 | 发送到 Web 服务或者从 Web 服务发出的 SOAP 消息。 |
以 Bean: 开头的字符串 | 发送到 Web 服务或者从 Web 服务发出的消息。 |
XSLT 和 Java
XSLT
XSLT 是用于定义转换 XML 文档的方法的一种机制。应用于 XML 文档的转换是在描述应用规则的第二个 XML 文档中指定的。转换的结果通常仍是(并不总是)另一个 XML 文档。
例如,让我们来看一下我们的订单跟踪系统。现有系统使用 JMS 文本消息向实现仓库发送订单,该消息在仓库中进行处理,以生成选择清单和传送指令。文本消息的内容是 XML 文档,清单 1 展示了 XML 文档的一个示例。
清单 1. 表示订单的 XML 文档示例<?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>
现在我们需要引入一个订单跟踪系统。向这一新系统发送的消息(除了其他许多消息之外)表示订单已发送到实现仓库。订单跟踪系统需要更简单的 JMS 文本消息,如下所示:
清单 2. 表示订单跟踪记录的 XML 文档示例<?xml version="1.0"?>
<neworder number="197392"/>
通过使用下面的 XSLT 样式表转换清单 1 中的消息,可以生成清单 2 中的消息:
清单 3. 将订单转换成订单跟踪记录的 XSLT 示例<?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>
Java
Java(从 JDK 1.4.0 开始)支持 XSLT 作为一个标准。该支持是由 javax.xml.transform 包提供的。该支持可以用于执行 XSLT 转换,如清单 4 所示。
清单 4. 应用 XSLT 的 Java 类示例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();
}
}
}
如果该示例看起来有点长,请记住,大部分程序实际上是设置我们的示例的逻辑。该程序中需要注意的几个要点(在清单 4 中用粗体数字表示)有:
TransformerFactory 用于创建模板对象。
模板对象定义要使用的转换,因此在定义模板对象时,需要提供样式表。由于样式表是一个 XML 文档,因此可以采用多种不同的形式提供样式表;此处,它是作为字符串对象提供的,因此我们需要将字符串转换成源对象。
既然我们创建了模板对象,接下来,我们就需要获得实际执行 XML 文档转换的转换器对象。需要注意的重要一点是,仅当在某一时刻只有一个线程执行转换方法时,转换器对象才是线程安全的。
在此处执行实际转换。
设计决策
缓存
用于执行转换的对象可能占用大量内存,此外,构造这些对象可能要花费很长时间。因而,我们要作出一流的时间和内存设计决策。其决定因素是执行转换所需的频率。如果消息定期到达,并且需要应用同一样式表,那么我们需要缓存各种对象。在本文中,我们假设需要这种缓存。
线程
我们需要作出的另一个设计决策(由于我们决定缓存转换器对象,因此需要另一个设计决策)是如何处理转换器对象的线程需求。此外,我们有两种选择。
我们可以要求,在应用程序服务器定义中介时取消选择 Concurrent mediation 复选框,这表示在某一时刻中介处理程序只能传递一条消息。
另一种方法是确保每一个线程都使用自己的转换器对象,这可以通过使用 ThreadLocal 对象为执行的每一个线程创建并保存转换器对象来实现。
中介处理程序
中介处理程序由四个类构成:
ThreadLocalTransformer 类
XSLTTransform 类
MediationHandlerBase 类
XSLTMediationHandler 类。
这些类中的第一个类是 ThreadLocalTransformer 类,它向每一个线程提供自己的转换器对象,以避免任何潜在的线程冲突。该类基于标准的 Java ThreadLocal 类,如清单 5 所示。
清单 5. ThreadLocalTransformer 类/**
* 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;
}
}
}
下一个类实际执行 XSL 转换。向它提供一个字节数组,该字节数组是要转换的文档。XSL 转换类还被提供要应用的样式表,然后返回另一个字节数组作为转换的文档:
清单 6. XSLTTransform 类/**
* 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();
}
转换方法获得要转换的 XML 文档,并将 XSLT(通过下一个方法获得)应用于该文档。然后返回转换结果。我们使用字节数组(而非字符串)存放 XML 文档,这可能使人感到惊讶。这样做的原因在于 XML 文档的编码不需要是 Unicode 编码(例如,如果初始文档来自 zSeries® 系统,则 XML 文档很可能使用 EBCDIC 编码)。
清单 7. 转换方法 /**
* 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();
}
transformerForclass 方法获得样式表,并返回专用的 Transformer 对象(通过执行当前线程)。该方法通过映射来支持多个样式表,在该映射中,将样式表作为键,并且将 ThreadLocalTransformer 对象作为与键相关联的值。
清单 8. transformerForclass 方法 /**
* 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);
}
}
createThreadLocalTransformerFor 方法为特定的样式表创建新的 ThreadLocalTransformer 对象。
第三个类是一个标准的基类。此类用于大多数中介处理程序,它提供了一些方法,通过这些方法,可以获得用于配置实际的中介处理程序的上下文属性的值:
清单 9. MediationHandlerBase 类/**
* 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;
}
}
句柄方法包装抽象的 innerHandler,以捕获并报告该方法抛出的异常,然后重新抛出该异常。
清单 10. 句柄方法 /**
* @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());
}
}
当验证属性是否存在以及它是否确实具有字符串值时,子类使用 getStringProperty 方法获得字符串属性的值。这使我们能够在其他中介处理程序中简化该基类。
清单 11. getStringProperty 方法 /**
* @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();
}
}
通过 isTraceEnabled 方法,子类可以确定是否应该以某种标准的方式提供调试信息。为了启用跟踪功能,需要将跟踪上下文属性设置为布尔值 true。
最后,我们得到了实际的中介处理程序:
清单 12. XSLTMediationHandler 类/**
* 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;
}
}
该中介处理程序使用两个上下文属性,Stylesheet 用于指定要使用的样式表,而 TrackingDestination 用于指定新跟踪消息应该发送到的目的地。
操作该中介处理程序有几个重要方面需要注意。
我们始终获得以 JMS 字节数组作为要传递的消息的主体,这意味着我们几乎可以接受任何形式的消息,而不存在任何修改实际消息体的转换的危险;它只是返回一个不同的消息视图而已。
我们根据 XSLT 转换的结果构建一个全新的消息。然后,我们将所有重要方面从初始消息 Header 复制到新消息 Header。
我们将新消息的转发路由路径设置为包含一个目的地:它是跟踪消息应该发送到的目的地。构建 SIDestinationAddress 的第二个参数表示我们不需要将消息发送到同一消息引擎,因为该中介正在此处执行。发送方法的第二个参数表示,仅在提交用于传递消息的事务时才应该发送消息。
结束语
使用 XSLT 转换包含 XML 文档的消息是比较简单的。在本文中,我们演示了如何组合各种构造块来获得标准的 Java 代码,如何执行 XSL 转换,以及如何提供作为中介处理程序的功能。
本文示例源代码或素材下载
更多精彩
赞助商链接