If you're like me and prefer working with the raw XML in your web service implementations instead of dealing with the mess of generated data binding code, this article will show you how to do so using JAX-WS and XPath. In this example, we use this approach to implement a WS-Notification consumer service that receives WSDM-based messages for monitoring the health and performance of services.
JAX-WS has the Provider interface that you can implement to get access to the raw XML message. You have the choice of implementing Provider<Source>, Provider<DataSource>, or Provider<SOAPMessage>. We'll be using the Provider<Source> in this example. The invoke(Source) method is what gets called to process a service request. We'll break down the implementation of that method step-by-step.
First we send the source through a Transformer to get a DOM tree that we can use for XPath processing:
Next, we instantiate an XPathFactory and create an XPath that we'll use to process the DOM tree against. The third line calls the setNamespaceContext() method and uses a custom class I created that provides a mapping of namespace URI's to prefixes and vice-versa. You can find more information on how to implement such a class here. We need this because we'll be using namespace prefixes in our expressions.
Now we'll process our first expression to extract the NotificationMessage elements from the message, passing in the XPath expression for the NotificationMessage and our DOM tree to evaluate the expression against. A WS-Notification message may contain multiple NotificationMessages so we get a NodeList in return.
Then we'll iterate through each item in the NodeList, i.e. each NotificationMessage, and process another expression against it to extract the ResourceId element and print it out.
JAX-WS has the Provider interface that you can implement to get access to the raw XML message. You have the choice of implementing Provider<Source>, Provider<DataSource>, or Provider<SOAPMessage>. We'll be using the Provider<Source> in this example. The invoke(Source) method is what gets called to process a service request. We'll break down the implementation of that method step-by-step.
First we send the source through a Transformer to get a DOM tree that we can use for XPath processing:
DOMResult dom = new DOMResult();
Transformer trans = TransformerFactory.newInstance().newTransformer();
trans.transform(source, dom);
Next, we instantiate an XPathFactory and create an XPath that we'll use to process the DOM tree against. The third line calls the setNamespaceContext() method and uses a custom class I created that provides a mapping of namespace URI's to prefixes and vice-versa. You can find more information on how to implement such a class here. We need this because we'll be using namespace prefixes in our expressions.
XPathFactory xpf = XPathFactory.newInstance();
XPath xp = xpf.newXPath();
xp.setNamespaceContext(MyNamespaceContext.getInstance());
Now we'll process our first expression to extract the NotificationMessage elements from the message, passing in the XPath expression for the NotificationMessage and our DOM tree to evaluate the expression against. A WS-Notification message may contain multiple NotificationMessages so we get a NodeList in return.
NodeList resultList = (NodeList)xp.evaluate("/wsnt:Notify/wsnt:NotificationMessage", dom.getNode(), XPathConstants.NODESET);Then we'll iterate through each item in the NodeList, i.e. each NotificationMessage, and process another expression against it to extract the ResourceId element and print it out.
String exprPrefix = "";
int len = resultList.getLength();
for (int i=1;i<=len;i++) {
String expr1 = "/wsnt:Notify/wsnt:NotificationMessage[" + i
+ "]" + "/wsnt:Message/muws1:ManagementEvent"
+ "/muws1:SourceComponent/muws1:ResourceId";
String resourceId = xp.evaluate(expr1, dom.getNode());
System.out.println("Resource ID: " + resourceId);
} The entire source code listing can be found here. You'll notice that the class has the following two annotations:
@WebServiceProvider()@ServiceMode(value=Service.Mode.PAYLOAD)
The first one tells JAX-WS that this class is a Provider endpoint. The second one says that we only want to work with the message at the payload level, i.e. we don't need to access the message headers. In addition to these annotations, we have to customize the WSDL to mark the port as a Provider interface. This can be done by embedding the customizations directly within the WSDL or through a separate customization file. We'll do it using a separate provider-customize.xml file--you can find this file here.