JAX-WS SOAP Handler Example
In this example we are going to see how to Use SOAP Handler to intercept SOAP messages form the client – server communication. A SOAP handler can also change the SOAP messages from/to client/server. In this example we are going to create a Handler in the Client Side that will add a header to the SOAP message containing a random property String. At the other end, there is a Handler in the Server Side that will exctract this property String and decide whether to serve him or not. For example you can have a list of your trusted IP addresses (or any kind of filter for that mater), and only serve SOAP messages containing these trusted IP addresses at their headers.
For invalid SOAP messages, an Exception will be thrown and a SOAP Fault message will be attached to the SOAP envelope.
Service Endpoint
In the Web Server Endpoint there will be a SOAPHandler
that will extract the special header from the SOAP message, will read the property that the client put it that header, and decide whether to serve him or not.
But let’s see the Endpoint Creation step by step :
In order to create our Web Service Endpoint:
- First you have to create a Web Service Endpoint Interface. This interface will contain the declerations of all the methods you want to include in the Web Service.
- Then you have to create a class that actually implements the above interface, which will be your Endpoint implementation.
- Next we are going to create an
IpValidator
class that will implementSOAPHandler<SOAPMessageContext>
. - We will create an XML file (
handlers.xml
) that will define theIpValidator
class as a SOAP handler. This file can also chain other handlers. The SOAP message will be handled by all of those handlers, one by one. Then, we are going to add a@HandlerChain
annotation to the Server Endpoint Implementation class, to state to the web service to use a specific SOAP handler (obviously the one that we’ve created) - Finally you create your Endpoint publisher which actually deploys the web service and creates and publishes the endpoint for the specified implementor object at a given address. The necessary server infrastructure will be created and configured by the JAX-WS implementation. You have to run the publisher to make your Web Service available to clients.
Web Service Endpoint Interface
WebServiceInterface.java:
package com.javacodegeeks.enterprise.ws; import javax.jws.WebMethod; import javax.jws.WebService; import javax.jws.soap.SOAPBinding; import javax.jws.soap.SOAPBinding.Style; @WebService @SOAPBinding(style = Style.DOCUMENT) public interface WebServiceInterface { @WebMethod String printMessage(); }
Web Service Endpoint Implementation
WebServiceImpl.java:
package com.javacodegeeks.enterprise.ws; import javax.jws.HandlerChain; import javax.jws.WebService; @WebService(endpointInterface = "com.javacodegeeks.enterprise.ws.WebServiceInterface") public class WebServiceImpl implements WebServiceInterface{ @Override public String printMessage() { return "Hello from Java Code Geeks Server"; } }
SOAP Handler
PropertyValidator.java:
package com.javacodegeeks.enterprise.ws.handler; import java.io.IOException; import java.util.Iterator; import java.util.Set; import javax.xml.namespace.QName; import javax.xml.soap.Node; import javax.xml.soap.SOAPBody; import javax.xml.soap.SOAPConstants; import javax.xml.soap.SOAPEnvelope; import javax.xml.soap.SOAPException; import javax.xml.soap.SOAPFault; import javax.xml.soap.SOAPHeader; import javax.xml.soap.SOAPMessage; import javax.xml.ws.handler.MessageContext; import javax.xml.ws.handler.soap.SOAPHandler; import javax.xml.ws.handler.soap.SOAPMessageContext; import javax.xml.ws.soap.SOAPFaultException; public class PropertyValidator implements SOAPHandler<SOAPMessageContext> { private final String VALID_PROPERTY = "RANDOM"; @Override public boolean handleMessage(SOAPMessageContext context) { System.out.println("Server executing SOAP Handler"); Boolean outBoundProperty = (Boolean) context .get(MessageContext.MESSAGE_OUTBOUND_PROPERTY); // if this is an incoming message from the client if (!outBoundProperty) { try { // Get the SOAP Message and grab the headers SOAPMessage soapMsg = context.getMessage(); SOAPEnvelope soapEnv = soapMsg.getSOAPPart().getEnvelope(); SOAPHeader soapHeader = soapEnv.getHeader(); // Grab an iterator to go through the headers Iterator<?> headerIterator = soapHeader .extractHeaderElements(SOAPConstants.URI_SOAP_ACTOR_NEXT); // if there is no additional header if (headerIterator != null && headerIterator.hasNext()) { // Extract the property node of the header Node propertyNode = (Node) headerIterator.next(); String property = null; if (propertyNode != null) property = propertyNode.getValue(); if (VALID_PROPERTY.equals(property)) { // Output the message to the Console -- for debug soapMsg.writeTo(System.out); } else { // Restrict the execution of the Remote Method // Attach an error message as a response SOAPBody soapBody = soapMsg.getSOAPPart().getEnvelope().getBody(); SOAPFault soapFault = soapBody.addFault(); soapFault.setFaultString("Invalid Property"); throw new SOAPFaultException(soapFault); } } } catch (SOAPException e) { System.err.println(e); } catch (IOException e) { System.err.println(e); } } return true; } @Override public boolean handleFault(SOAPMessageContext context) { return true; } @Override public void close(MessageContext context) { } @Override public Set<QName> getHeaders() { return null; } }
SOAP Handler XML File
handlers.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <javaee:handler-chains xmlns:javaee="http://java.sun.com/xml/ns/javaee" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <javaee:handler-chain> <javaee:handler> <javaee:handler-class>com.javacodegeeks.enterprise.ws.handler.PropertyValidator</javaee:handler-class> </javaee:handler> </javaee:handler-chain> </javaee:handler-chains>
Add @HandlerChain annotation to SEI
WebServiceImpl.java:
package com.javacodegeeks.enterprise.ws; import javax.jws.HandlerChain; import javax.jws.WebService; @WebService(endpointInterface = "com.javacodegeeks.enterprise.ws.WebServiceInterface") @HandlerChain(file="./handler/handlers.xml") public class WebServiceImpl implements WebServiceInterface{ @Override public String printMessage() { return "Hello from Java Code Geeks Server"; } }
Web Service Endpoint Publisher
WebServicePublisher.java:
package com.javacodegeeks.enterprise.ws; import javax.xml.ws.Endpoint; import com.javacodegeeks.enterprise.ws.WebServiceImpl; public class WebServicePublisher{ public static void main(String[] args) { Endpoint.publish("http://localhost:8888/webservice/validator", new WebServiceImpl()); } }
Now when you run the publisher the Web Service will be available to the clients, deployed in the URL:
http://localhost:8888/webservice/validator
Ok, that’s it for the Server Side. Let’s see the project structure od the Eclipse project to get a clear view of all the files and their location:
Client Side
In the Client Side there will be a SOAP Handler to attach a new Header to every outgoing SOAP message. But first, we are going to use wsimport tool to create all necessary file for the Client Side program (see JAX-WS Hello World Example – RPC Style).
So, this is the command that I issued :
wsimport -keep http://localhost:8888/webservice/validator?wsdl -d F:\nikos7\Desktop
-keep
: is an options that lets you keep the generated files-d
: you can sepcify the folder where the generated files will be stored
This command will create six java source files. In our case we are only going to manipulate WebServiceImplService.java
This is how that file looks like:
WebServiceImplService.java:
package com.javacodegeeks.enterprise.ws; import java.net.MalformedURLException; import java.net.URL; import javax.xml.namespace.QName; import javax.xml.ws.Service; import javax.xml.ws.WebEndpoint; import javax.xml.ws.WebServiceClient; import javax.xml.ws.WebServiceException; import javax.xml.ws.WebServiceFeature; import javax.jws.HandlerChain; /** * This class was generated by the JAX-WS RI. JAX-WS RI 2.2.4-b01 Generated * source version: 2.2 * */ @WebServiceClient(name = "WebServiceImplService", targetNamespace = "http://ws.enterprise.javacodegeeks.com/", wsdlLocation = "http://localhost:8888/webservice/validator?wsdl") public class WebServiceImplService extends Service { ... }
We are going to create the SOAP Handler and the SOAP Handler XML file and then add a @HandlerChain
annotation to the above file
SOAP Handler
PropertyInjector.java:
package com.javacodegeeks.enterprise.ws.handler; import java.io.IOException; import java.util.Set; import javax.xml.namespace.QName; import javax.xml.soap.SOAPConstants; import javax.xml.soap.SOAPEnvelope; import javax.xml.soap.SOAPException; import javax.xml.soap.SOAPHeader; import javax.xml.soap.SOAPHeaderElement; import javax.xml.soap.SOAPMessage; import javax.xml.ws.handler.MessageContext; import javax.xml.ws.handler.soap.SOAPHandler; import javax.xml.ws.handler.soap.SOAPMessageContext; public class PropertyInjector implements SOAPHandler<SOAPMessageContext> { @Override public boolean handleMessage(SOAPMessageContext context) { System.out.println("Client executing SOAP Handler"); Boolean outboundProperty = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY); // If it is an outgoing message if (outboundProperty.booleanValue()) { try { SOAPMessage soapMessagg = context.getMessage(); SOAPEnvelope soapEnvelope = soapMessagg.getSOAPPart().getEnvelope(); // Grab the header of the SOAP envelop SOAPHeader soapHeader = soapEnvelope.getHeader(); // Attach a new header if there is none... if (soapHeader == null) { soapHeader = soapEnvelope.addHeader(); } // add the property to the header QName qname = new QName("http://ws.enterprise.javacodegeeks.com/", "PROPERTY"); // Create a new HeaderElement in order to place the new property SOAPHeaderElement soapHeaderElement = soapHeader.addHeaderElement(qname); soapHeaderElement.setActor(SOAPConstants.URI_SOAP_ACTOR_NEXT); soapHeaderElement.addTextNode("RANDOM"); soapMessagg.saveChanges(); // Output the message to the Console -- for debug soapMessagg.writeTo(System.out); } catch (SOAPException e) { System.err.println(e); } catch (IOException e) { System.err.println(e); } } return true; } @Override public boolean handleFault(SOAPMessageContext context) { return true; } @Override public void close(MessageContext context) { } @Override public Set<QName> getHeaders() { return null; } }
This Handler basically adds a new Header element to the SOAP envelope with a simple String in it. It will also add an Actor to the Header. A SOAP message may travel from a sender to a receiver by passing different endpoints along the message path. However, not all parts of a SOAP message may be intended for the ultimate endpoint, instead, it may be intended for one or more of the endpoints on the message path. The SOAP actor attribute is used to address the Header element to a specific endpoint.
The SOAP actor global attribute can be used to indicate the recipient of a header element. The value of the SOAP actor attribute is a URI. The special URI “http://schemas.xmlsoap.org/soap/actor/next” as the value of actor attribute indicates that the next SOAP node in the message path must process the header element.
SOAP 1.1 defines only one single role named http://schemas.xmlsoap.org/soap/actor/next (URI_SOAP_ACTOR_NEXT, for short). Every SOAP node is required to assume the next role. Thus, when a SOAP message arrives at any SOAP node, the node must process all headers marked with the next role. In addition to next, SOAP 1.2 defines a few more roles and applications are allowed to define custom roles as well.
The absence of the SOAP actor attributes, in the header block, indicates that the recipient is the ultimate destination of the SOAP message. This attribute must appear in the SOAP message instance in order to be effective.
SOAP Handler XML File
handlers.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <javaee:handler-chains xmlns:javaee="http://java.sun.com/xml/ns/javaee" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <javaee:handler-chain> <javaee:handler> <javaee:handler-class>com.javacodegeeks.enterprise.ws.handler.PropertyInjector</javaee:handler-class> </javaee:handler> </javaee:handler-chain> </javaee:handler-chains>
Add @HandlerChain annotation to WebServiceImplService
WebServiceImplService .java:
package com.javacodegeeks.enterprise.ws; import java.net.MalformedURLException; import java.net.URL; import javax.xml.namespace.QName; import javax.xml.ws.Service; import javax.xml.ws.WebEndpoint; import javax.xml.ws.WebServiceClient; import javax.xml.ws.WebServiceException; import javax.xml.ws.WebServiceFeature; import javax.jws.HandlerChain; /** * This class was generated by the JAX-WS RI. JAX-WS RI 2.2.4-b01 Generated * source version: 2.2 * */ @WebServiceClient(name = "WebServiceImplService", targetNamespace = "http://ws.enterprise.javacodegeeks.com/", wsdlLocation = "http://localhost:8888/webservice/validator?wsdl") @HandlerChain(file="./handler/handlers.xml") public class WebServiceImplService extends Service { ... }
WSClient.java
package com.javacodegeeks.enterprise.ws; import com.javacodegeeks.enterprise.ws.WebServiceImplService; public class WSClient { public static void main(String[] args) { WebServiceImplService webService = new WebServiceImplService(); WebServiceInterface serviceInterface = webService.getWebServiceImplPort(); System.out.println(serviceInterface.printMessage()); } }
Let’s see the Eclipse project structure:
Test the Application
Now let’s see what happens if we run the above program.
Output for a valid Property
Client executing SOAP Handler
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Header>
<PROPERTY xmlns="http://ws.enterprise.javacodegeeks.com/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:actor="http://schemas.xmlsoap.org/soap/actor/next">
RANDOM
</PROPERTY>
</S:Header>
<S:Body>
<ns2:printMessage xmlns:ns2="http://ws.enterprise.javacodegeeks.com/"/>
</S:Body>
</S:Envelope>
Client executing SOAP Handler
Hello from Java Code Geeks Server
Output for a invalid Property
Client executing SOAP Handler
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Header>
<PROPERTY xmlns="http://ws.enterprise.javacodegeeks.com/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:actor="http://schemas.xmlsoap.org/soap/actor/next">
ascascasc
</PROPERTY>
</S:Header>
<S:Body>
<ns2:printMessage xmlns:ns2="http://ws.enterprise.javacodegeeks.com/"/>
</S:Body>
</S:Envelope>
Exception in thread "main" javax.xml.ws.soap.SOAPFaultException: Invalid Property
at com.sun.xml.internal.ws.fault.SOAP11Fault.getProtocolException(Unknown Source)
at com.sun.xml.internal.ws.fault.SOAPFaultBuilder.createException(Unknown Source)
at com.sun.xml.internal.ws.client.sei.SyncMethodHandler.invoke(Unknown Source)
at com.sun.xml.internal.ws.client.sei.SyncMethodHandler.invoke(Unknown Source)
at com.sun.xml.internal.ws.client.sei.SEIStub.invoke(Unknown Source)
at com.sun.proxy.$Proxy22.printMessage(Unknown Source)
at com.javacodegeeks.enterprise.ws.WSClient.main(WSClient.java:11)
This was an example on JAX-WS SOAP Handler. Download the Eclipse Projects of this Example : SOAPHandlerExample.zip