Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Wiki Markup
{span:style=font-size:2em;font-weight:bold} JAX-RS : Understanding the Basics {span}

 

Table of Contents

What is New in JAX-RS 2.0

...

CompletionCallback can be registered with AsyncResponse to receive the notifications when the async response has been sent back.

ConnectionCallback is not currently supportedsupported starting from CXF 3.0.0-milestone2.

This feature can help developers write very sophisticated asynchronous applications.

...

JAX-RS 2.0 supports a proper resource method selection in cases where multiple root resource classes have the same Path value, for example:

Code Block
java
java

@Path("/")
public class Root1 {
   @Path("/1")
   @GET 
   public Response get() {...}
}

@Path("/")
public class Root2 {
   @Path("/2")
   @GET 
   public Response get() {...}
}

...

A resource class is a Java class annotated with JAX-RS annotations to represent a Web resource. Two types of resource classes are available: root resource classes and subresource classes. A root resource class is annotated with at least a @Path annotation, while subresource classes typically have no root @Path values. A typical root resource class in JAX-RS looks like this below:

Code Block
java
java

package demo.jaxrs.server;

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;

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

    public CustomerService() {
    }

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

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

    @PUT
    @Path("/customers/{id}")
    @Consumes("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) {
       ......
    }
}

...

The @Path annotation is applied to resource classes or methods. The value of @Path annotation is a relative URI path and follows the URI Template format and may include arbitrary regular expressions. When not available on the resource method, it's inherited from a class. For example:

Code Block
java
java

@Path("/customers/{id}")
public class CustomerResource {

    @GET
    public Customer getCustomer(@PathParam("id") Long id) {
        ......
    }

    @GET
    @Path("/order/{orderid}")
    public Order getOrder(@PathParam("id") Long customerId, @PathParam("orderid") Long orderId) {
        ......
    }

    @GET
    @Path("/order/{orderid}/{search:.*}")
    public Item findItem(@PathParam("id") Long customerId, 
                         @PathParam("orderid") Long orderId,
                         @PathParam("search") String searchString,
                         @PathParam("search") List<PathSegment> searchList) {
        ......
    }
}

...

The 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. For example :

Code Block
java
java

package org.apache.cxf.customverb;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@HttpMethod("PATCH")
public @interface PATCH { 
}

...

For example :

Code Block
java
java

@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);
    }

}

Yet another option is to register an ExceptionMapper provider. Ex :

Code Block
java
java

public BookExceptionMapper implements ExceptionMapper<BookException> {
    public Response toResponse(BookException ex) {
        // convert to Response
    }
}

...

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

Code Block
java
java


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

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

...

@QueryParam, @HttpHeader, @MatrixParam, @FormParam and @CookieParam annotations are also supported.
Parameters can be of type String or of any type that have constructors accepting a String parameter or static valueOf(String s) methods.
Additionally CXF JAXRS checks for static fromString(String s) method, so types with no valueOf(String) factory methods can also be dealt with:

Code Block
java
java


public enum Gender {
   MALE,
   FEMALE;

   public static Gender fromString(String s) {
       if ("1".equals(s)) {
           return FEMALE;
       } else if ("1".equals(s)) {
           return MALE;
       }  
       return valueOf(s); 
   }
}

@Path("/{g}")
public class Service {

    
    @PUT
    @Path("{id}")
    public Response update(@PathParam("g") Gender g, @PathParam("id") UUID u) {
        ...
    }
}

...

CXF JAXRS supports ParameterHandler extensions which can be used to deal with method parameters annotated with one of the JAXRS parameter annotations :

Code Block
java
java

public class MapHandler implements ParameterHandler<Map> {
    public Map fromString(String s) {...}
}

@Path("/map")
public class Service {

    
    @PUT
    @Path("/{mapvalue:(.)+}")
    public Response update(@PathParam("mapvalue") Map m, byte[] bytes) {
        ...
    }
}

...

All the parameters are automatically decoded. This can be disabled by using @Encoded annotation.
Parameters can have a default value set using a DefaultValue annotation :

Code Block
java
java


    public Response updateCustomer(@DefaultValue("123") @QueryParam("id") Long id, @PathParam("name") String name) { ... }

JAX-RS mandates that only a single method parameter which is not annotated with JAXRS annotations applicable to method parameters is allowed in a resource method. For example :

Code Block
java
java

public Response do(@PathParam("id") String id, String body) {
}

...

It's also possible to inject all types of parameters into fields or through dedicated setters. For example, the first code fragment in this section can be rewritten like this:

Code Block
java
java

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

    @PathParam("id")
    private Long id; 
    
    private String name;

    @PathParam("name")
    public setName(String name) {
        this.name = name;
    } 

    @PUT
    @Path("{name}")
    public Response updateCustomer() {
        // use id and name
    }
}

...

There's a CXF extension which makes it possible to inject a sequence of @PathParam, @QueryParam, @FormParam or @MatrixParam parameters into a bean. For example:

Code Block
java
java


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

    
    @PUT
    @Path("{name}")
    public Response updateCustomer(@PathParam("") Customer customer) {
        ...
    }

    @GET
    @Path("/order")
    public Response getCustomerOrder(@PathParam("id") int customerId, 
                                     @QueryParam("") OrderBean bean,
                                     @MatrixParam("") OrderBean bean) {
        ...
    }

    @POST
    public Response addCustomerOrder(@PathParam("id") int customerId,
                                     @FormParam("") OrderBean bean) {
        ...
    }
}

public class Customer {
   public void setId(Long id) {...}
   public void setName(String s) {...}  
}

public class OrderBean {
   public void setId(Long id) {...}
   public void setWeight(int w) {...}  
}



...

Nested beans are also supported, which among other things, makes it possible to formulate advanced search queries. For example, given the following bean definitions:

Code Block
java
java

class Name {
    String first;
    String last;
}

class Address {
    String city;
    String state;
}

class Person {
    Name legalName;
    Address homeAddr;
    String race;
    String sex;
    Date birthDate;
}

class MyService
{
    @GET
    @Path("/getPerson")
    Person getPerson(@QueryParam("") Person person);
} 

...

Classes with prototype scopes can get JAXRS contexts or parameters injected at construction time:

Code Block
java
java

@Path("/")
public class PerRequestResourceClass {

   public PerRequestResourceClass(@Context HttpHeaders headers, @QueryParam("id") Long id) {}
}

...

When multiple resource classes match a given URI request, the following algorithm is used :
1. Prefer the resource class which has more literal characters in its @Path annotation.

Code Block
java
java

@Path("/bar/{id}")
public class Test1 {}
@Path("/bar/{id}/baz")
public class Test2 {}
@Path("/foo")
public class Test3 {}
@Path("/foo/")
public class Test4 {}

...

2. Prefer the resource class which has more capturing groups in its @Path annotation.

Code Block
java
java

@Path("/bar/{id}/")
public class Test1 {}
@Path("/bar/{id}/{id2}")
public class Test2 {}

...

3. Prefer the resource class which has more capturing groups with arbitrary regular expressions in its @Path annotation.

Code Block
java
java

@Path("/bar/{id:.+}/baz/{id2}")
public class Test1 {}
@Path("/bar/{id}/bar/{id2}")
public class Test2 {}

...

4. Prefer a resource method to a subresource locator method

Code Block
java
java

@Path("/")
public class Test1 {

 @Path("/bar")
 @GET
 public Order getOrder() {...}

 @Path("/bar")
 public Order getOrderFromSubresource() {...}
}

public class Order {

 @Path("/")
 @GET
 public Order getOrder() { return this; }

}

...

Consider this resource class with 2 resource methods :

Code Block
java
java

@Path("/")
public class Test1 {

 @Path("/bar")
 @POST 
 @Consumes("application/json")
 @Produces("application/json")
 public Order addOrderJSON(OrderDetails details) {...}

 @Path("/bar")
 @POST
 @Consumes("application/xml")
 @Produces("application/xml")
 public Order getOrderXML(OrderDetails details) {...}
 
}

...

The above code can be replaced with this one :

Code Block
java
java

@Path("/")
public class Test1 {

 @Path("/bar")
 @POST 
 @Consumes({"application/json", "application/xml"})
 @Produces({"application/json", "application/xml"})
 public Order addOrder(OrderDetails details) {...}

}

...

Starting from CXF 2.2.5 it is possible to register a custom ResourceComparator implementation using a jaxrs:server/jaxrs:resourceComparator element, example:

Code Block
xml
xml

<!-- JAX-RS endpoint declaration fragment -->
<jaxrs:server address="/">
<!-- Usual elements, like serviceBeans or providers, etc -->

<!-- Custom ResourceComparator -->
<jaxrs:resourceComparator>
   <bean class="org.custom.CustomResourceComparator"/>
</jaxrs:resourceComparator>
</jaxrs:server>

...

For example, the optional HTTP request and URI parameters (query, matrix, headers, cookies) and form parameters do not affect the selection algorithm.
A custom ResourceComparator can be used when this limitation is considered to be problematic. For example, the following shows one such implementation:

Code Block
java
java


import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.ws.rs.core.MultivaluedMap;

import org.apache.cxf.jaxrs.ext.ResourceComparator;
import org.apache.cxf.jaxrs.model.ClassResourceInfo;
import org.apache.cxf.jaxrs.model.OperationResourceInfo;
import org.apache.cxf.jaxrs.model.OperationResourceInfoComparator;
import org.apache.cxf.jaxrs.model.Parameter;
import org.apache.cxf.jaxrs.util.JAXRSUtils;
import org.apache.cxf.message.Message;

public class QueryResourceInfoComperator extends OperationResourceInfoComparator implements
         ResourceComparator {

     public QueryResourceInfoComperator() {
         super(null, null);
     }

     @Override
     public int compare(ClassResourceInfo cri1, ClassResourceInfo cri2, Message message) {
         // Leave Class selection to CXF
         return 0;
     }

     @Override
     public int compare(OperationResourceInfo oper1, OperationResourceInfo oper2, Message message) {

         // Check if CXF can make a decision
         int cxfResult = super.compare(oper1, oper2);
         if (cxfResult != 0)
             return cxfResult;

         int op1Counter = getMatchingRate(oper1, message);
         int op2Counter = getMatchingRate(oper2, message);

         return op1Counter == op2Counter
                 ? 0
                 : op1Counter<  op2Counter
                         ? 1
                         : -1;
     }

     /**
      * This method calculates a number indicating a good or bad match between
      * values provided within the request and expected method parameters. A
      * higher number means a better match.
      *
      * @param operation
      *            The operation to be rated, based on contained parameterInfo
      *            values.
      * @param message
      *            A message containing query from user request
      * @return A positive or negative number, indicating a good match between
      *         query and method
      */
     protected int getMatchingRate(OperationResourceInfo operation, Message message) {

         List<Parameter>  params = operation.getParameters();
         if (params == null || params.size() == 0)
             return 0;

         // Get Request QueryParams
         Set<String>  qParams = getParams((String) message.get(Message.QUERY_STRING));

         int rate = 0;
         for (Parameter p : params) {
             switch (p.getType()) {
             case QUERY:
                 if (qParams.contains(p.getName()))
                     rate += 2;
                 else if (p.getDefaultValue() == null)
                     rate -= 1;
                 break;
             // optionally support other parameter types such as headers, etc
             // case HEADER:
             //  break;
             default:
                 break;
             }
         }
         return rate;
     }

     /**
      * @param query
      *            URL Query Example: 'key=value&key2=value2'
      * @return A Set of all keys, contained within query.
      */
     protected Set<String>  getParams(String query) {
         Set<String>  params = new HashSet<String>();
         if (query == null || query.length() == 0)
             return params;

         MultivaluedMap<String, String> allQueries =
             JAXRSUtils.getStructuredParams(query, "&", false, false);
         return allQueries.keySet();
     }
} 

Now consider this code:

Code Block
java
java

@Path("/paramTest")
public class MySimpleService {

      @GET
      public String getFoo(@QueryParam("foo") String foo){
          return "foo:" + foo;
      }

      @GET
      public String getFooBar(@QueryParam("foo") String foo,
@QueryParam("bar") String bar){
          return "foo:" + foo + " bar:" + bar;
      }

  
} 

...

Example:

Code Block
java
java

@Path("/customer")
public class CustomerService {
    
    @Context 
    private org.apache.cxf.jaxrs.ext.MessageContext mc; 
    @Context 
    private ServletContext sc;
    private UriInfo ui;
    
    @Context
    public void setUriInfo(UriInfo ui) {
        this.ui = ui;
    }

    @PUT
    public Response updateCustomer(@Context HttpHeaders h, Customer c) {
        mc.getHttpHeaders();
    }
}

...

Registering a custom ContextProvider implementation such as SearchContextProvider lets attach Context annotations to arbitrary classes which can be helpful when some of the information representing the current request needs to be optimized or specialized, example:

Code Block
java
java

package resources;
import org.apache.cxf.jaxrs.ext.search.SearchContext;
@Path("/")
public class RootResource {
    @Context
    private  SearchContext sc;
    // the rest of the code
}

and

Code Block
xml
xml

<jaxrs:server>
  <serviceBeans>
    <bean class="resources.RootResource"/>
  </serviceBeans>
  <jaxrs:providers>
    <bean class="org.apache.cxf.jaxrs.ext.search.SearchContextProvider"/>
  </jaxrs:providers>
</jaxrs:server>

...

Mapping of a particular URI to a service that returns some resource is straightforward using the @Path annotation. However RESTful services are often connected: one service returns data that is used as the key in another service. Listing entities and accessing a particular entity is a typical example:

Code Block
java
java

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

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

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

}

For this service we can assume that the returned list of customers exposes only basic attributes and more details is returned using the second method which uses the customer id as the key. Something like this:

Code Block

GET http://foobar.com/api/customers

<customers>
    <customer id="1005">John Doe</customer>
    <customer id="1006">Jane Doe</customer>
    ...
</customers>
    
GET http://foobar.com/api/customers/1005

<customer id="1005">
    <first-name>John</first-name>
    <last-name>Doe</last-name>
    ...
</customer>

How does a client of this service know how to get from list of customers to given customer? A trivial approach would be to expect the client to compute the proper URI. But wouldn't it be better to have the services provide full URIs in the response that can be used directly? This way the client would be more decoupled from the service itself (which may change URI format over time). A client could be provided the following on response, for example:

Code Block

GET http://foobar.com/api/customers

<customers-list>
    <customer id="1005" url="http://foobar.com/api/customers/1005">John Doe</customer>
    <customer id="1006" url="http://foobar.com/api/customers/1006">Jane Doe</customer>
    ...
</customers-list>

...

The core part of the solution is to inject the UriInfo object into method "getCustomers". This helper object allows for extracting useful information about the current URI context, but more importantly allows for getting the UriBuilder object. UriBuilder has multiple appender methods for building the URI for each object; in our case to the stem URI we can append path in multiple ways, providing it as a string (which we actually want to avoid here) or a resource (class or method) to extract the @Path value. Finally UriBuilder must have values bound to its template variables to render the actual URI. This case in action looks like this:

Code Block
java
java

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

    @GET
    public Customers getCustomers(@Context UriInfo ui) {
        ......
        // builder starts with current URI and has the path of the getCustomer method appended to it
        UriBuilder ub = ui.getAbsolutePathBuilder().path(this.getClass(), "getCustomer");
        for (Customer customer: customers) {
            // builder has {id} variable that must be filled in for each customer
            URI uri = ub.build(customer.getId());
            c.setUrl(uri);
        }
        return customers;
    }

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

}

...

Most of the JAX-RS annotations can be inherited from either an interface or a superclass. For example:

Code Block
java
java


public interface CustomerService {

    @PUT
    @Path("/customers/{id}")
    Response updateCustomer(@PathParam("id") Long id, Customer customer);

    @POST
    @Path("/customers")
    Customer addCustomer(Customer customer);

}

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

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

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

}

...

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

Code Block
java
java

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

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

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

    public Order() {
    }

    public long getId() {
        return id;
    }


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

    @Path("products/{productId}/items")
    public Order getItems(@PathParam("productId") int productId) {
       return this;
    }

    @GET
    public Items getItems() {
       ......
    }

}

...

Note that a given subresource can be represented as an interface or some base class resolved to an actual class at runtime. In this case any resource methods which have to be invoked on an actual subresource instance are discovered dynamically at runtime:

Code Block
java
java

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

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

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

@XmlRootElement(name = "Order")
public class OrderImpl implements Order {
    
    public Product getProduct(int productId) {
       ......
    }
}

@XmlRootElement(name = "Order")
public class OrderImpl2 implements Order {
    
    // overrides JAXRS annotations
    @GET
    @Path("theproducts/{productId}/")
    Product getProduct(@PathParam("id")int productId) {...}
}

...

If you know that all the JAXRS annotations are available on a given subresource type (or one of its superclasses or interfaces) returned by a subresource locator method then you may want to disable the dynamic resolution :

Code Block
xml
xml

<beans>
<jaxrs:server staticSubresourceResolution="true">
<!-- more configuration -->
</jaxrs:server>
</beans>

...

Here's an example of a custom MessageBodyReader for InputStream:

Code Block
java
java


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

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

    public InputStream readFrom(Class<InputStream> clazz, Type t, Annotation[] a, MediaType mt, 
                         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 objects:

Code Block
java
java


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

    public long getSize(Long l, Class<?> type, Type genericType, Annotation[] annotations, MediaType mt) {
        return -1;
    }

    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mt) {
        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());
        
    }

...

One can easily register a provider either from Spring configuration or programmatically:

Code Block
xml
xml


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

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

...

When using the custom configuration (as shown above) provider instances of different types (handling the same format of request/response bodies) or differently configured instances of the same type can be registered with a different jaxrs:server instance. Yet another requirement might be to have only a given jaxrs:server endpoint among multiple available ones to handle requests with a given media type:

Code Block
xml
xml


<beans>
<jaxrs:server id="customerService1" address="/1">
   <bean id="serviceBean" class="org.CustomerService" /> 

   <jaxrs:serviceBeans>
      <ref bean="serviceBean"/>
   </jaxrs:serviceBeans>

   <jaxrs:providers>
      <bean class="com.bar.providers.InputStreamProvider"/>
   </jaxrs:providers>
</jaxrs:server>
<jaxrs:server id="customerService2" address="/2">
    <jaxrs:serviceBeans>
      <ref bean="serviceBean"/>
    </jaxrs:serviceBeans>

    <jaxrs:providers>
      <bean id="isProvider" class="baz.foo.jaxrsproviders.InputStreamProvider"/>
    </jaxrs:providers>
</jaxrs:server>

<jaxrs:server id="customerService3" address="/3">
    <jaxrs:serviceBeans>
      <ref bean="serviceBean"/>
    </jaxrs:serviceBeans>

    <jaxrs:providers>
      <ref bean="isProvider"/>
    </jaxrs:providers>
</jaxrs:server>


<bean id="isProvider" class="com.bar.providers.InputStreamProvider">
   <property name="limit" value="5"/>
</bean>

</beans>

...