Quantcast
RSS Entries RSS
RSS Subscribe by Email

Web Services Tutorial with Apache CXF

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.

Tags: , , , ,

78 Comments »

  1. Espen Schulstad said,

    February 5, 2008 at 2:37 am

    Thx, great article. Got the juices flowing in the cxf-grinder in no time ;)

  2. Ben said,

    February 5, 2008 at 8:32 am

    Thank you Espen. I’m so happy to hear the CXF tutorial was helpful. As a new blogger, I’m always open to suggestions, so feel free to let me know if there was anything that wasn’t clear enough.

    -Ben

  3. Alessandro said,

    February 12, 2008 at 4:00 pm

    Hi Ben
    Even if I’m a newbie at webservices I found your tutorial very useful, thanks.
    Anyway I have a doubt: everything works just fine if I had a method returning, for example a String. If I try to make it returning an Object (for example an Employee) I get a message about the marshalling process of my bean (Employee).
    Any advice?
    Thanks, Alessandro

  4. Ben said,

    February 12, 2008 at 6:07 pm

    Alessandro,
    Glad to hear you’re part way there!
    It sounds to me like you’ve encountered a problem serializing your Object to XML. CXF and other web services take your object, turn it into XML, and then transmit it over HTTP as XML. Since it is having trouble turning your Object into XML, there is probably some object member that is not a simple data type or composed of them. You can return your custom Objects composed of standard Java classes as you can see from my example bean above. If your class has something like say a File or BufferedImage in it then it’s a lot less likely that JAXB will know how to turn that into XML. I’d comment out anything that’s not a standard data element and see if it works. Adding them back in one at a time, you should be able to see which cause you problems. If that’s what’s wrong then a good place to start in the documentation would be http://cwiki.apache.org/CXF20DOC/databindings.html If you’re really unable to turn your objects into XML, then CXF can handle other bindings like CORBA, but I’m not sure that any of that is out of the box yet. I think JAXB and Aegis are the two supported right now with XMLBeans, Castor, and JiBX to come in CXF 2.1. There’s some integration with Yoko I believe, but that’s not an area I’d be able to help with.

  5. Alessandro said,

    February 13, 2008 at 6:16 am

    Ben,
    thanks for your very quick reply.
    Yes, my problem is that CXF can’t serialize my class to XML, and the error I get is
    ‘org.apache.cxf.interceptor.Fault: Marshalling Error: SdrProcess is not known to this context’.
    I’m tryin’ to make CXF marshalling automatically by putting annotation in my class, like follows:

    @XmlAccessorType(XmlAccessType.FIELD)
    @XmlType(name = "SdrProcess")
    public class SdrProcess implements Serializable{
    
    	@XmlAttribute
    	private static final long serialVersionUID = -8267456479467102760L;
    	@XmlAttribute
    	private Long id;
    	@XmlAttribute
    	private String name;
    
    	public SdrProcess(Long id, String name){
    		this.id = id;
    		this.name = name;
    	}
    
    	public Long getId() {
    		return id;
    	}
    
    	public String getName() {
    		return name;
    	}
    

    I’m expecting CXF to do the marshalling by itself, am I right or am I missing some configuration?
    I’m not putting the class full qualified name (w/package). is it maybe that?

    I’m not sure about the link you posted about the wiki: it doesn’t work, are you sure it is that?

    Thanks a lot!
    Alessandro

  6. dom said,

    February 14, 2008 at 10:58 am

    is it possible to post the source up here?

  7. Ben said,

    February 14, 2008 at 2:58 pm

    Dom,
    Which source specifically do you want to see? Basically all of the source code is already in the post.

  8. Ben said,

    February 14, 2008 at 3:53 pm

    Alessandro,
    You DO need the fully qualified class name of your service implementation in the cxf.xml Spring configuration file. You also, DO need the fully qualified class name of your service interface when specifying the endpoint interface in your service implementation.
    You don’t need any annotations in your bean for CXF. It will work correctly with just a POJO (Plain Old Java Object). However, I don’t think CXF will like having only getters. If I remember correctly, you need to have matching getters and setters.
    Also, I corrected the link in my previous comment.

    -Ben

  9. dom said,

    February 15, 2008 at 9:48 am

    Hi ben , just the employees stuff, i am getting an error saying : org.apache.xbean.propertyeditor.PropertyEditorException: Value is not an instance of Map

    thanks for spending the time to write all of this.

    Dom

  10. Ben said,

    February 15, 2008 at 2:13 pm

    Dom,
    I added the code for my bean to the post. As I stated, it really is just a simple POJO.
    I’m not sure where you are getting the error you mentioned, but I’d make sure whatever method you call in the stack trace that is expecting a Map is actually being passed a class which implements the Map interface such as a HashMap.

    -Ben

  11. dom said,

    February 18, 2008 at 4:32 am

    Would you be able to post your DAO up as well? Sorry i am very new to this and i am just trying to get a working example. My lastest error that i am getting is : Non-default namespace can not map to empty URI (as per Namespace 1.0 # 2) in XML 1.0 documents. Any thoughts would be geat :-)

  12. dom said,

    February 18, 2008 at 5:35 am

    here what i have done for my DAO.

    
    public class EmployeeDAO{
    
    	private Employee employee = new Employee();
    
    	public  EmployeeDAO() {
    		employee.setFirstName("john");
    		employee.setLastName("smith");
    		Set privileges = new HashSet();
    		privileges.add("tea boy");
    		employee.setPrivileges(privileges);
    		employee.setGid("0223938");
    	}
    
    	public Employee getEmployee(String gid) {
    		return employee;
    	}
    
    }
  13. Ben said,

    February 19, 2008 at 11:10 am

    Hi Dom,
    I’ve edited a few lines in your comment to fix some null pointer exceptions that would occur if your code was run so as not to confuse other people that might see it. You might want to update your DAO using the corrections.

    -Ben

  14. Apache CXF Tutorial - WS-Security with Spring | Lumidant said,

    February 19, 2008 at 5:17 pm

    [...] cover adding an authentication component to your web service though WS-Security. If you need an overview of how to setup CXF then you may find our previous tutorial helpful. Another helpful resource is CXF’s own [...]

  15. Digger said,

    February 20, 2008 at 12:51 am

    I’m getting exactly the same thing as Alessandro regarding marshalling. The root cause is

    Caused by: javax.xml.bind.JAXBException: com.xyz.SimpleComment is not known to this context
    at com.sun.xml.bind.v2.runtime.JAXBContextImpl.getBeanInfo(JAXBContextImpl.java:538)
    at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl$1.serializeBody(ElementBeanInfoImpl.java:82)
    … 37 more

    I get it when trying to return a List of complex types (in this case, a List of SimpleComments). If I return a List of Strings then there’s no problem. I tried Ben’s suggestion above regarding removing properties from the SimpleComment–in fact I removed everything except a single String property–but no luck. Just curious whether other people are running into this when trying to return a complex type.

  16. Ben said,

    February 20, 2008 at 3:44 pm

    I would think your best bet is to look into other data bindings as I mentioned earlier. I tried to get Aegis working today, but didn’t have any luck. I’ll try a bit more and update you if I get it working. In the meantime, would you be able to use a Set instead of a List? A Set worked for me using JAXB and unless the ordering matters to you that might be a quick fix.

  17. Sadashiv said,

    February 20, 2008 at 4:01 pm

    Hi Ben,

    Thanks for the good tutorial. I wanted to know how can we do logging of soap request and response using CXF. using configuration files. I used following url ( http://cwiki.apache.org/CXF20DOC/configuration.html ) to do it through configuration but it gives me following exception. Please let me know what iam doing wrong

    INFO: Load the bus with application context
    20 Feb 2008 20:27:19,653 ERROR [[/MyCXFWebservice],] StandardWrapper.Throwable
    java.lang.NullPointerException
    at org.apache.cxf.transport.servlet.CXFServlet.loadSpringBus(CXFServlet.java:104)
    at org.apache.cxf.transport.servlet.CXFServlet.loadBus(CXFServlet.java:63)
    at org.apache.cxf.transport.servlet.AbstractCXFServlet.init(AbstractCXFServlet.java:86)
    at org.apache.catalina.core.StandardWrapper.loadServlet(StandardWrapper.java:1139)
    at org.apache.catalina.core.StandardWrapper.load(StandardWrapper.java:966)
    at org.apache.catalina.core.StandardContext.loadOnStartup(StandardContext.java:3956)
    at org.apache.catalina.core.StandardContext.start(StandardContext.java:4230)
    at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:760)
    at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:740)
    at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:544)
    at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:825)
    at org.apache.catalina.startup.HostConfig.deployWARs(HostConfig.java:714)
    at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:490)
    at org.apache.catalina.startup.HostConfig.start(HostConfig.java:1138)
    at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:311)
    at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:120)
    at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1022)
    at org.apache.catalina.core.StandardHost.start(StandardHost.java:736)
    at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1014)
    at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:443)
    at org.apache.catalina.core.StandardService.start(StandardService.java:448)
    at org.apache.catalina.core.StandardServer.start(StandardServer.java:700)
    at org.apache.catalina.startup.Catalina.start(Catalina.java:552)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:585)
    at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:295)
    at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:433)

  18. Ben said,

    February 20, 2008 at 4:55 pm

    I found someone else on the mailing list getting your same error. Here was his fix: “I just tried copying the xml files I’m importing in spring.xml into my WEB-INF/classes directory. This fixed the NullPointerException (not sure why it couldn’t find them with the default imports on the classpath).” So I’d check if you’re having a classpath issue.

    If you’d like another example, you can see that I added logging in a similar manner in my Spring WS-Security post: http://www.lumidant.com/blog/apache-cxf-tutorial-ws-security-with-spring/

  19. Digger said,

    February 20, 2008 at 5:38 pm

    Thanks Ben for the suggestions. I will give the Set and Aegis ideas a try and see what happens. If I have any luck I’ll report back.

  20. Sadashiv said,

    February 20, 2008 at 7:01 pm

    Hi Ben,

    Thanks for your response i used ur example and it worked fine but i cannot see the <soap> message in the log file do i need to add anything in order to view ?

    thanks in advance
    Sada

  21. Digger said,

    February 20, 2008 at 11:56 pm

    Ben–your suggestion to use Aegis worked great. Thanks dude.

  22. Digger said,

    February 21, 2008 at 12:00 am

    p.s. You mentioned that you didn’t get Aegis working. Let me know if you need help with that. Here’s what I put in my Spring config:


  23. Sponsors

  24. Archives