Maximum Zeal ~ Emphatic prose on indulged fascinations

REST service example using CXF 2.2, JAX-RS 1.0, JAXB and Spring

In the modern day transition from a service oriented architecture to a resource oriented architecture the tools are only just catching up. Here’s a very brief and simple example of a REST service using CXF 2.2-SNAPSHOT, JAX-RS 1.0 and Spring 2.5.6. A few key points to note are as below.

  • The maven pom file imports a cxf jax-rs bundle which in turn imports all prerequisites – this saves individual cxf imports.
  • The getUsers() call demonstrates a rather unorthodox use of a dto as a replacement for a standard collection due to the fact that jaxb cannot handle collections as first class citizens.
  • A snapshot version (2.2) is being used of CXF which supports the final version of JAX-RS (1.0) whereas CXF 2.1.x supports only JAX-RS 0.8.
  • Annotations have been applied to the implementation but can be applied to the interface.
  • Annotations are inherited from interface to implementation and from class level to method level and can be overridden at method level.

Maven dependencies (pom.xml)

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cxf-rest-example</groupId>
    <artifactId>cxf-rest-example</artifactId>
    <version>1.0</version>
    <packaging>war</packaging>
    <name>cxf-rest-example</name>
    <description>cxf-rest-example</description>

    <organization>
        <name>dhruba.name</name>
        <url>http://dhruba.name</url>
    </organization>

    <build>
        <finalName>cxf-rest-example</finalName>
        <plugins>

            <!-- enable java 6 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>

            <!-- skip tests -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.4.2</version>
                <configuration>
                    <skipTests>true</skipTests>
                </configuration>
            </plugin>

            <!-- automatically clean before build -->
            <plugin>
                <artifactId>maven-clean-plugin</artifactId>
                <executions>
                    <execution>
                        <id>auto-clean</id>
                        <phase>validate</phase>
                        <goals>
                            <goal>clean</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

        </plugins>
    </build>

    <dependencies>

        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-bundle-jaxrs</artifactId>
            <version>2.2-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>2.5.6</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>2.5.6</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>2.5.6</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>2.5.6</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.5</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.15</version>
            <exclusions>
                <exclusion>
                    <groupId>com.sun.jmx</groupId>
                    <artifactId>jmxri</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>javax.jms</groupId>
                    <artifactId>jms</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.sun.jdmk</groupId>
                    <artifactId>jmxtools</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

    </dependencies>

    <repositories>
        <repository>
            <id>java.net.2</id>
            <name>Java Net 2 Repository</name>
            <url>http://download.java.net/maven/2</url>
        </repository>
        <repository>
            <id>apache.incubating</id>
            <name>Apache Incubating Repository</name>
            <url>http://people.apache.org/repo/m2-incubating-repository</url>
        </repository>
        <repository>
            <id>apache.snapshot</id>
            <name>Apache Snapshot Repository</name>
            <url>http://people.apache.org/repo/m2-snapshot-repository</url>
        </repository>
    </repositories>

</project>

Container descriptor file (web.xml)

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

    <display-name>CXF REST Example</display-name>
    <description>CXF REST Example</description>

    <context-param>
        <param-name>webAppRootKey</param-name>
        <param-value>cxf.rest.example.root</param-value>
    </context-param>

    <context-param>
        <param-name>log4jConfigLocation</param-name>
        <param-value>/WEB-INF/classes/log4j.properties</param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/deploy-context.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>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>CXFServlet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

</web-app>

Spring context file (deploy-context.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:jaxrs="http://cxf.apache.org/jaxrs"
    xsi:schemaLocation="

http://www.springframework.org/schema/beans


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


http://cxf.apache.org/jaxrs

http://cxf.apache.org/schemas/jaxrs.xsd"
    default-lazy-init="false">

    <import resource="classpath:META-INF/cxf/cxf.xml" />
    <import resource="classpath:META-INF/cxf/cxf-extension-jaxrs-binding.xml" />
    <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

    <jaxrs:server id="myService" address="/">
        <jaxrs:serviceBeans>
            <ref bean="serviceImpl" />
        </jaxrs:serviceBeans>
        <jaxrs:extensionMappings>
            <entry key="xml" value="application/xml" />
        </jaxrs:extensionMappings>
    </jaxrs:server>

    <bean id="serviceImpl" class="service.ServiceImpl" />

</beans>

User pojo

package pojo;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "user")
public class User {

    private Integer id;

    private String  name;

    public User() {
    }

    public User(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return String.format("{id=%s,name=%s}", id, name);
    }

}

User DTO

package pojo;

import java.util.Collection;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class UserCollection {

    private Collection users;

    public UserCollection() {
    }

    public UserCollection(Collection users) {
        this.users = users;
    }

    @XmlElement(name="user")
    @XmlElementWrapper(name="users")
    public Collection getUsers() {
        return users;
    }

}

Service interface

package service;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Response;

import pojo.User;
import pojo.UserCollection;

public interface ServiceDefn {

    UserCollection getUsers();

    User getCustomer(Integer id);

    Response getBadRequest();

}

Service implementation

package service;

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

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

import pojo.User;
import pojo.UserCollection;

@Path("/myservice/")
@Produces("application/xml")
public class ServiceImpl implements ServiceDefn {

    private static Map users = new HashMap();

    static {
        users.put(1, new User(1, "foo"));
        users.put(2, new User(2, "bar"));
        users.put(3, new User(3, "baz"));
    }

    public ServiceImpl() {
    }

    @GET
    @Path("/users")
    @Override
    public UserCollection getUsers() {
        return new UserCollection(users.values());
    }

    @GET
    @Path("/user/{id}")
    @Override
    public User getUser(@PathParam("id") Integer id) {
        return users.get(id);
    }

    @GET
    @Path("/users/bad")
    @Override
    public Response getBadRequest() {
        return Response.status(Status.BAD_REQUEST).build();
    }

}

REST Service

The following urls should now show the expected REST output.

  • http://localhost:8080/cxf-rest-example/myservice/users/
  • http://localhost:8080/cxf-rest-example/myservice/user/1
Users REST output
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<userCollection>
    <users>
        <user>
            <id>1</id>
            <name>foo</name>
        </user>
        <user>
            <id>2</id>
            <name>bar</name>
        </user>
        <user>
            <id>3</id>
            <name>baz</name>
        </user>
    </users>
</userCollection>
Single user REST output
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<user>
    <id>1</id>
    <name>foo</name>
</user>

Discussion

It’s a little rough around the edges (the bad request call doesn’t respond in the expected fashion) but the REST service works using CXF 2.2-SNAPSHOT, JAXB 2.1.7, JAX-RS 1.0 and Spring 2.5.6. CXF can do a lot more but here I’ve created only a simple skeleton project. If you are using CXF 2.1 which supports JAX-RS 0.8 then this example will use @ProduceMime and @ConsumeMime instead of @Produces and @Consumes – a full migration list is provided by CXF. When I get a chance I’ll create an equivalent using Spring 3.0 – they’ve just released M1 which I’m hoping has REST.

The eclipse project is available for download. Build using mvn package. The maven build file automatically cleans before build and skips tests. To compile on Mac OS X you must tell maven where Java lives.

Update: Made correction – removed all annotations from interface as not necessary for the purposes of this example.

Update2: [15/12/2008] Made user collection rest xml a bit more semantic by fixing singular and plurals.

Questions

If anyone knows how to enable xml formatting in the jaxb marshalling output when using cxf please let me know. I know how to do this in jaxb but because the use of jaxb and its marshaller is embedded within cxf it is not immediately clear how this would be done.

Has this been helpful to you? Please say so. What other topics would you like to see? Let me know and I’ll do my best.

Standard Entity Providers

Update3 [25/12/2008]: In comment #1 to this post Victor asked why it is not necessary to implement MessageBodyReader or MessageBodyWriter and annotate with @Provider which are types from the api of the jax-rs 1.0 specification. In order to answer this, first, I quote the relevant section (4.2.4) of the jax-rs 1.0 specification. The CXF manual also sheds much light on what it supports and how.

An implementation MUST include pre-packaged MessageBodyReader and MessageBodyWriter implementations for the following Java and media type combinations:

  • byte[] All media types (*/*).
  • java.lang.String All media types (*/*).
  • java.io.InputStream All media types (*/*).
  • java.io.Reader All media types (*/*).
  • java.io.File All media types (*/*).
  • javax.activation.DataSource All media types (*/*).
  • javax.xml.transform.Source XML types (text/xml, application/xml and application/*+xml).
  • javax.xml.bind.JAXBElement and application-supplied JAXB classes XML media types (text/xml, application/xml and application/*+xml).
  • MultivaluedMap Form content (application/x-www-form-urlencoded).
  • StreamingOutput All media types (*/*), MessageBodyWriter only.

As the spec says above a jax-rs implementation must provide reader and writer implementations for the above types out of the box and ready to use which cxf does provide. It is only necessary to implement a reader or writer if you want to support a custom type or would like to provide a more efficient implementation of an existing type but luckily most of the work has already been done for you. If you look at the cxf source you’ll see provider implementations for the above minimum set of types already as below.

$ ls -1 Downloads/apache-cxf-2.2-SNAPSHOT-src/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/
AbstractAegisProvider.java
AbstractConfigurableProvider.java
AbstractJAXBProvider.java
AegisElementProvider.java
AtomEntryProvider.java
AtomFeedProvider.java
BinaryDataProvider.java
FormEncodingReaderProvider.java
FormValidator.java
JAXBElementProvider.java
JSONProvider.java
Messages.properties
PrimitiveTextProvider.java
ProviderFactory.java
SourceProvider.java
StringProvider.java
XMLBeanStreamSerializer.java
XMLBeansElementProvider.java
XMLBeansJSONProvider.java

With particular reference to jaxb as a data binding technology the specification also mentions the following.

The implementation-supplied entity provider(s) for javax.xml.bind.JAXBElement and application supplied JAXB classes MUST use JAXBContext instances provided by application-supplied context resolvers, see section 4.3. If an application does not supply a JAXBContext for a particular type, the implementation-supplied entity provider MUST use its own default context instead. […]. An implementation MUST support application-provided entity providers and MUST use those in preference to its own pre-packaged providers when either could handle the same request.

The crux of it is that if you supply a JAXBContextResolver then CXF will use it otherwise it will create its own. An example JAXBContextResolver is provided below.

package context;

import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;

@Provider
public class JAXBContextResolver implements ContextResolver {

    private final static String      annotatedPackages = "pojo";

    private final static JAXBContext context           = initContext();

    private static JAXBContext initContext() {
        JAXBContext context = null;
        try {
            context = JAXBContext.newInstance(annotatedPackages);
        } catch (JAXBException e) {
            throw new RuntimeException(e);
        }
        return context;
    }

    @Override
    public JAXBContext getContext(Class clazz) {
        return context;
    }

}

The specification mentions that an implementation must detect automatically any classes which are annotated as providers. However CXF does not yet support it and expresses a definite preference for explicit definition of providers as below.

<?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:jaxrs="http://cxf.apache.org/jaxrs"
    xsi:schemaLocation="

http://www.springframework.org/schema/beans


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


http://cxf.apache.org/jaxrs

http://cxf.apache.org/schemas/jaxrs.xsd"
    default-lazy-init="false">

    <import resource="classpath:META-INF/cxf/cxf.xml" />
    <import resource="classpath:META-INF/cxf/cxf-extension-jaxrs-binding.xml" />
    <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

    <jaxrs:server id="myService" address="/">
        <jaxrs:serviceBeans>
            <ref bean="serviceImpl" />
        </jaxrs:serviceBeans>
        <jaxrs:providers>
            <ref bean="jaxbContextResolver" />
        </jaxrs:providers>
    </jaxrs:server>

    <bean id="jaxbContextResolver" class="context.JAXBContextResolver" />

    <bean id="serviceImpl" class="service.ServiceImpl" />

</beans>

However it seems that CXF does not in fact recognise the provider even with this configuration and I’m still trying to work out why. It would be nice to provide one’s own context and one’s own marshaller properties. This is left as a task for the reader for the time being and I’ll also continue to look into it.

Update [2/7/2011]: It’s nice to see this featured on StackOverflow.

23 Responses to REST service example using CXF 2.2, JAX-RS 1.0, JAXB and Spring

  1. Thanks for the sample. I have a question: Why isn’t there any “@Provider” class (those who implements MessageBodyReader or MessageBodyWriter). Does CXF build them for us?
    Regards

  2. On User DTO I had to change @XmlElement(name=”user”) for @XmlElement(name=”user”, type=User.class). Otherwise, It won’t work for me.
    Regards

  3. Another comment. On User DTO I had to add the setter for the users collection. Otherwise, the unmarshalling does not work.
    Regards

  4. Victor thanks for your comments and corrections. Please find my responses below.

    About comment #2 what problem/exception did you face? I had no problems with the original code. Were you trying to marshall or unmarshal or both? I have to confess I only needed and tested marshaling.

    About comment #3, yes, if you are unmarshalling then the object will need to be mutable. As I said above, for the purposes of the REST service, I only verified the marshalling side of it.

    About comment #1, this is a very good question indeed and one that I had to read the jax-rs 1.0 specification to get an insight into. Please see the end of the post above for my answer under the section ‘Standard Entity Providers’.

  5. Excellent post!
    I have a question: is there a way to implement a generic wrapper to return different types of List<>. I mean, if I have services returning things like List<User>, List<Product>, List<Whatever>, Do I have to implement a wrapper for each one?
    Thanks, and keep the good posting :)

  6. Hi JJ,

    Thanks for your kind words. From what I’ve seen so far creating a strongly typed wrapper e.g. a User wrapper is the only way that works with CXF and JAXB. If you were using JAXB alone then you could create one generic Collection wrapper (which caters for Set, List and Queue) and one generic Map wrapper (since Map doesn’t implement Collection) and the marshalling works fine. If you are using JAXB alone without CXF let me know and I can post the two generic wrappers I have. However it won’t marshal with CXF. I’m in the process of researching how to supply a custom JAXBContext to CXF. I have a feeling that if we can do that then CXF marshalling capabilities will be the same as for JAXB alone. The documented way of supplying a custom JAXBContext to CXF doesn’t work so it’s going to be a case of sending an email to the cxf-user mailing list.

    Cheers.

  7. 1) Your interface-implementation code is a little off. I copy+pasted some of the code to do a quick example run, and there was some little errors.

    Interface: User getCustomer(Integer id);
    Impl: public User getUser(@PathParam(“id”) Integer id)

    Need to change the interface, else you get a compile issue. It also would behoove you to add the enumeration to the users HashMap instance in the ServiceImpl class.

    2) Also, I was getting JAXB Marshalling context errors. Seems that in the UserCollection class, there is no reference to the pojo.User object. So, add the enumeration value to the Collection class, e.g.: private Collection users; (and references to it), and all should be worked out. The exception (from the server)

    javax.xml.bind.MarshalException
    – with linked exception:
    [javax.xml.bind.JAXBException: class pojo.User nor any of its super class is known to this context.]
    at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:331)

    etc. etc.

    This is using cxf-2.1.2 and jsr311-api-0.8 and spring-2.0.8

  8. Salam,

    could you plz give an exemple (a simple one) on how to combine JAX-RS and JAX-WS using ApacheCXF2.2+Spring2.5.6

    I’ve followed what was said in the ApachCXF UserGuide and only the WebServices using JAX-RS are working.

    I’m trying to figure it out but I think i’m going to need help on this!

    Thank you in advance

  9. Thanks a lot for this tutorial. I was able to get it to work to some degree. But when I changed the service implementation to scope=”request” to make it multi threaded it spat out a rather large Exception telling me that this is not supported when you don’t use DispatcherServlet. I haven’t gotten it to work with RequestContextListener or RequestContextFilter yet.

    Without that your webservice would be a singleton that will have to be secured for multiple threads, which is not what you’d typically want. Do you have any idea how you would have to modify your project to do that?

  10. Good day

    How configure CXF-WS webservice to consume CXF-RS Restful service using Spring. In order words is it possible to have CXF-WS as client to CXF-RS service. I will highly appreciate your help in this regard.

    Thanks
    Hasani

  11. Hi All,

    Thanks for the post !

    Please help me out how to deal with Date parameters in CXF with JAX-RS

    I am facing isses whether Date to be passed as @QueryParam.

    Any help would be appreciated

    Thank you

  12. Hi boss,
    now i am working with Jersy framework for Restful Json. i faced below problem. could u please help me out of this.

    is it possible to send custom object as a @Pathparam using get method and i need json as output using RESTFUL.

    I mean instead of sending all the parameters URI like —-> custom/1/aaa/addr shall we custom/pojo

    Client Side:

    class Pojo{
    int id=1;
    String name=”aaa”;
    String addr=”addr”;
    //setter & getters

    }

    Main class:

    public static void main(String… a)
    {
    Pojo pojo = new Pojo();

    System.out.println(service.path(“rest”).path(“custom/pojo”).accept(MediaType.APPLICATION_JSON).get(String.class));
    }

    server side:

    @Path(“/custom”)
    class Custom
    {
    @GET
    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    @Path(“{pojo}”)
    public Pojo getJsonOutput(@PathParam(“pojo”) Pojo p) {
    if(1==p.getId() && “aaa”.equals(p.getName()) && “addr”.equals(p.getAddr())

    return new Pojo();

    else

    return null;
    }
    }

    class Pojo{
    int id=1;
    String name=”aaa”;
    String addr=”addr”;
    String firstName=”aaa”;
    String lastName=”bbb”;
    String firstAddr=”ccc”;
    String lastAddr=”ddd”;
    String mobnumber=”123457896″;

    //Setters & Getters

    }

Leave a Reply