You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 24 Next »

CXF has an initial implementation of JAX-RS (JSR-311): Java API for RESTfulWeb Services. JAX-RS provides a more standard way to build RESTful services in Java. 0.6 version of JSR-311 API is currently supported.

JAX-RS related demos are located under samples\jax_rs directory (CXF 2.1 only).

Understanding the basics

You are encouraged to read JAX-RS spec to find out information not covered by this documentation.

Resource class

A resource class is a Java Class annotated with JAX-RS annotations to represent a Web resource. A typical resource class in JAX-RS looks like this below:

package demo.jaxrs.server;

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

import javax.ws.rs.GET;
import javax.ws.rs.ProduceMime;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;

@Path("/customerservice/")
@ProduceMime("application/xml")
public class CustomerService {

    public CustomerService() {
    }

    @GET
    public Customers getCustomers() {
        ......
    }

    @GET
    @Path("/customers/{id}")
    @ProduceMime("application/json")
    public Customer getCustomer(@PathParam("id") String id) {
        ......
    }

    @PUT
    @Path("/customers/{id}")
    @ConsumeMime("application/xml")
    public Response updateCustomer(@PathParam("id") Long id, Customer customer) {
        ......
    }

    @POST
    @Path("/customers")
    public Response addCustomer(Customer customer) {
        ......
    }

    @DELETE
    @Path("/customers/{id}/")
    public Response deleteCustomer(@PathParam("id") String id) {
        ......
    }

    @Path("/orders/{orderId}/")
    public Order getOrder(@PathParam("orderId") String orderId) {
       ......
    }
}

Customer resource class can handle requests starting from /customerservice. When /customerservice request is matched to this class, its getCustomers() method will be selected. updateCustomer(), deleteCustomer() and addCustomer() are used to serve POST, PUT and DELETE requests starting from /customerservice/customer, while getOrder() method delegates the handling of requests like /customerservice/orders/1 to a subresource locator Order.

ProduceMime annotation is used to specify the format of the response. When not available on the method, it's inherited from a class, and if it's not available on the class then it's inherited from a corresponding message body writer, if any. Default value is */*, but it's recommended that some definite value is specified. The same applies to ConsumeMime, only it's message body readers that are checked as the last resort.

For example, getCustomers() method inherits the ProduceMime annotation from its class, while getCustomer() method overrides it with its own value.

@Path

@Path annotation is applied to resource classes or methods. The value of @Path annotation is a relative URI path follows the URI Template format.

More information about Path annotations can be found from JAX-RS spec section 2.3.

HTTP Method

JAX-RS specification defines a number of annotations such as @GET, @PUT, @POST and @DELETE. Using an @HttpMethod designator, one can create a custom annotation such as @Update or @Patch

Return types

Either javax.ws.rs.core.Response or custom type can be returned. javax.ws.rs.core.Response can be used to set the HTTP response code, headers and entity.

Exception handling

One can either throw an unchecked WebApplicationException or return Response with a proper error code set.
The former option may be a better option when no JAX-RS types can be added to method signatures.

For example :

@Path("/customerservice/")
public class CustomerService {

    
    @PUT
    @Path("/customers/{id}")
    public Response updateCustomer(@PathParam("id") Long id, Customer customer) {
        return Response.status(errorCode).build();
    }

    @POST
    @Path("/customers")
    public Customer addCustomer(Customer customer) {
        throw new WebApplicationException(errorCode);
    }

}

Dealing with Parameters

PathParam annotation is used to map a given Path template variable to a method parameter.
For example :


@Path("/customer/{id}")
public class CustomerService {

    
    @PUT
    @Path("{name}")
    public Response updateCustomer(@PathParam("id") Long id, @PathParam("name") String name) {
        ...
    }
}

In this case a template variable id available from a root class annotation is mapped to a parameter of type Long, while a name variable is mapped to a parameter of type String.
Paramaters can be of type String or of any type that have constructors accepting a String argument or static valueOf(String s) methods. JAX-RS PathSegment is also supported.

QueryParam, HttpHeader and CookieParam annotations are also supported.

Context annotations

If a parameter or field of either UriInfo or HttpHeaders or SecurityContext or one of the Servlet types like ServeltContext or MessageBodyWorkers is annotated with a Context annotation then CXF will provide properly initialized instances to the application code.

Note that if fields are annotated with Context then care should be taken for singleton resource classes as CXF does not support yet a thread-local injection of Context fields.

Injection of contexts into message body providers is not supported yet.

Example :

@Path("/customer")
public class CustomerService {
    
    @Context ServletContext sc;
    @Context SecurityContext sc;
    
    @PUT
    public Response updateCustomer(@Context UriInfo u, @Context HttpHeader h, Customer c) {
        ...
    }
}

Annotation inheritance

Most of the JAX-RS annotations can be inherited from either an interface or a superclass. JAX-RS specification requires that only the ones applied to methods can be inherited. For example :


public interface CustomerService {

    @PUT
    @Path("/customers/{id}")
    Response updateCustomer(Long id, Customer customer) {
        return Response.status(errorCode).build();
    }

    @POST
    @Path("/customers")
    Customer addCustomer(Customer customer) {
        throw new WebApplicationException(errorCode);
    }

}

@Path("/customerservice/")
public class Customers implements CustomerService {

    
    public Response updateCustomer(@PathParam("id"), Customer customer) {
        return Response.status(errorCode).build();
    }

    public Customer addCustomer(Customer customer) {
        throw new WebApplicationException(errorCode);
    }

}

Note that annotations applied to the interface are inherited. CXF is not capable yet of inheriting the annotations applied to parameters such as PathParam so at the moment they need to be specified anyway at the root class level.

Similarly annotations can be inherited from super-classes.

CXF can inherit annotations applied directly to super-classes such as @Path("/customerservice/") which is applied to the Customers class above. It may support the same type of inheritance for interfaces too. Note this is not JAX-RS compliant.

Sub-resource locators.

A method of a resource class that is annotated with @Path becomes a sub-resource locator when an annotation with an HttpMethod designator like @GET is not present. Sub-resource locators are used to further resolve the object that will handle the request. They can delegate to other sub-resource locators.

In the example below, getOrder method is a sub-resource locator:

@Path("/customerservice/")
public class CustomerService {

    @Path("/orders/{orderId}/")
    public Order getOrder(@PathParam("orderId") String orderId) {
       ......
    }
}

@XmlRootElement(name = "Order")
public class Order {
    private long id;

    public Order() {
    }

    public long getId() {
        return id;
    }


    @GET
    @Path("products/{productId}/")
    public Product getProduct(@PathParam("productId")int productId) {
       ......
    }
}

A HTTP GET request to http://localhost:9000/customerservice/orders/223/products/323 is dispatched to getOrder method first. If the Order resource whose id is 223 is found, the Order 223 will be used to further resolve Product resource. Eventually, a Product 323 that belongs to Order 223 is returned.

Message Body Providers

JAX-RS relies on MessageBodyReader and MessageBodyWriter implementations to serialize and de-serialize Java types. JAX-RS requires that certain types has to be supported out of the box.
By default, CXF supports String, byte[], InputStream, File, JAXP Source, JAXB-annotated types with application/xml and application/json formats (see below). JAX-RS MultivaluedMap is also supported for
form contents. Support for other types like Reader and StreamingOutput is on the way.

Custom Message Body Providers

It's likely that a given application may need to deal with types which are not supported by default.
Alternatively, developers may want to provide a more efficient support for handling default types such as InputStream.

Here's an example of a custom MessageBodyReader for InputStream (0.7 jax-rs api) :


@ConsumeMime("application/octet-stream")
@Provider
public class InputStreamProvider implements MessageBodyReader<InputStream> {

    
    public boolean isReadable(Class<InputStream> type, Type genericType, Annotation[] annotations) {
        return InputStream.class.isAssignableFrom(type);
    }

    public InputStream readFrom(Class<InputStream> clazz, Type t, MediaType mt, 
                         Annotation[] a, MultivaluedMap<String, String> headers, InputStream is) 
        throws IOException {
        return new FilterInputStream(is) {
             @Override
             public int read(byte[] b) throws IOException {
                 // filter out some bytes
             }              
        }     
    }
}

and here's an example of a custom MessageBodyWriter for Long (0.7 jax-rs api) :


@ProduceMime("text/plain")
@Provider
public class LongProvider implements MessageBodyWriter<Long> {

    public long getSize(Long l) {
        return -1;
    }

    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations) {
        return long.class.isAssignableFrom(type) || Long.class.isAssignableFrom(type);
    }

    public void writeTo(Long l, Class<?> clazz, Type type, Annotation[] a, 
                        MediaType mt, MultivaluedMap<String, Object> headers, OutputStream os) 
        throws IOException {
        os.write(l.toString().getBytes());
        
    }

Registering custom providers

Putting @Provider annotation on the provider class is something that should lead to your provider being registered with the runtime. CXF does not support this feature yet. One can easily register a provider either from the Spring configuration :


<beans>
<jaxrs:server id="customerService" address="/">
    <jaxrs:serviceBeans>
      <bean class="org.CustomerService" />
    </jaxrs:serviceBeans>

    <jaxrs:entityProviders>
      <bean ref="isProvider" />
      <bean ref="longProvider" />
    </jaxrs:entityProviders>
    <bean id="isProvider" class="com.bar.providers.InputStreamProvider"/>
    <bean id="longProvider" class="com.bar.providers.LongProvider"/>
</jaxrs:server>
</beans>

Please note that once CXF is updated to support a 0.7 version of JAX-RS api, <jaxrs:entityProviders> will be changed to <jaxrs:providers>. JAX-RS supports different types of providers and having a single <jaxrs:providers> container is in line with the way other JAX-RS implementations discover providers by checking for @Provider annotations only.

See below a more complete beans.xml definition.

Content type negotiation

One of the coolest thing of REST is that the same resource can be served using multiple representations. @ProduceMime and @ConsumeMime annotations are used to declare the supported request and response media types.

JAXB support

The request and response can be marshaled and unmarshaled to/from Java object using JAXB. The Java object needs to be marked with @XmlRootElement annotation. For example:

@XmlRootElement(name = "Customer")
public class Customer {
    private String name;
    private long id;

    public Customer() {
    }

    public void setName(String n) {
        name = n;
    }

    public String getName() {
        return name;
    }

    public void setId(long i) {
        id = i;
    }

    public long getId() {
        return id;
    }

}

In the example below, the Customer object returned by getCustomer is marshaled using JAXB data binding:

@Path("/customerservice/")
public class CustomerService {
    @GET
    @Path("/customers/{customerId}/")
    public Customer getCustomer(@PathParam("customerId") String id) {
        ....
    }
}

The wire representation of Customer object is:

@Path("/customerservice/")
<Customer>
    <id>123</id>
    <name>John</name>
</Customer>

To work with collections, you need to define an object collection type. For example:

@XmlRootElement(name = "Customers")
public class Customers {
    private Collection<Customer> customers;

    public Collection<Customer> getCustomer() {
        return customers;
    }

    public void setCustomer(Collection<Customer> c) {
        this.customers = c;
    }
}
@Path("/customerservice/")
public class CustomerService {
    @GET
    @Path("/customers/")
    public Customers getCustomers() {
        ....
    }
}

JSON support

Following code returns a Customer object that is marshaled to JSON format:

@Path("/customerservice/")
public class CustomerService {
    @ProduceMime("application/json")
    @GET
    @Path("/customers/{customerId}/")
    public Customer getCustomer(@PathParam("customerId") String id) {
        ....
    }

The wire representation of Customer object is:

{"Customer ":{"id":"123","name":"john"}}

Source support

You can also receive the request as a Source object or return a Source object as response:

@Path("/customerservice/")
public class CustomerService {
    @PUT
    @Path("/customers/")
    public javax.xml.transform.Source updateCustomer(javax.xml.transform.Source ds) {
        ....
    }
}

Advanced Content type negotiation

CXF provides a partial support for an advanced content type negotiation by handling If-Match headers and ETags in its JAX-RS Request implementation. Neither If-Modified-Since nor Vary headers are currently supported.

Secure JAX-RS services

A demo called samples\jax_rs\basic_https shows you how to do communications using HTTPS.

Configuring JAX-RS services

Configuring JAX-RS services programmatically

You can create a JAX-RS RESTful service by using JAXRSServerFactoryBean from the cxf-rt-frontend-jaxrs package:

JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
sf.setResourceClasses(CustomerService.class);
sf.setAddress("http://localhost:9000/");
sf.create();

A couple things to note:

  • The JAXRSServerFactoryBean creates a Server inside CXF which starts listening for requests on the URL specified.
  • By default, the JAX-RS runtime is responsible for the lifecycle of resource classes, default lifecycle is per-request. You can set the lifecycle to singleton by using following line:
    sf.setResourceProvider(BookStore.class, new SingletonResourceProvider());
    
  • If you prefer not to let the JAX-RS runtime to handle the resource class lifecycle for you (for example, it might be the case that your resource class is created by other containers such as Spring), you can do following:
    JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
    CustomerService cs = new CustomerService();
    sf.setServiceBeans(cs);
    sf.setAddress("http://localhost:9080/");
    sf.create();
    

Configuring JAX-RS services in container with Spring configuration file.

web.xml

<?xml version="1.0" encoding="ISO-8859-1"?>

<!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>
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>WEB-INF/beans.xml</param-value>
	</context-param>

	<listener>
		<listener-class>
			org.springframework.web.context.ContextLoaderListener
		</listener-class>
	</listener>

	<servlet>
		<servlet-name>CXFServlet</servlet-name>
		<display-name>CXF Servlet</display-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>

beans.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">

  <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="customerService" address="/">
    <jaxrs:serviceBeans>
      <ref bean="customerService" />
    </jaxrs:serviceBeans>
  </jaxrs:server>

  <bean id="customerService" class="demo.jaxrs.server.CustomerService" />
</beans>

Combining JAX-WS and JAX-RS

Here's a beans.xml showing how to have a single service class supporting both SOAP and REST-based invocations at the same time with the help of JAX-WS and JAX-RS :

<?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"
  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/jaxrs
http://cxf.apache.org/schemas/jaxrs.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-jaxrs-binding.xml" />
  <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

  <!-- JAX-RS -->
  <jaxrs:server id="customerService" address="/">
    <jaxrs:serviceBeans>
      <ref bean="customerService" />
    </jaxrs:serviceBeans>
  </jaxrs:server>

  <!-- JAX-WS -->
  <jaxws:endpoint implementor="#customerService"
    address="/CustomerWorld" wsdlLocation="..."/>
  
  <bean id="customerService" class="demo.jaxrs.server.CustomerService" />
</beans>

Either contract-first or Java-first approach can be used for JAX-WS. JAX-RS annotations can be added to the existing service class. Some custom providers may need to be created, depending on the complexity of the method signatures.

When a WSDL-first approach is used then a document-literal-wrapped style may or may not be a good fit as the code generator unwraps all the types into a signature, for example :

public class CustomerService {
   void doIt(String a, String b) {...};
}

By default JAX-RS may not be able to handle such methods as it requires that only a single parameter can be available in a signature that is not annotated by one of the JAX-RS annotations like @PathParam. So if
a 'String a' parameter can be mapped to a @Path template variable or one of the query segments then this signature won't need to be changed :

@Path("/customers/{a}")
public class CustomerService {
   void doIt(@PathParam("a") String a, String b) {...};
}

For methods returning 'void', a simple custom provider can be created which will basically be a no-op implementation.

JAX-RS and Spring AOP

CXF JAX-RS is capable of working with AOP interceptors applied to resource classes from Spring.
For example :


<beans xsi:schemaLocation=" http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop  http://www.springframework.org/schema/aop/spring-aop.xsd http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd">
<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="bookservice" address="/">
	<jaxrs:serviceBeans>
          <ref bean="bookstore"/>
          <ref bean="bookstoreInterface"/>
        </jaxrs:serviceBeans>
   </jaxrs:server>
   <bean id="bookstore" class="org.apache.cxf.systest.jaxrs.BookStore"/>
   <bean id="bookstoreInterface" class="org.apache.cxf.systest.jaxrs.BookStoreWithInterface"/>

   <aop:config>
	<aop:aspect id="loggingAspect" ref="simpleLogger">
          <aop:before method="logBefore" pointcut="execution(* org.apache.cxf.systest.jaxrs.BookStore*.*(..))"/>
          <aop:after-returning method="logAfter" pointcut="execution(* org.apache.cxf.systest.jaxrs.BookStore*.*(..))"/>
        </aop:aspect>
   </aop:config>
   <bean id="simpleLogger" class="org.apache.cxf.systest.jaxrs.SimpleLoggingAspect"/>
</beans>

Note that some AOP configuration is applied to two JAX-RS resource classes. By default Spring uses JDK dynamic proxies every time a class to be proxified implements at least one interface or CGLIB proxies otherwise.

For example, here's how org.apache.cxf.systest.jaxrs.BookStoreWithInterface looks like :


public interface BookInterface {
    @GET
    @Path("/thosebooks/{bookId}/")
    @ProduceMime("application/xml")
    Book getThatBook(Long id) throws BookNotFoundFault;
}

public class BookStoreWithInterface extends BookStoreStorage implements BookInterface {

    public Book getThatBook(@PathParam("bookId") Long id) throws BookNotFoundFault {
        return doGetBook(id);
    }

    @Path("/thebook")
    public Book getTheBook(@PathParam("bookId") Long id) throws BookNotFoundFault {
        return doGetBook(id);
    }
}

In this case Spring will use a JDK proxy to wrap a BookStoreWithInterface class. As such it is important that a method which needs to be invoked such as getThatBook(...) is part of the interface.

The other method, getTheBook() can not be dispatched to by a JAX-RS runtime as it's not possible to discover it through a JDK proxy. If this method also needs to be invoked then this method should either be added to the interface or CGLIB proxies have to be explicitly enabled (consult Spring AOP documentation for more details)

Areas for improvement and TODO list.

  • No labels