Tuesday Feb 07, 2012

JAX-RS 2.0 Early Draft Explained - Java EE 7 Making Progress


JAX-RS 2.0 Early Draft has been available for about 3 months now. JAX-RS 2.0, just like JPA 2.1, was one one of the first JSRs to be filed as part of Java EE 7. Several other specifications in Java EE 7 have released early drafts as well (JavaServer Faces 2.2CDI 1.1, EJB 3.2, and more coming as well) and I'll cover them in later blogs.

Here are the topics covered so far:
JAX-RS 2.0 is a brand new specification and here are the main highlights so far:
  • Client API: The HTTPUrlConnection is too low level and is not RESTful-oriented. Invoking a RESTful resource using this class would look like:

    URL url = new URL("http://.../atm/balance");
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    conn.setRequestMethod("GET");
    conn.setDoInput(true);
    conn.setDoOutput(false);
               
    BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
    String line;
    while ((line = br.readLine()) != null) {
        out.println(line);
    }


    Notice, how much code has to be written and its brittle. Some JAX-RS 1.0/1.1 implementations already provide a higher-level client-side API to access the Web resources. For example, read TOTD #57 for more details about Jersey Client API. A slightly more advanced sample to access Twitter timeline using Jersey Client API is described in TOTD #143.

    JAX-RS 2.0 introduces a standard Client API to in javax.ws.rs.client package to access the Web resource. It also share features with JAX-RS server API (readers/writers).

    A simple usage looks like:

    Client client = ClientFactory.newClient();
    String balance = client.target("http://.../atm/balance")
                           .request("text/plain")
                           .get(String.class);


    Instead of Client client = ClientFactory.newClient(), would you like @Inject Client client then vote for JAX_RS_SPEC-170.

    Path and query parameters can be easily specified using the builder pattern as shown below:

    Client client = ClientFactory.newClient();
    String balance = client.target("http://.../atm/{card}/balance")
                           .pathParam("card", "1111222233334444")
                           .queryParam("pin", "1234")
                           .request("text/plain")
                           .get(String.class);


    See how the template in the target path is automatically substituted with the correct value. The request type is specified to be "text/plain" and GET method of this web resource is invoked. This will be translated to:

    http://.../atm/1111222233334444/balance?pin=1234

    A POST request will look like:

    Money balance = client.target("http://.../atm/{card}/withdraw")
                          .pathParam("card", "1111222233334444")
                          .queryParam("pin", "1234")
                          .request("application/json")
                          .post(text("50.0"), Money.class);

    There is also generic command pattern that enables separation of concern between the creator and submitteruseful for batch processing using Invocation. And the code would look like:

    Invocation inv1 = client.target("http://.../atm/{card}/balance")
                            .pathParam("card", "1111222233334444")
                            .queryParam("pin", "1234")
                            .request("text/plain")
                            .buildGet();


    Invocation inv2 = client.target("http://.../atm/{card}/withdraw")
                            .pathParam("card", "1111222233334444")
                            .queryParam("pin", "1234")
                            .request("application/json")
                            .buildPost(text("50.0"));

    And once the Invocations are ready then they can be invoked.

  • Filters and Handlers: The filters and handlers allow app developers to perform message request pre-processing and response post-processing via well-defined extension points on the client- and server-side. This is yet another feature that was supported by several JAX-RS 1.0/1.1 implementations with each using slightly different semantics and now getting standardized in JAX-RS 2.0.

    Filters are non-wrapping extension points, allow pre-processing without modifying the request itself. A filter implements interface RequestFilter or ResponseFilter or both and is annotated with @Provider. A logging filter that simply logs the message may look like:

    @Provider
    class LoggingFilter implements RequestFilter, ResponseFilter {

        @Override
        public FilterAction preFilter(FilterContext ctx) throws IOException {
            logRequest(ctx.getRequest());
            return FilterAction.NEXT;
        }

        @Override
        public FilterAction postFilter(FilterContext ctx) throws IOException {
            logResponse(ctx.getResponse());
            return FilterAction.NEXT;
        }
    }

    Multiple filters are grouped in filter chains. The response from preFilter and postFilter indicate whether the next filter in the chain need to be executed (FilterAction.NEXT) or stopped (FilterAction.STOP).

    Handlers provide wrapping extension points. A handler implements interface ReadFromHandler or WriteToHandler or both and is annotated with @Provider. A GZIP filter that provides deflate and inflate capabilities may look like:

    @Provider
    class GzipHandler implements ReadFromHandler, WriteToHandler {
        @Override
        public Object readFrom(ReadFromHandlerContext ctx) throws IOException {
            InputStream old = ctx.getInputStream();
            ctx.setInputStream(new GZIPInputStream(old));
            try {
                return ctx.proceed();
            } finally {
                ctx.setInputStream(old);
            }
        }

        @Override
        public Object writeTo(WriteToHandlerContext ctx) throws IOException {
            OutputStream old = ctx.getOutputStream();
            GZIPOutputStream gzipOutputStream =
    new GZIPOutputStream(old);
            ctx.setInputStream(gzipOutputStream);
            try {
                return ctx.proceed();
            } finally {
                gzipOutputStream.finish();
                ctx.setOutputStream(old);
            }
        }
    }

    Multiple handlers are grouped in handler chains. The proceed method must be explicitly called in order for the next handler in the chain to be invoked.

    In the direction of flow the filters always executed before handlers. The following diagram shows the exact execution order on client and server-side:



    The handlers and filters can be associated to each method of a resource specifically using @NameBinding. The specification defines @GlobalBinding to associate handlers and filters to all methods of a resource but the recent version of the specification removes it and makes it a default. Also look at JAX_RS_SPEC-146 that asks for a mechanism to override the global filters/handlers.

    The dynamic binding, enabled by implementing DynamicBinding, provide more control on the association with resources and methods.
  • Hypermedia: Linking resources together is one of the main RESTful principles. There are structural links that are used to avoid sending a complete representation of a resource and enable lazy loading. The clients can follow these type of links to retrieve the "pieces" they need. A transitional link is used to update the state of a resource and is typically identified by a "rel" attribute. Structural links are normally in the entity; transitional links could be in link headers or the entity.

    JAX-RS 2.0 will only support transitional links in headers using newly added Link and LinkBuilder classes. The proposed Client API can also create a target from a link. The code may look like:

    Response r = client.target("/product").request("application/json").get();
    ResponseHeaders rh = r.getHeaders();
    if (rh.hasLink("ship")) {
        client.invocation(rh.getLink("ship")).invoke();
    }


    On the server side, ResponseBuilder has support for adding one or more link headers. The code may look like:

    @Path("/products")
    public class MyResponse {

        @GET
        @Path("{id}")
        @Produces({"application/json", "application/xml"})
        public Response getProduct(@PathParam("id")int id) {
            Product product = new Product(id);
            return Response
                    .ok(product)
                    .link("http://.../orders/" + id + "/ship", "ship")
                    .build();
        }
    }


  • Validation: Web resources must validate data received in query or header parameters or entity bodies. Currently this validation has to be performed in the application code. The Bean Validation specification already defines a extensible mechanism to specify validation constraints on a bean. So the JAX-RS specification leverages that and introduces support for declarative validation. The constraint annotations can be specified in public constructor parameters, method parameters, fields and bean properties. In addition, they can also decorate resource classes, entity parameters and resource methods. Here is a sample class augmented with constraint annotations:

    @Path("/")
    class ProductResource {

        @POST
        @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
        public void addProduct(@NotNull @FormParam("productName") String name,
                               @NotNull @Category @FormParam("category") String category) {
            . . .
        }
    }


    The @NotNull is a pre-defined constraint in the Bean Validation API and ensures that the target parameters are not null. The @Category is a user-defined constraint using the extensible mechanism provided by the Bean Validation API. If the constraints are violated then the resource method is not invoked and instead a response with status code 400 (Bad Request) and an entity that describe the violations is returned to the client.

    Here is a sample code of how to validate request entity bodies when they are mapped to resource method parameters:

    @CheckProduct
    class Product { . . . }

    @Path("/")
    class ProductResource {

        @POST
        @Consumes(MediaType.APPLICATION_JSON)
        public void addProduct(@Valid Product product) {
            . . .
        }
    }


    The presence of @Valid (a pre-defined annotation in Bean Validation API) next to the method parameter ensures that the @CheckProduct constraint is called to verify the mapped entity.

    The JAX-RS specification also defines the sequence to validate root resource class instances. The recommendation is to return as many violations as possible instead of aborting after the first violation is encountered.
  • Asynchronous Processing: JAX-RS 2.0 introduces asynchronous processing on server- and client-side APIs for the usual reasons. The server-side code will look like:

    @Path("/async")
    class ProductResource {
        @Context ExecutionContext ctx;

        @GET
        @Suspend
        public Product longOp() {
            Executors.newSingleThreadExecutor().submit(
                new Runnable() {
                    public void run() {
                        Proruct product = longQueryFromDatabase();
                        ctx.resume(product);
                    }
                }
            );
        }
    }

    The longOp method is invoked when this resource is accessed using GET, forks a new thread, and returns immediately without producing the result. Once longQueryFromDatabase returns the product then the connection is resumed and the response is returned by calling ctx.resume and setting the value.

    ExecutionContext also provide suspend() that allows to override the values, such as timeout, specified in the annotation based upon the runtime state.

    On the client-side, the code will look like:

    Client client = ClientFactory.newClient();
    Future<String> future = client.target("http://.../atm/{card}/balance")
                                  .pathParam("card", "1111222233334444")
                                  .queryParam("pin", "1234")
           
                           .request("text/plain")
                  
                    .async()
                         
             .get();

    The async() is called during building the client request. The return Future<String> can be used to query/cancel the status of execution on the server-side using isDone and cancel. Once the response is ready then Future<T>.get() is invoked to receive an instance of T if the response was successful or null if the invocation failed.

    Optionally an InvocationCallback<T> may be registered during the request invocation. The completed method is called when the invocation completes successfully and a response is available and failed method is called when the invocation fails. The code looks like:

    Future<String> future = client.target("http://.../atm/{card}/balance")
                                  .pathParam("card", "1111222233334444")
                                  .queryParam("pin", "1234")
           
                           .request("text/plain")
                  
                    .async()
                         
             .get(
                                      new InvocationCallback<String>() {
                                          @Override
                                          public void completed(String result) {
                                              // got the correct result
                                              System.out.println(result);
                                          }

                                          @Override
                                          public void failed(InvocationException error) {
                                              // ouch, got an error!
                                              System.err.println(error.getCause());
                                          }
                                      }
                                   );


    Notice, the type of the result is specified as type parameter to InvocationCallback.

    Client and server developers optimize their resources (threads) depending upon their needs and are independent of each other. A client really can't tell if a resource is implemented asynchronously or not and shouldn't even need to know.

  • Improved connection negotiation: This would allow a server to specify a preferred MIME type if the client does not care about it. This can be easily specified using the "qs" qualifier as shown below:

    @Path("/")
    class ProductResource {

        @GET
        @Produces({ "text/xml
    ;qs=0.75", "application/json"})
        public Product[] getProducts() {
            . . .
        }
    }


    The default server-side qs-value and the final order of server-side served types preferences follow the same rules as those specified for HTTP Accept Header. Per those rules an unspecified value takes the default value of 1. So the above @Produces rule says "application/json" will be served as the preferred type if there is no Accept header from the client. The section 3.5 talks more about qs parameter and the section 3.8 in the specification provide complete details about how media type of the response is chosen by a JAX-RS runtime.

The Appendix D in the specification provide a comprehensive list of changes from the previous version of the specification.

Here are some final set of references for you:

And of course, it'll all be delivered as part of GlassFish 4.0!

Learn the latest and greatest about JAX-RS 2.0 from Marek Potociar's talk on JAX-RS at Devoxx 2011:

About

profile image
Arun Gupta is a technology enthusiast, a passionate runner, author, and a community guy who works for Oracle Corp.


Java EE 7 Samples

Stay Connected

Search

Archives
« February 2012 »
SunMonTueWedThuFriSat
   
2
3
4
5
8
9
13
14
16
17
18
19
20
22
23
24
25
26
27
28
   
       
Today