JAX-WS Web Services with Spring and Apache CXF

I recently had to evaluate CXF to expose existing services in a Spring project. I thought I would jot down my thoughts and conclusions from my experiments with the technology, and log my experience as a quick tutorial for fellow coders.

Prerequisites:

  • I use the Eclipse IDE but any IDE of your choice should suffice as long as you know your way around.
  • Download Spring 3 from here and download CXF 2.5 from here.
  • Create a working install of Tomcat in your IDE or standalone.
  • Intermediate working knowledge of Java is assumed.

Project Setup:

  • Create a new Dynamic Web project in Eclipse.
  • Add the following JARs from your Spring and CXF downloads into the lib folder:
spring-asm-3.0.6.RELEASE.jar
spring-beans-3.0.6.RELEASE.jar
spring-context-3.0.6.RELEASE.jar
spring-context-support-3.0.6.RELEASE.jar
spring-core-3.0.6.RELEASE.jar
spring-web-3.0.6.RELEASE.jar
commons-logging-1.1.1.jar
wsdl4j-1.6.2.jar
wss4j-1.6.3.jar
xml-resolver-1.2.jar
xmlschema-core-2.0.1.jar
xmlsec-1.4.5.jar
jaxrpc.jar
saaj.jar
saaj-api-1.3.jar
cxf-2.5.0.jar
bcprov-jdk16-146.jar
serializer-2.7.1.jar
xalan-2.7.1.jar
dom4j-1.6.1.jar

When creating web services, two approaches are possible: Contract-first and Contract-last. In contract-first, you define the service contract in WSDL (Web Service Definition Language) first, and then implement your service to the contract. This approach is better suited for newer projects. However, since the project I was working on already had the Java code written, the contract-last approach was a better choice, which is also what I will be explaining here.

The Server:

First up is our Java class, GreetingService. The service defines a single method, greet. It accepts a username and returns a greeting to the caller as a String (I know, I apologize for my lack of imagination).

package test;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
/**
 *
 * @author JD
 *
 */
@WebService(name = "GreetingService", targetNamespace = "http://test/")
public class GreetingService {
	@WebMethod
	public @WebResult(name = "greeting")
	String greet(@WebParam(name = "user") String user) {
		return "Hi there, " + user + "!";
	}
}

As you can see, this is a POJO with some annotations sprinkled in. The only required annotation would be @WebService.
Everything else simply assists in generating cleaner WSDL. For example, without the @WebParam annotation, the names of method arguments would be replaced with arg0, etc, making life a little difficult for a client.

Next up is our Spring configuration file, which will contain instructions for CXF to expose our service as a web service. Save this as a file named cxfContext.xml and put it in the WEB-INF directory.

<?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:soap="http://cxf.apache.org/bindings/soap" xmlns:cxf="http://cxf.apache.org/core"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
         http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd
         http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
	<!-- Interceptors -->
	<bean id="logIn" class="org.apache.cxf.interceptor.LoggingInInterceptor" />
	<bean id="logOut" class="org.apache.cxf.interceptor.LoggingOutInterceptor" />
	<!-- SAAJ Interceptor needs to be explicitly declared only in CXF 2.0.x -->
	<bean id="saajIn" class="org.apache.cxf.binding.soap.saaj.SAAJInInterceptor" />
	<bean id="wss4jIn" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
		<constructor-arg>
			<map>
				<entry key="action" value="UsernameToken Timestamp" />
				<entry key="passwordType" value="PasswordText" />
			</map>
		</constructor-arg>
	</bean>
	<!-- The CXF Bus -->
	<cxf:bus>
		<cxf:features>
			<cxf:logging />
		</cxf:features>
		<cxf:properties>
			<entry key="ws-security.validate.token" value="false" />
			<entry key="ws-security.ut.no-callbacks" value="true" />
			<entry key="ws-security.ut.validator" value="test.UTValidator" />
		</cxf:properties>
		<cxf:inInterceptors>
			<ref bean="logIn" />
		</cxf:inInterceptors>
		<cxf:outInterceptors>
			<ref bean="logOut" />
		</cxf:outInterceptors>
		<cxf:inFaultInterceptors>
			<ref bean="logIn" />
		</cxf:inFaultInterceptors>
		<cxf:outFaultInterceptors>
			<ref bean="logOut" />
		</cxf:outFaultInterceptors>
	</cxf:bus>
	<!-- End points -->
	<jaxws:endpoint id="greeter" serviceName="GreetingService"
		implementor="test.GreetingService" address="/">
		<!-- wsdlLocation="WEB-INF/wsdl/library.wsdl" This will be generated automcatically
			when not specified -->
		<jaxws:inInterceptors>
			<ref bean="saajIn" />
			<ref bean="wss4jIn" />
		</jaxws:inInterceptors>
	</jaxws:endpoint>
</beans>

Lines 10 – 21 define the required interceptors for implementing WS-Security via the WSS4JInInterceptor and for logging. Line 50 defines the end point. Details on the attributes can be found here Notice how everything can be configured in a very Spring-like way thanks to the custom namespaces. Also note how common properties for all endpoints, such as logging interceptors and properties can be defined in the bus and don’t have to be repeated for each endpoint.

The

<cxf:properties>

element specifies a set of properties for the bus. Here we are saying we will be handling authentication ourselves, via ws-security.ut.validator key in the UTValidator class, and that no password callback handlers are required by setting ws-security.validate.token to true. If you use a different password scheme other than PasswordText, like PasswordDigest, you would have to set the plain text password from the digest in the password callback class. Note that the properties can also be overridden per endpoint using the

<jaxws:properties>

child element of

<jaxws:endpoint>

.

The authentication method we have chosen here is UserNameToken, which validates a username and password provided by the client, and Timestamp. While this is sufficient for development and testing, this needs to be beefed up by either encrypting the transport(https) or by using security tokens in production.

The UTValidator is simply a subclass of CXF’s UsernameTokenValdator, and overrides the verifyPlaintextPassword method to provide custom authentication. In a real world application, an authentication service such as one that validates the credentials provided against a directory such as LDAP can be used. If validation fails, this method must throw a WSSecurityException with the error code (the first constructor argument) set as WSSecurityException.FAILURE.

package test;
import org.apache.ws.security.WSSecurityException;
import org.apache.ws.security.handler.RequestData;
import org.apache.ws.security.message.token.UsernameToken;
import org.apache.ws.security.validate.UsernameTokenValidator;
public class UTValidator extends UsernameTokenValidator {
	@Override
	protected void verifyPlaintextPassword(UsernameToken usernameToken,
			RequestData data) throws WSSecurityException {
		// usernameToken.getName(), usernameToken.getPassword(), authenticate against
		// directory store or database. Do nothing if successful. Throw
		// throw new WSSecurityException(WSSecurityException.FAILED_AUTHENTICATION); on failure.
	}
}

The final configuration on the server side is the web.xml file, which will initialize the Spring context, pass in our own Spring context as well as a couple from CXF (which are in its own jar files) to the ContextLoaderListener, and finally setup the CXF servlet which will handle all incoming requests for our service.

<?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" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
	version="3.0">
	<display-name>jax-ws-spring-test</display-name>
	<context-param>
		<!-- Spring contexts -->
		<param-name>contextConfigLocation</param-name>
		<param-value>
			classpath:META-INF/cxf/cxf.xml
      		        classpath:META-INF/cxf/cxf-servlet.xml
      		        /WEB-INF/cxfContext.xml
		</param-value>
	</context-param>
	<!-- Listener which bootstraps the Spring config files -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	<!-- The CXF Servlet -->
	<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>/*</url-pattern>
	</servlet-mapping>
</web-app>

And thats it! Now if you were to fire up your server, package the project as a war and deploy it on the server, your web service should be up and running. You can verify this by navigation to http://localhost:8080//greetingservice?wsdl on your browser to view the WSDL that is automatically generated by CXF based on the JAX-WS annotations defined in your service class.

The Client:

To test the service, create a second Dynamic web project, and use the same build path as the previous project.

What we will need to create a working client to test the service:

  1. The service interface which will be proxied by Spring.
  2. A spring context configuration file to define the jaxws client proxy for the service.
  3. A password callback handler which will supply the authentication token when invoking the service
  4. A simple spring bean to test the proxy and invoke the greet operation.

The service interface can be generated from the WSDL either by CXF’s wsdl2java tool or Java 6′s wsimport utility. The source below was generated by wsimport when invoked as follows:

wsimport -d . -keep http://localhost:8080/jax-ws-spring-test/greetingservice?wsdl
package test;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.xml.ws.RequestWrapper;
import javax.xml.ws.ResponseWrapper;
/**
 * This class was generated by the JAX-WS RI.
 * JAX-WS RI 2.1.6 in JDK 6
 * Generated source version: 2.1
 *
 */
@WebService(name = "GreetingService", targetNamespace = "http://test/")
public interface GreetingService {
    /**
     *
     * @param user
     * @return
     *     returns java.lang.String
     */
    @WebMethod
    @WebResult(name = "greeting", targetNamespace = "")
    @RequestWrapper(localName = "greet", targetNamespace = "http://test/", className = "test.Greet")
    @ResponseWrapper(localName = "greetResponse", targetNamespace = "http://test/", className = "test.GreetResponse")
    public String greet(
        @WebParam(name = "user", targetNamespace = "")
        String user);
}

The client Spring context contains the client proxy definition and is shown next:

<?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:soap="http://cxf.apache.org/bindings/soap" xmlns:cxf="http://cxf.apache.org/core"
	xsi:schemaLocation="
         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
         http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd
         http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
	<!-- Interceptors -->
	<bean id="logIn" class="org.apache.cxf.interceptor.LoggingInInterceptor" />
	<bean id="logOut" class="org.apache.cxf.interceptor.LoggingOutInterceptor" />
	<bean id="saajOut" class="org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor" />
	<bean id="wss4jOut" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
		<constructor-arg>
			<map>
				<entry key="action" value="UsernameToken Timestamp" />
				<entry key="user" value="ws-client" />
				<entry key="passwordType" value="PasswordText" />
				<entry key="passwordCallbackClass" value="test.PWCallbackHandler" />
			</map>
		</constructor-arg>
	</bean>
	<!-- The CXF Bus -->
	<cxf:bus>
		<cxf:features>
			<cxf:logging />
		</cxf:features>
		<cxf:inInterceptors>
			<ref bean="logIn" />
		</cxf:inInterceptors>
		<cxf:outInterceptors>
			<ref bean="logOut" />
		</cxf:outInterceptors>
		<cxf:inFaultInterceptors>
			<ref bean="logIn" />
		</cxf:inFaultInterceptors>
		<cxf:outFaultInterceptors>
			<ref bean="logOut" />
		</cxf:outFaultInterceptors>
	</cxf:bus>
	<!-- Client Proxies -->
	<jaxws:client
		name="{http://www.springframework.org/schema/beans}GreetingService"
		address="http://localhost:8080/<your_project_name>/greetingservice?wsdl"
		serviceClass="test.GreetingService"
		wsdlLocation="http://localhost:8080/<your_project_name>/greetingservice?wsdl"
		serviceName="GreetingService" endpointName="GreetingServicePort">
		<jaxws:outInterceptors>
			<ref bean="saajOut" />
			<ref bean="wss4jOut" />
		</jaxws:outInterceptors>
	</jaxws:client>
</beans>

Make sure you replace with the name of your project in the snippet above. Worthy of note is how the InInterceptors on the Server side are mirrored by OutInterceptors on the client side.

The PWCallbackHandler is an implementation of a CallbackHandler, and is responsible for providing the password to be encoded in the soap request header. Note that by calling the setIdentifier method, a different username can also be set. This allows for specifying different credentials per request. In a real world client, these values would probably come from a Session variable or from a SessionContextHolder.

package test;
import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.apache.ws.security.WSPasswordCallback;
public class PWCallbackHandler implements CallbackHandler {
	@Override
	public void handle(Callback[] callbacks) throws IOException,
			UnsupportedCallbackException {
		WSPasswordCallback c = (WSPasswordCallback) callbacks[0];
		// in a real world client these would probably come from SecurityContextHolder
		// or some session object
		c.setIdentifier("jd");
		c.setPassword("secret");
	}
}

The Web.xml will contain the same content as the one from the server project.

Finally, we create a spring bean which will have the proxy injected, and will then call the greet method on the service:

package test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@Service
@Qualifier("greetingServiceClient")
public class GreetingServiceClient {
	@Autowired
	private GreetingService gs;
	@Scheduled(cron="0 m h * * ?")
	public void testService() {
		try {
			System.out.println(gs.greet("Jeshurun"));;
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

Adjust the cron task by replacing the h with the hour of your current time, and m with two minutes plus your current time’s minute.

Deploying the project on the server, and waiting for the cron to kick in should print “Hi there, Jeshurun!”. You can even deploy the client on a separate machine to make it even more awesome.

To sum things up, lets see how CXF stacks up against other popular stacks:

  1. Easy Spring integration by using custom namespaces.
  2. Modular, lets you pick and choose eactly the features you need.
  3. Setting up security is relatively easy.
  4. Uses standard JAX-WS annotations, so your services can be ported easily to a different stack if needed.
  5. Way better documentation compared to Metro, although not on par with Spring (but definitely getting there).
  6. Performance is comparable to Metro and is faster than Axis.
  7. Can be integrated right into your project and doesn’t force you to deploy each service as a separate war.

Some of the cons for me, which are making me think twice about using it:

  1. There seems to be no easy way of defining the jaxws:endpoint or jaxws:client as an abstract bean and create beans which inherit common properties. Because of this, properties such as interceptors have to be redefined for every endpoint or client definition, even when they are exactly the same. This could make your configuration xml lengthy and error-prone in larger projects.
  2. Can’t be configured to automagically detect POJOs annotated with @WebService and publish them, like Spring’s SimpleJaxWsServiceExporter.

Leave a Reply

Your email address will not be published. Required fields are marked *


1 − = zero

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>