Web Services Tutorial with Apache CXF

02/01/2008

I created a web service today with CXF and wanted to share the steps it took to get it up and running in this quick tutorial. Apache CXF was created by the merger of the Celtix and XFire projects. I chose to implement my service in CXF because some colleagues had been using XFire and would likely want to upgrade at some point. I am using the latest version, which is 2.0.4. While the library itself seems to be of high quality, the documentation is still a work in progress. However, do not fret because this CXF tutorial will get you up and running in no time. I will be creating a simple web service that will allow the retrieval of employee information. The service will return this simple POJO (Plain Old Java Object) bean with matching getters and setters:

package com.company.auth.bean;

import java.io.Serializable;
import java.util.Set;

public class Employee implements Serializable {

	private static final long serialVersionUID = 1L;
	private String gid;
	private String lastName;
	private String firstName;
	private Set<String> privileges;

	public Employee() {}

	public Set<String> getPrivileges() {
		return privileges;
	}

	public void setPrivileges(Set<String> privileges) {
		this.privileges = privileges;
	}	

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public String getGid() {
		return gid;
	}

	public void setGid(String gid) {
		this.gid = gid;
	}

	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	public boolean isUserInRole(String role) {
		if(privileges == null) { return false; }
		else { return privileges.contains(role); }
	}

}

First off, you need to download Apache CXF and drop the necessary .jars in your WEB-INF/lib directory:

aopalliance-1.0.jar
commons-logging-1.1.jar
cxf-2.0-incubator.jar
geronimo-activation_1.1_spec-1.0-M1.jar (or Sun’s Activation jar)
geronimo-annotation_1.0_spec-1.1.jar (JSR 250)
geronimo-javamail_1.4_spec-1.0-M1.jar (or Sun’s JavaMail jar)
geronimo-servlet_2.5_spec-1.1-M1.jar (or Sun’s Servlet jar)
geronimo-ws-metadata_2.0_spec-1.1.1.jar (JSR 181)
jaxb-api-2.0.jar
jaxb-impl-2.0.5.jar
jaxws-api-2.0.jar
jetty-6.1.5.jar
jetty-util-6.1.5.jar
neethi-2.0.jar
saaj-api-1.3.jar
saaj-impl-1.3.jar
spring-core-2.0.4.jar
spring-beans-2.0.4.jar
spring-context-2.0.4.jar
spring-web-2.0.4.jar
stax-api-1.0.1.jar
wsdl4j-1.6.1.jar
wstx-asl-3.2.1.jar
XmlSchema-1.2.jar
xml-resolver-1.2.jar

The first thing which needed to be done was to create the service interface. The service interface defines which methods the web service client will be able to call. It’s pretty standard Java with just two JWS (Java Web Service) annotations thrown in:

package com.company.auth.service;

import javax.jws.WebService;
import javax.jws.WebParam;
import com.company.auth.bean.Employee;

@WebService
public interface AuthService {
    Employee getEmployee(@WebParam(name="gid") String gid);
}

The @WebParam annotation is in fact optional, but highly recommended since it will make like easier for the end consumers of your service. Without it, your parameter would be named arg0 making it less clear what parameters your service actually takes.

Implementing the actual service comes next:

package com.company.auth.service;

import javax.jws.WebService;

import com.company.auth.bean.Employee;
import com.company.auth.dao.EmployeeDAO;

@WebService(endpointInterface = "com.company.auth.service.AuthService", serviceName = "corporateAuthService")
public class AuthServiceImpl implements AuthService {

	public Employee getEmployee(String gid) {
		EmployeeDAO dao = new EmployeeDAO();
		return dao.getEmployee(gid);
	}

}

I then had to tell Spring (which is used by CXF) where to find my Java classes. I created the following cxf.xml file inside my package directory (com/company/auth/service):

<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:jaxws="http://cxf.apache.org/jaxws"
      xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans.xsd


http://cxf.apache.org/jaxws

 					http://cxf.apache.org/schemas/jaxws.xsd">

  <import resource="classpath:META-INF/cxf/cxf.xml" />
  <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml"/>
  <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
  <jaxws:endpoint id="auth"
                  implementor="com.company.auth.service.AuthServiceImpl"
                  address="/cxfAuth"/>
</beans>

Finally, I updated my WEB-INF/web.xml file to let CXF know where my cxf.xml file was and define the CXF servlet:

<?xml version="1.0"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
  <display-name>Auth Manager</display-name>
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:com/company/auth/service/cxf.xml</param-value>
  </context-param>
  <listener>
    <listener-class>
      org.springframework.web.context.ContextLoaderListener
    </listener-class>
  </listener>
  <servlet>
    <servlet-name>CXFServlet</servlet-name>
    <servlet-class>
        org.apache.cxf.transport.servlet.CXFServlet
    </servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>CXFServlet</servlet-name>
    <url-pattern>/services/*</url-pattern>
  </servlet-mapping>
</web-app>

The most frustrating portion of getting the CXF web service up and running was the following exception:
Invocation of init method failed; nested exception is java.lang.NoSuchMethodError: javax.jws.WebService.portName()Ljava/lang/String;

This is due to the fact that I am running WebLogic 9.2, which contains a library with an older version of JSR (Java Specification Request) 181. The quickest solution for me was to prepend geronimo-ws-metadata_2.0_spec-1.1.1.jar to the WebLogic classpath. This will likely not be the solution I choose to implement in the end, but it got me back up and running. It seemed I also had to clear my WebLogic cache for this fix to take effect. Because I often find instances where this seems necessary, I have created a Windows script to clear the Weblogic Cache. If you run into app server related issues, this was one area I found the Apache CXF docs to be helpful.

Also, if you are running Hibernate, you may encounter ASM incompatibilities between Hibernate’s CGLib and the version of ASM which CXF requires. But do not fret because this is easy enough to solve.

Once this was solved, the list of services was available. The context root for my web project is authManager, so my regular web index page is available at http://localhost:7001/authManager/. The list of web services is then automagically generated by CXF at http://localhost:7001/authManager/services/.

The final step is to build the client. This was very easy when compared to getting the server up and running because I was able to simply swipe the code from the Apache CXF documentation. So, without further ado:

package com.company.auth.client;

import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;

import com.company.auth.bean.Employee;
import com.company.auth.service.AuthService;

public final class Client {

    private Client() {
    } 

    public static void main(String args[]) throws Exception {

    	JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();

    	factory.getInInterceptors().add(new LoggingInInterceptor());
    	factory.getOutInterceptors().add(new LoggingOutInterceptor());
    	factory.setServiceClass(AuthService.class);
    	factory.setAddress("http://localhost:7001/authManager/services/cxfAuth");
    	AuthService client = (AuthService) factory.create();

    	Employee employee = client.getEmployee("0223938");
    	System.out.println("Server said: " + employee.getLastName() + ", " + employee.getFirstName());
    	System.exit(0);

    }

}

Wow! Wasn’t that cool? (Yes, I’m a dork :o) You should now be up and running with a CXF web service.

If you are looking for something to learn next then may I suggest our tutorial on adding security to your web service.  That tutorial will also show you how to setup the client using Spring, which you may find helpful as well.