jws

Jax-Ws LogicalHandler Example

In this example, we will show you how to implement a JAX-WS Logical Handler.

1. Introduction

When you send a request to a web service, the SOAP message may require verification. The SOAP message response may also require verification. For these requirements, you can use a SOAP message handler to intercept the SOAP message for further processing.

SOAP message handlers have access to the SOAP message and can be implemented on the consumer or the provider side of the web service. The handler can determine if the message is inbound or outbound and build specific behavior for those events.

SOAP message handlers are well suited for addressing non-functional requirements such as:

  • Custom authentication
  • Caching responses to improve performance
  • Logging

There are two types of SOAP message handlers; SOAP handlers and logical handlers. SOAP handlers have access to all parts of the message; the HTTP header, the SOAP header and the SOAP body. Logical handlers can access the payload of the SOAP message only, that is, the data contained within the SOAP body. Logical handlers are the focus of this example.

1.1 Tools Used in this Example

  • Eclipse Oxygen
  • Maven 3.2.1
  • Tomcat 8.5.4
  • SoapUI 5.3.0

For Maven support within Eclipse, install M2Eclipse. Please visit the M2Eclipse website for more information.

To setup a Tomcat server for use in Eclipse, see Tomcat in Eclipse: 6 popular how to questions.

2. JAX-WS Logical Handler Example

In this example, we will create a simple web service that returns the capital city name for a given state. This exercise uses the “WSDL-first” or “top-down” approach to web services development.

2.1 Create the Web Service Provider

2.1.1 Create the Web Service Project

Let’s begin by creating a simple Maven Project.

  1. Select New -> Other… Maven Project. Click “Next”.
  2. Select “Create a simple project (skip archetype selection)” and click “Next”.
  3. Enter a Group Id and Artifact Id. Select “war” for Packaging and enter a Name and Description if desired. Click “Finish”.

Maven Project Configuration

At this point, you will see the following error: web.xml is missing and<failOnMissingWebXml> is set to true , since we elected to package the application as a war file.  To fix this, right-click on the project and select Java EE Tools -> Generate Deployment Descriptor Stub.

2.1.2 Create the WSDL File

Create a new folder named “wsdl” in the WEB-INF directory and create a WSDL file inside the folder with the following content:

StateCapital.wsdl

<?xml version='1.0' encoding='UTF-8'?>
<wsdl:definitions xmlns:xsd="http://www.w3.org/2001/XMLSchema"
	xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://jaxws.examples.javacodegeeks.com/"
	xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:ns1="http://schemas.xmlsoap.org/soap/http"
	name="StateCapitalLookupService" targetNamespace="http://jaxws.examples.javacodegeeks.com/">

	<wsdl:types>
		<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
		xmlns:tns="http://jaxws.examples.javacodegeeks.com/" elementFormDefault="unqualified"
		targetNamespace="http://jaxws.examples.javacodegeeks.com/" version="1.0">

			<xs:complexType name="getStateCapitalRequest">
				<xs:sequence>
					<xs:element minOccurs="0" name="state" type="xs:string" />
				</xs:sequence>
			</xs:complexType>

			<xs:complexType name="getStateCapitalResponse">
				<xs:sequence>
					<xs:element minOccurs="0" name="city" type="xs:string" />
				</xs:sequence>
			</xs:complexType>

		<xs:element name="getStateCapitalRequest" type="tns:getStateCapitalRequest" />

		<xs:element name="getStateCapitalResponse" type="tns:getStateCapitalResponse" />

		</xs:schema>
	</wsdl:types>

	<wsdl:message name="getStateCapitalRequest">
		<wsdl:part element="tns:getStateCapitalRequest" name="parameters">
		</wsdl:part>
	</wsdl:message>
	<wsdl:message name="getStateCapitalResponse">
		<wsdl:part element="tns:getStateCapitalResponse" name="parameters">
		</wsdl:part>
	</wsdl:message>

	<wsdl:portType name="StateCapitalPortType">
		<wsdl:operation name="getStateCapital">
			<wsdl:input message="tns:getStateCapitalRequest" name="getStateCapitalRequest">
			</wsdl:input>
			<wsdl:output message="tns:getStateCapitalResponse" name="getStateCapitalResponse">
			</wsdl:output>
		</wsdl:operation>
	</wsdl:portType>

	<wsdl:binding name="StateCapitalLookupServiceSoapBinding"
		type="tns:StateCapitalPortType">
		<soap:binding style="document"
			transport="http://schemas.xmlsoap.org/soap/http" />
		<wsdl:operation name="getStateCapital">
			<soap:operation soapAction="" style="document" />
			<wsdl:input name="getStateCapitalRequest">
				<soap:body use="literal" />
			</wsdl:input>
			<wsdl:output name="getStateCapitalResponse">
				<soap:body use="literal" />
			</wsdl:output>
		</wsdl:operation>
	</wsdl:binding>

	<wsdl:service name="StateCapitalLookupService">
		<wsdl:port binding="tns:StateCapitalLookupServiceSoapBinding"
			name="StateCapitalPort">
			<soap:address
				location="http://localhost:8080/jaxwslogicalhandlerexample/services/stateCapitalLookupService" />
		</wsdl:port>
	</wsdl:service>
</wsdl:definitions>	

The WSDL file defines one operation for our StateCapitol lookup service, getStateCapital. The operation takes a String as a parameter and returns a String.

2.1.3 Update the POM File

Open the pom.xml and add the following Maven plugins just above the closing </project> tag:

pom.xml

<properties>
 <cxf.version>3.1.11</cxf.version>
 </properties>
 <build>
	 <plugins>
		 <plugin>
		 <groupId>org.apache.maven.plugins</groupId>
		 <artifactId>maven-compiler-plugin</artifactId>
		 <version>3.6.1</version>
		 <configuration>
			 <source>1.8</source>
			 <target>1.8</target>
		 </configuration>
	 </plugin>
	 <plugin>
		 <groupId>org.apache.maven.plugins</groupId>
		 <artifactId>maven-war-plugin</artifactId>
		 <version>3.0.0</version>
		 <configuration>
			 <warSourceDirectory>src/main/webapp</warSourceDirectory>
			 <webXml>src/main/webapp/WEB-INF/web.xml</webXml>
			 <warName>JaxWsLogicalHandler</warName>
		 </configuration>
		 </plugin>
		 <plugin>
			 <groupId>org.apache.cxf</groupId>
			 <artifactId>cxf-codegen-plugin</artifactId>
			 <version>${cxf.version}</version>
			 <executions>
				 <execution>
					 <id>generate-sources</id>
					 <phase>generate-sources</phase>
					 <configuration>
						 <sourceRoot>${basedir}/src/generated</sourceRoot>
						 <wsdlOptions>
							 <wsdlOption>
								 <wsdl>${basedir}/src/main/webapp/WEB-INF/wsdl/StateCapital.wsdl</wsdl>
							 </wsdlOption>
						 </wsdlOptions>
					 </configuration>
					 <goals>
						 <goal>wsdl2java</goal>
					 </goals>
				 </execution>
			 </executions>
		 </plugin> 
		 </plugins> 
 </build> 

In addition to the usual compile and war plugins, we are including the CXF codegen plugin. This plugin will run the wsdl2java goal to generate the web services stubs based on our WSDL file. The configuration section specifies the folder where the generated files will be placed.
It also specifies the location of the WSDL file.

Note: It is important the generated files to be placed under the source directory so that it is part of the Java Build Path.

Also, add the following dependencies below the closing </build> tag:

<dependencies>
  <dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-frontend-jaxws</artifactId>
    <version>${cxf.version}</version>
  </dependency>
  <dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-transports-http</artifactId>
    <version>${cxf.version}</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>4.3.8.RELEASE</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
   <version>4.3.8.RELEASE</version>
  </dependency>
</dependencies>

Save the changes and select: Maven->Update Project from the project context menu. If you look at your project structure you will see a new folder under “src” named “generated”. (You may need to “refresh” to see them.) This is where the wsdl2java tool placed the generated web service stubs.

2.1.4 Implement the Web Service

Create a new package named com.javacodegeeks.examples.jaxws.impl in “/src/main/java”. Inside this package, create a class that implements the StateCapitalPortType service endpoint interface and add the following code:

StateCapitolWSImpl.java

import java.util.HashMap;
import java.util.Map;

import com.javacodegeeks.examples.jaxws.GetStateCapitalRequest;
import com.javacodegeeks.examples.jaxws.GetStateCapitalResponse;
import com.javacodegeeks.examples.jaxws.StateCapitalPortType;

public class StateCapitolWSImpl implements StateCapitalPortType {

        Map<String, String> stateDataMap = new HashMap<>();
	
	public StateCapitolWSImpl() {
		init();
	}

	private void init() {
		stateDataMap.put("AL", "Montgomery");
		stateDataMap.put("CO", "Denver");
		stateDataMap.put("GA", "Atlanta");
		
	}

	@Override
	public GetStateCapitalResponse getStateCapital(GetStateCapitalRequest parameters) {
		String state = parameters.getState();
		String capital = stateDataMap.get(state);
		GetStateCapitalResponse response = new GetStateCapitalResponse();
		response.setCity(capital);
		return response;
	}
}

First, we create a class member of type Map that we’ll use to store entries of state abbreviations and capital city names. We then define a constructor that will call the init method, which adds a few entries to our map. Finally, we implement the getStateCapital method. This method returns a response that includes the name of the capital city for the state that was passed in the request.

2.1.5 Create the Spring Configuration File

Create the cxf-servlet.xml file in the webapp/WEB-INF directory to set up our service endpoint.

cxf-servlet.xml

 <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxws="http://cxf.apache.org/jaxws"
	xmlns:cxf="http://cxf.apache.org/core" xmlns:soap="http://cxf.apache.org/bindings/soap"
	xsi:schemaLocation="http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://cxf.apache.org/bindings/soap http://cxf.apache.org/schemas/configuration/soap.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">

	<jaxws:endpoint id="stateCapitalWs" address="/stateCapital"
		wsdlLocation="WEB-INF/wsdl/StateCapital.wsdl" implementor="com.javacodegeeks.examples.jaxws.impl.StateCapitalWSImpl"
		serviceName="stateCapital:StateCapitalLookupService"
		xmlns:stateCapital="http://jaxws.examples.javacodegeeks.com/" />

</beans>

Notice that we defined a namespace xmlns:stateCapital=http://jaxws.examples.javacodegeeks.com/ that is used as a prefix for the serviceName , which must match exactly with the service name we defined in the WSDL file.

2.1.6 Configure the CXF servlet in web.xml

The last step is to configure the CXFServlet in web.xml.  We’ll map the servlet to handle all requests coming through /services/*.

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
  <display-name>JaxWsLogicalHandler
</display-name>
 <servlet>
   <servlet-name>cxfservlet</servlet-name>
   <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
   <load-on-startup>1</load-on-startup>
 </servlet>
 <servlet-mapping>
   <servlet-name>cxfservlet</servlet-name>
   <url-pattern>/services/*</url-pattern>
 </servlet-mapping>
</web-app>

2.1.7 Start the StateCapital Service

Run maven clean to clear the target directory and maven install  to create the war file. (Right-click the project and select Run as and you will see these options in the context menu.) The generated war file can be deployed to a Tomcat server by copying it to the webapps directory and starting the server.

Let’s test our web service from within Eclipse. Right-click the project and select Run As -> Run on Server. Select the Tomcat server and add our project to configure and click “Finish”. When the application starts, we will see a 404 error in the browser, since we do not have a welcome page specified in web.xml. Enter /services/ at the end of the URL and hit “Enter”. You will see a link to the WSDL file on the “Available SOAP services” page.

StateCapitolLookup Service

Click the link to see the WSDL file that was generated by the web services runtime.

Note: If you are using Eclipse’s internal browser, you may see a blank page. Copy the URL from the address bar and open the link in an external browser.

2.2 Testing the Web Service with SoapUI

We will test our web service with SoapUI in this example.

Start SoapUI and create a new SOAP project by clicking the SOAP button on the top bar.

Create a SOAP Project
Create a SOAP Project

Enter a name for your project and paste the WSDL URL link of the web service in the Initial WSDL text box, for example: http://localhost:8080/JaxWsLogicalHandler/services/stateCapital?wsdl . Make sure “Create Requests” is selected and click OK.

New SOAP Project Configuration

Expand “StateCapitalLookupServiceSoapBinding” and “getStateCapital” in the Navigator and double-click “Request 1”. Enter “al” (in lowercase letters) inside the state tag of the request.

request

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:jax="http://jaxws.examples.javacodegeeks.com/">
  <soapenv:Header/>
  <soapenv:Body>
    <jax:getStateCapitalRequest>
      <!--Optional:-->
      <state>al</state>
    </jax:getStateCapitalRequest>
  </soapenv:Body>
</soapenv:Envelope>

Click the green arrow to submit the request.

response

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <ns2:getStateCapitalResponse xmlns:ns2="http://jaxws.examples.javacodegeeks.com/"/>
  </soap:Body>
</soap:Envelope>

If you look at the response, you will see that the getStateCapitalResponse is empty and the capital city was not returned. That is because the StateCapital lookup is case-sensitive. Let’s fix that by creating a Logical Handler.

2.3 Create a Logic Handler

To fix the issue, we will intercept the incoming request on the service side and modify the payload so that state abbreviations are always in uppercase. You can modify the payload by working with the message body as an XML object or a JAXB object. We will use the latter in this example

2.3.1 Create the Handler Class

Begin by creating a new Java class StateHandler that implements LogicalHandler.

At this point, you will see several errors.

  • C cannot be resolved to a type.

LogicalHandler is a parameterized interface. Add the parameter type LogicalMessageContext. This is the context we’ll use to work with the logical message. LogicalHandler<LogicalMessageContext>

You will also see these errors:

  • The type StateHandler must implement the inherited abstract method Handler.close(MessageContext)
  • The type StateHandler must implement the inherited abstract method Handler.handleFault(C)
  • The type StateHandler must implement the inherited abstract method Handler.handleMessage(C)

In its current state, StateHandler has unimplemented methods. Hover over StateHandler and select “Add unimplemented methods” from the available quick fixes. This will add the three methods to StateHandler .

StateHandler.java

package com.javacodegeeks.examples.jaxws.handler;

import javax.xml.ws.handler.LogicalHandler;
import javax.xml.ws.handler.LogicalMessageContext;
import javax.xml.ws.handler.MessageContext;

public class StateHandler implements LogicalHandler {

	@Override
	public boolean handleMessage(LogicalMessageContext context) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public boolean handleFault(LogicalMessageContext context) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public void close(MessageContext context) {
		// TODO Auto-generated method stub
		
	}
}

Let’s discuss the purpose of these methods.

  • handleMessage( ): This method is invoked on both the inbound request and outbound response SOAP messages. You implement this method to inspect or change the logical message. Returning true will invoke the next handler in the chain, while returning false will stop further message processing.
  • handleFault( ): This method is invoked instead of handleMessage( ) when the message contains a fault. You implement this method for any exception handling you want to perform. If you want the message to be processed by the next message handler (providing there is another message handler in the chain), return true, otherwise, return false.
  • close( ): This method is called once during the response phase of the service. You implement this method to perform any cleanup of resources.

2.3.2 Implement the handleMessage Method

Let’s implement our handleMessage method by adding the following code:

	@Override
	public boolean handleMessage(LogicalMessageContext context) {
		boolean outbound = (Boolean)
	             context.get (MessageContext.MESSAGE_OUTBOUND_PROPERTY);
		if (!outbound) {
			System.out.println("\nInbound message:");
           LogicalMessage msg = context.getMessage(); 
           JAXBContext jaxbContext = null;
           try {
				jaxbContext = JAXBContext.newInstance(ObjectFactory.class);
               Object payload = msg.getPayload(jaxbContext); 
               if (payload instanceof JAXBElement) {
                   Object obj = ((JAXBElement)payload).getValue();
                   System.out.println("payload:"+payload.getClass().getName());
                   GetStateCapitalRequest request = (GetStateCapitalRequest) obj;
                   String stateUC = request.getState().toUpperCase();
                   request.setState(stateUC);
                   ((JAXBElement)payload).setValue(request);
                   msg.setPayload(payload, jaxbContext);
               }             	
			} catch (JAXBException e) {
				e.printStackTrace();
			}
		} else {
           System.out.println("\nOutbound message:");
		}
		return true;
	}

The first thing we do is determine if this is an inbound or outbound message. You accomplish this by inspecting the MESSAGE_OUTBOUND_PROPERTY property of the LogicalMessageContext.

On inbound messages:
We retrieve the LogicalMessage from the message context. Next, we create a JAXBContext instance from the com.javacodegeeks.examples.jaxws.ObjectFactory of our generated files. We then retrieve the message payload as an Object.

If the payload is a instance of JAXBElement, we get its value and cast it to our request object (GetStateCapitalRequest) so that we can call its methods. We then create a local variable stateUC that we initialize as an uppercase version of the state abbreviation retrieved from the request.

Next, we re-build the request by setting the updated state value in GetStateCapitalRequest, setting GetStateCapitalRequest in the payload, and finally, setting the payload in the Logical message.

On outbound messages:
We print out the message direction to the console.

Finally, we return true. This is important so that message processing continues by other handlers in the chain as well as the service.

2.3.3 Configure the Logical Handler

In order to configure the application to use our Logical Handler, we need to create a handler configuration file.

Create a new XML file called handlers.xml in the com.javacodegeeks.examples.jaxws.impl package with the following content. (The name is not important as we will specify it below, but the file has to be placed in the same package as our service implementation class.)

handlers.xml

<?xml version="1.0" encoding="UTF-8"?>
<handler-chains xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
  http://java.sun.com/xml/ns/javaee/javaee_web_services_metadata_handler_2_0.xsd">
    <handler-chain>
        <handler>
            <handler-name>StateHandler</handler-name>
            <handler-class>com.javacodegeeks.examples.jaxws.handler.StateHandler</handler-class>
        </handler>      
    </handler-chain>
</handler-chains>

We specify our handler class in this file. If we have other handlers that we want to invoke, we can add them here inside the handler-chain element.

The CXF runtime has to know the name of our handler configuration file. We address this requirement by adding the following annotation to our service implementation class.

StateCapitolWSImpl.java

@HandlerChain(file = "handlers.xml")
public class StateCapitolWSImpl implements StateCapitolPortType {

That’s all there is to it. The next step it to re-test our updated service.

2.4 Test the Updated Service with SoapUI

Start the application and re-run the previous test.

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
   <soap:Body>
      <ns2:getStateCapitalResponse xmlns:ns2="http://jaxws.examples.javacodegeeks.com/">
         <city>Montgomery</city>
      </ns2:getStateCapitalResponse>
   </soap:Body>
</soap:Envelope>

You see that this time you get the capital city returned in the SOAP message response, even if the state value of the request is in lowercase.

3. Conclusion

In this example, we demonstrated how to to implement a JAX-WS Logical Handler.

4. Download the Source Code

This was a JAX-WS LogicalHandler Example.

Download
You can download the full source code of this example here:

  1. JaxWsLogicalHandlerExample
  2. message JaxWsLogicalHandlerSoapUIProject

Gilbert Lopez

Gilbert Lopez is an application developer and systems integration developer with experience building business solutions for large and medium-sized companies. He has worked on many Java EE projects. His roles have included lead developer, systems analyst, business analyst and consultant. Gilbert graduated from California State University in Los Angeles with a Bachelor of Science degree in Business.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Jonatan Cantarino
Jonatan Cantarino
4 years ago

Is there a way to specify the @HandlerChain tag in the cxf-codegen-plugin configuration so it autogenerates?

Great post, thanks

Back to top button