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

Filed under:  javaspring — Tags:  cxfjaxbjaxrsmavenrestroaservicespring — Dhruba
Bandopadhyay @ 00:46

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 JAXBContextinstances 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.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值