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.
{ 17 comments… read them below or add one }
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
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
Another comment. On User DTO I had to add the setter for the users collection. Otherwise, the unmarshalling does not work.
Regards
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’.
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 :)
Hi JJ,
Thanks for your kind works. 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.
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
Hi tmarthal,
I am facing the same issue, how can you fix it?
Thanks.
I can resolve it by changing @XmlElement(name=”user”) for @XmlElement(name=”user”, type=User.class) as Victor’s comment.
Regards.
Hah, that last comment removed lots of > and < signs (“greater than” and “less than” signs, if those html codes get filtered).
I have a question about the URL. Is it possible in the REST service to parse url’s like:
http://localhost:8080/cxf-rest-example/myservice/?user=1
And going further, what about something like
http://localhost:8080/cxf-rest-example/myservice/?user=1&location=home
where “location” is optional? Is that possible to implement if I changed your example?
Sorry, to clarify, my my comment. My first question is really asking if it’s possible to change the format of the url. So instead of
http://localhost:8080/cxf-rest-example/myservice/user/1
can I use
http://localhost:8080/cxf-rest-example/myservice/?user=1
Thanks. And great write up.
Salam,
yes it’s possible by adding or changing a @pathParam to a @queryParam for your user parameter
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
to use this url
http://localhost:8080/cxf-rest-example/myservice/?user=1
in ur service class where ur definng the path
@webservice
@path(“/myservice/”)
public User getUser(@QueryParam(”id”) Integer id)
use QueryParam instead of pathparam
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?
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
{ 1 trackback }