This is an ongoing blog to Web Services development. In my previous blog, I talked about JAX-WS framework in detail and how to create code first and contract web services. In this blog, I would describe how to intercept soap messages.
Many web service design uses soap headers to send information which it not related to actual payload of the message, like user id and password for accessing the resource, sending tokens for application processing. Standards like WS- Security standards requires security context to be set in soap headers.
JAX-WS provides concepts of handlers to intercept soap messages. Handlers can be easily plugged into the web service container supporting the JAX-WS 2.0 runtime environment to do additional processing of inbound and outbound messages. Handlers also can be used to validate the SOAP request and also to log the interactions. JAX-WS defines two types of handlers: protocol handlers and logical handlers.
Protocol Handler
Protocol handlers are specific to a protocol such as SOAP, which access or change any part of the message, including the message header. The JAX-WS specification defines the SOAPHandler interface for dealing with SOAP Binding. You need to implement the SOAPHandler class for intercepting and manipulating soap messages. You will look at how to create a Soap handler in the following section.
Logical Handler
Logical handlers are protocol-agnostic and can’t manipulate any protocol-specific parts of a message, such as the soap header. Logical handlers act only on the payload of the message. JAX-WS specifies the LogicalHandler interface, which needs to be extended for intercepting the payload or the soap body in case of soap messages.
Handler chain
Multiple handlers can be combined into an ordered group called a handler chain. The syntax of a handler chain is defined by JSR-109: Implementing Enterprise Web Services specification. Multiple handlers may optionally use a service name, port name, or protocol pattern in their description to apply some chains to certain ports and protocols for web services, thus limiting the handler execution for other web services. Logical handlers are executed before SOAP handlers, maintaining the original order in the chain.
Creating Soap Handlers
JAX-WS provides a plug-in framework for handlers. It also defines the life cycle of handlers and the way handlers interact with the client and server. You will now look at how to configure handlers on server and client.
Creating and Configuring SOAP Handler on Server
You can configure handlers in a handler chain either by using the annotation @javax.jws.HandlerChain on the service end point implementation or through the web services deployment descriptor that is packaged along with the web archive. Using the @javax.jws.HandlerChain annotation is much simpler if handlers needs to be executed for a particular service.
Creating and Configuring Sever Side SOAP Handler
You will modify the Order Process Web Service developed in earlier blog , and provide the @javax.jws.HandlerChain annoation for configuring handler , along with handler configuration. The following provides the code listing for Order Process Web Service with the @javax.jws.HandlerChain annotation.
package com.nb.beginjava6.service; @WebService(serviceName = "OrderProcess", portName = "OrderProcessPort", targetNamespace = "http://nb.com/beginjava6/orderprocess") @HandlerChain( file="handlers.xml") @SOAPBinding(style=SOAPBinding.Style.DOCUMENT,use=SOAPBinding.Use.LITERAL,parameterStyle=SOAPBinding.ParameterStyle.WRAPPED) public class OrderProcessService { //…. Implementation remains same. }
Code: The code listing for order process service which configures soap handler.
The handler.xml file is located in same package com.nb.beginjava6.service and defines the following configuration as per the JSR 109 specification.
<?xml version="1.0" encoding="UTF-8"?> <jws:handler-chains xmlns:jws="http://java.sun.com/xml/ns/javaee"> <jws:handler-chain> <jws:handler> <jws:handler-class>com.nb.beginjava6.service.OrderProcessServerHandler</jws:handler-class> </jws:handler> </jws:handler-chain> </jws:handler-chains>
The jws:handler-class tag, provides the handler class name com.nb.beginjava6.service.OrderProcessServerHandler. If you need to define multiple handlers, you will define < jws:handler> … </ jws:handler> configuration for each of them. Following provides the code listing for the OrderProcessServerHandler.
package com.nb.beginjava6.service; import java.util.Iterator; import java.util.Set; import javax.xml.namespace.QName; import javax.xml.soap.SOAPHeader; import javax.xml.soap.SOAPHeaderElement; import javax.xml.ws.handler.MessageContext; import javax.xml.ws.handler.soap.SOAPHandler; import javax.xml.ws.handler.soap.SOAPMessageContext; import org.w3c.dom.NodeList; public class OrderProcessServerHandler implements SOAPHandler<SOAPMessageContext> { public Set<QName> getHeaders() { return null; } public boolean handleMessage(SOAPMessageContext messageContext) { try { Boolean outboundProperty = (Boolean) messageContext .get(MessageContext.MESSAGE_OUTBOUND_PROPERTY); if (outboundProperty.booleanValue()) { // System.out.println("\nOutbound message:"); // Outbond processing.. } else { SOAPHeader soapHeader = messageContext.getMessage() .getSOAPHeader(); if (soapHeader != null) { //Extract all Soap Headers Iterator iterator = soapHeader.extractAllHeaderElements(); if (iterator.hasNext()) { SOAPHeaderElement he = (SOAPHeaderElement) iterator.next(); System.out.println("Inbound message"); System.out.println("SOAP Headers"); NodeList list = he.getChildNodes(); //Extract Child Nodes userid and tokenId for AuthToken element for (int i = 0; i < list.getLength(); i++) { System.out.println(list.item(i).getLocalName() + ":" + list.item(i).getTextContent()); } } } } } catch (Exception e) { e.printStackTrace(); return false; } return true; } public boolean handleFault(SOAPMessageContext arg0) { // TODO Auto-generated method stub return false; } public void close(MessageContext arg0) { // TODO Auto-generated method stub } }
Code: The code listing for server soap handler.
Let’s analyze the important APIs in the above code listing. The OrderProcessServerHandler implements the SOAPHandler interface. The SOAPHandler extends the base Handler interface which provides the handleMessage(C context), handleFault(C context) and close(MessageContext context) method, where C extends the MessageContext interface. The MessageContext abstracts the message context that is processed by a handler in the handleMessage and handleFault methods. There are two sub interfaces of MessageContext, LogicalMessageContext which provides access to the contained message for Logical Handlers and SOAPMessageContext interface which provides access to SOAP message for SOAP Handlers.
The handleMessage method provides an entry point for intercepting soap messages. The method is invoked for normal processing of inbound and outbound messages by the JAX-WS runtime. In the above code listing, the following steps are performed.
- Get the SOAPHeader from SOAP Message
- Get all the headers elements in SOAP header , using the extractAllHeaderElements() method on SOAPHeader
- Iterate through the elements and get the SOAPHeaderElement from the SOAPHeader
- Get the child nodes associated with SOAPHeaderElement and print it on the console.
Later you will see how to add soap headers in the client handler, which is extracted by the above soap handler and printed on the console.
Creating and Configuring a Client SOAP Handler
Handlers can be configured on the client using a @HandlerChain annotation or programmatically using API defined by JAX-WS.
Creating and Configuring Client Side SOAP Handler
You will create a web service client named OrderSOAPHandlerClient, which configures handler chains programmatically. Handler chains are configured on the client side at runtime by setting a chain directly on a BindingProvider (the port proxy) or by using a HandlerResolver.
The code listing provided below shows how to set the handler on the BindingProvider. The code is exactly the same as OrderClient, with required modifications in processOrder() listed in bold. Rest of the code implementations remains same as in OrderClient and not shown for simplicity.
package com.nb.beginjava6.service.client; import java.net.MalformedURLException; import java.net.URL; import java.util.List; import javax.xml.namespace.QName; import javax.xml.ws.Binding; import javax.xml.ws.BindingProvider; import javax.xml.ws.handler.Handler; public class OrderSOAPHandlerClient { public static void main(String[] args) { //Get the URL , similar to OrderSOAPClient OrderSOAPHandlerClient client = new OrderSOAPHandlerClient(); client.processOrder(url); } public void processOrder(URL url) { OrderProcess orderProcessingService = new OrderProcess(url, qName); OrderBean order = populateOrder(); OrderProcessService port = orderProcessingService.getOrderProcessPort(); //Instantiate the Handler passing in user id and token id. OrderProcessClientHandler handler = new OrderProcessClientHandler( "john", "XYXYXZZ"); Binding binding = ((BindingProvider) port).getBinding(); // Get the handler chain from binding and add your soap handler to it. List<Handler> handlerList = binding.getHandlerChain(); <%%KEEPWHITESPACE%%> handlerList.add(handler); <%%KEEPWHITESPACE%%> //Set the handlerList to handler chain binding.setHandlerChain(handlerList); OrderBean orderResponse = port.processOrder(order); System.out.println("Order id is " + orderResponse.getOrderId()); } }
Code: The code listing for web service client which configures the client handler.
In the above code listing , the OrderProcessClientHandler represents the client soap handler that is added to the handler chain. In order to get acess to hander chain , you need to get the Binding object from the proxy (OrderProcessService) port and call the getHandlerChain() on the Binding object. One you have acess to handler chain , you add the handler(OrderProcessClientHandler) to it. Next you need to set the handler chain back on the Binding object using the setHandlerChain() method.
Before running the OrderSOAPHandlerClient, let’s look at the following code listing for client soap handler OrderProcessClientHandler. The handler creates a RequestInfo element (which contains user id and token id element) and adds the RequestInfo element to the soap header.
package com.nb.beginjava6.service.client; import java.util.Set; import javax.xml.namespace.QName; import javax.xml.soap.SOAPElement; import javax.xml.soap.SOAPHeader; import javax.xml.ws.handler.MessageContext; import javax.xml.ws.handler.soap.SOAPHandler; import javax.xml.ws.handler.soap.SOAPMessageContext; public class OrderProcessClientHandler implements SOAPHandler<SOAPMessageContext> { private String userId; private String tokenId; public OrderProcessClientHandler() { } public OrderProcessClientHandler(String userId, String tokenId) { this.userId = userId; this.tokenId = tokenId; } public Set<QName> getHeaders() { // TODO Auto-generated method stub return null; } public boolean handleMessage(SOAPMessageContext context) { try { Boolean outboundProperty = (Boolean) context .get(MessageContext.MESSAGE_OUTBOUND_PROPERTY); <%%KEEPWHITESPACE%%> if (outboundProperty.booleanValue()) { <%%KEEPWHITESPACE%%> System.out.println("\nOutbound message:"); SOAPHeader header = context.getMessage().getSOAPHeader(); if (header == null) { <%%KEEPWHITESPACE%%> context.getMessage().getSOAPPart().getEnvelope() <%%KEEPWHITESPACE%%> .addHeader(); <%%KEEPWHITESPACE%%> header = context.getMessage().getSOAPHeader(); <%%KEEPWHITESPACE%%> } SOAPElement reqHeader = header.addChildElement("RequestInfo", <%%KEEPWHITESPACE%%> "requestinfo", "urn:orderprocess"); reqHeader.addChildElement("userId", "requestinfo").addTextNode(userId); <%%KEEPWHITESPACE%%> reqHeader.addChildElement("tokenId", "requestinfo") <%%KEEPWHITESPACE%%> .addTextNode(tokenId); } else { System.out.println("\nInbound message:"); } context.getMessage().writeTo(System.out); } catch (Exception e) { e.printStackTrace(); return false; } return true; } public boolean handleFault(SOAPMessageContext arg0) { // TODO Auto-generated method stub return false; } public void close(MessageContext arg0) { // TODO Auto-generated method stub } public String getTokenId() { return tokenId; } public void setTokenId(String tokenId) { this.tokenId = tokenId; } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } }
Code: The code listing for client soap handler.
The code listing for client side soap handler is similar to the server side, expect it adds the required soap header information to out going soap message. The handleMessage() check if it’s an out bound request ands carries out the following steps :
- Checks is the header is null, if yes adds a header by getting the soap envelope and calling the add header method on it.
- Adds a child element with local name as “RequestInfo” , prefix as “requestinfo” and namespace as “urn:orderprocess” to soap header using addChildElement method as listed below.
SOAPElement reqHeader = header.addChildElement("RequestInfo", "requestinfo", "urn:orderprocess");
- Similarly , the userId and tokenId element is added to reqHeader created in above step.
Before running the OrderProcessClientHandler, make sure that the OrderProcess Web Service is running as mentioned in earlier blog, if not run the OrderWebServicePublisher class.
Execute the OrderProcessClientHandler web service client by providing the WSDL URL for the OrderProcess Web Service using the following command.
java com.nb.beginjava6.service.client.OrderSOAPHandlerClient
http://localhost:8080/OrderProcessWeb/orderprocess?wsdl
The following soap message will be printed at the console, where the OrderSOAPHandlerClient is executed. As you see in listing below the soap header contains the RequestInfo element that’s added by the client handler OrderProcessClientHandler.
Outbound message: Request being sent to Web Service
<?xml version="1.0"?> <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://nb.com/beginjava6/orderprocess" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <soapenv:Header> <requestinfo:RequestInfo xmlns:requestinfo="urn:orderprocess"><requestinfo:userId>john</requestinfo:userId><requestinfo:tokenId>XYXYXZZ</requestinfo:tokenId> </requestinfo:RequestInfo> </soapenv:Header> <soapenv:Body> <ns1:processOrder><arg0><customer><customerId>A123</customerId><firstName>John</firstName><lastName>Smith</lastName></customer><orderItems><itemId>11</itemId><qty>11</qty></orderItems></arg0> </ns1:processOrder> </soapenv:Body> </soapenv:Envelope>
Inbound message: Response received from Web Service
<?xml version="1.0"?> <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns1="http://nb.com/beginjava6/orderprocess"> <soapenv:Body> <ns1:processOrderResponse><return><customer><customerId>A123</customerId><firstName>John</firstName><lastName>Smith</lastName></customer><orderId>A1234</orderId><orderItems><itemId>11</itemId><qty>11</qty></orderItems></return> </ns1:processOrderResponse> </soapenv:Body> </soapenv:Envelope>
Order id is A1234
On the Server side, where the OrderWebServicePublisher is running, you will see the following being printed at the conssole. The server soap handler OrderProcessServerHandler as discussed earlier intercepts the incoming soap message , extracts the soap header and prints it on the console.
Inbound message
SOAP Headers
userId:john
tokenId:XYXYXZZ
processOrder called for customerA123
Number of items is 1
Thus you have sucesfully configured soap handlers on the client and server.Based on your requirments , your clients can pass in security or context information in the soap header and on the server , you can the extract the information from the soap header , validate the credentials and throw a expection if credentials are not satisfied.
In next blog , I would describe how to invoke a real world .NET web service using JAX-WS APIs.