Chapter 9. RESTful Web Services

RESTful web services are defined as JSR 311, and the complete specification can be downloaded from http://jcp.org/aboutJava/communityprocess/mrel/jsr311/index.html.

REST is an architectural style of services that utilizes web standards. Web services designed using REST are called RESTful web services. The main principles of RESTful web services are:

  • Everything can be identified as a resource and each resource is uniquely identifiable using a URI.

  • A resource can be represented in multiple formats, defined by a media type. The media type will provide enough information on how the requested format needs to be generated. Standard methods are defined for the client and server to negotiate on the content type of the resource.

  • Use standard HTTP methods to interact with the resource: GET to retrieve a resource, POST to create a resource, PUT to update a resource, and DELETE to remove a resource.

  • Communication between the client and the endpoint is stateless. All the associated state required by the server is passed by the client in each invocation.

Java API for RESTful web services (JAX-RS) defines a standard annotation-driven API that helps developers build a RESTful web service in Java. The standard principles of REST, such as identifying a resource as a URI, a well-defined set of methods to access the resource, and multiple representation formats of a resource, can be easily marked in a POJO using annotations.

Simple RESTful Web Services

A simple RESTful web service can be defined using @Path:

@Path("orders")
public class Orders {
  @GET
  public List<Order> getAll() { 
    //. .  .
  }

  @GET
  @Path("{oid}")
  public Order getOrder(@PathParam("oid")int id) {
    //. . . 
  }
}

@XmlRootElement
public class Order {
  int id;
  //. . .
}

In this code:

  • Orders is a POJO class and is published as a RESTful resource at orders path by adding the class-level @Path annotation.

  • The Order class is marked with the @XmlRootElement annotation, allowing a conversion between Java and XML.

  • The getAll resource method, providing a list of all orders, is invoked when this resource is accessed using the HTTP GET method; this is identified by specifying the @GET annotation on the method.

  • The @Path annotation on the getOrder resource method marks it as a subresource and accessible at orders/{oid}.

  • The curly braces around oid identifies it as a template parameter, and binds its value at runtime to the id parameter of the getOrder resource method.

  • The @PathParam can also be used to bind template parameters to a resource class field as well.

Typically, a RESTful resource is bundled in a .war file along with other classes and resources. The Application class and @ApplicationPath annotation is used to specify the base path for all the RESTful resources in the packaged archive. The Application class also provides additional metadata about the application.

Let’s say this POJO is packaged in the store.war file, deployed at localhost:8080, and the Application class is defined:

@ApplicationPath("webresources")
public class ApplicationConfig extends Application {
}

A list of all the orders is accessible by issuing a GET request to:

http://localhost:8080/store/webresources/orders

A specific order can be obtained by issuing a GET request to:

http://localhost:8080/store/webresources/orders/1

Here, the value 1 will be passed to getOrder’s method parameter id. The resource method will locate the order with the correct order number and return back the Order class. Having @XmlRootElement annotation ensures that an XML representation of the resource is returned back.

A URI may pass HTTP query parameters using name/value pairs. These can be mapped to resource method parameters or fields using @QueryParam annotation. If the resource method getAll is updated such that the returned results start from a specific order number, the number of orders returned can also be specified:

public List<Order> getAll(@QueryParam("start")int from, 
                          @QueryParam("page")int page) {
  //. . .
}

And the resource is accessed as:

http://localhost:8080/store/webresources/orders?
    start=10&page=20

Then 10 is mapped to the from parameter and 20 is mapped to the page parameter.

Binding HTTP Methods

JAX-RS provides support for binding standard HTTP GET, POST, PUT, DELETE, HEAD, and OPTIONS methods using the corresponding annotations described in Table 9-1.

Table 9-1. HTTP methods supported by JAX-RS

HTTP methodJAX-RS annotation
GET@GET
POST@POST
PUT@PUT
DELETE@DELETE
HEAD@HEAD
OPTIONS@OPTIONS

Let’s take a look at how @POST is used. Consider the following HTML form, which takes the order identifier and customer name and creates an order by posting the form to webresources/orders/create:

<form method="post" action="webresources/orders/create">
  Order Number: <input type="text" name="id"/><br/>
  Customer Name: <input type="text" name="name"/><br/>
  <input type="submit" value="Create Order"/>
</form>

The updated resource definition uses the following annotations:

@POST
@Path("create")
@Consumes("application/x-www-form-urlencoded")
public Order createOrder(@FormParam("id")int id, 
                         @FormParam("name")String name) {
  Order order = new Order();
  order.setId(id);
  order.setName(name);
  return order;
}

The @FormParam annotation binds the value of an HTML form parameter to a resource method parameter or a field. The name attribute in the HTML form and the value of the @FormParam annotation are exactly the same to ensure the binding. Clicking the submit button in this form will return the XML representation of the created Order. A Response object may be used to create a custom response.

The following code shows how @PUT is used:

@PUT
@Path("{id}")
@Consumes("*/xml")
public Order putXml(@PathParam("id")int id, 
                    String content) {
  Order order = findOrder(id);
  // update order from "content"
  . . .
  return order;
}

The resource method is marked as a subresource and {id} is bound to the resource method parameter id. The contents of the body can be any XML media type as defined by @Consumes and are bound to the content method parameter. A PUT request to this resource may be issued as:

curl -i -X PUT -d "New Order" 
    http://localhost:8080/store/webresources/orders/1

The content method parameter will have the value New Order.

Similarly, an @DELETE resource method can be defined:

@DELETE
@Path("{id}")
public void putXml(@PathParam("id")int id) {
  Order order = findOrder(id);
  // delete order
}

The resource method is marked as a subresource and {id} is bound to the resource method parameter id. A DELETE request to this resource may be issued as:

curl -i -X DELETE 
    http://localhost:8080/store/webresources/orders/1

The content method parameter will have the value New Order.

The HEAD and OPTIONS methods receive automated support from JAX-RS.

The HTTP HEAD method is identical to GET except that no response body is returned. This method is typically used to obtain meta-information about the resource without requesting the body. The set of HTTP headers in response to a HEAD request is identical to the information sent in response to a GET request. If no method is marked with @HEAD, an equivalent @GET method is called and the response body is discarded. The @HEAD annotation is used to mark a method serving HEAD requests:

@HEAD
@Path("{id}")
public void headOrder(@PathParam("id")int id) {
  System.out.println("HEAD");
}

This method is often used for testing hypertext links for validity, accessibility, and recent modification. A HEAD request to this resource may be issued as:

curl -i -X HEAD 
    http://localhost:8080/store/webresources/orders/1

The HTTP response header contains HTTP/1.1 204 No Content and no content body.

The HTTP OPTIONS method requests for communication options available on the request/response identified by the URI. If no method is designated with @OPTIONS, the JAX-RS runtime generates an automatic response using the annotations on the matching resource class and methods. The default response typically works in most cases. @OPTIONS may be used to customize the response to the OPTIONS request:

@OPTIONS
@Path("{id}")
  public Response options() {
  // create a custom Response and return
}

An OPTIONS request to this resource may be issued as:

curl -i -X OPTIONS 
    http://localhost:8080/store/webresources/orders/1

The HTTP Allow response header provides information about the HTTP operations permitted. The Content-Type header is used to specify the media type of the body, if any is included.

In addition to the standard set of methods supported with corresponding annotations, HttpMethod may be used to build extensions such as WebDAV.

Multiple Resource Representations

By default, a RESTful resource is published or consumed with */* MIME type. A RESTful resource can restrict the media types supported by request and response using the @Consumes and @Produces annotations, respectively. These annotations may be specified on the resource class or a resource method. The annotation specified on the method overrides any on the resource class.

Here is an example showing how Order can be published using multiple MIME types:

@GET
@Path("{oid}")
@Produces({"application/xml", "application/json"})
public Order getOrder(@PathParam("oid")int id) { . . . }

The resource method can generate an XML or JSON representation of Order. The exact return type of the response is determined by the HTTP Accept header in the request.

Wildcard pattern matching is supported as well. The following resource method will be dispatched if the HTTP Accept header specifies any application MIME type such as application/xml, application/json, or any other media type:

@GET
@Path("{oid}")
@Produces("application/*")
public Order getOrder(@PathParam("oid")int id) { . . . }

Here is an example of how multiple MIME types may be consumed by a resource method:

@POST
@Path("{oid}")
@Consumes({"application/xml", "application/json"})
public Order getOrder(@PathParam("oid")int id) { . . . }

The resource method invoked is determined by the HTTP Content-Type header of the request.

A mapping between a custom representation and a corresponding Java type can be defined by implementing the MessageBodyReader and MessageBodyWriter interfaces and annotating with @Provider.

Binding Request to a Resource

By default, a new resource is created for each request to access the resource. The resource method parameters, fields, or bean properties are bound using xxxParam annotations during object creation time. In addition to @PathParam and @QueryParam, the following annotations can be used to bind different parts of the request to a resource method parameter, field, or bean property:

  • @CookieParam binds the value of a cookie:

    public Order getOrder(
        @CookieParam("JSESSIONID")String sessionid) {
      //. . .
    }

    This code binds the value of the "JSESSIONID" cookie to the resource method parameter sessionid.

  • @HeaderParam binds the value of an HTTP header:

    public Order getOrder(
        @HeaderParam("Accept")String accept) {
      //. . .
    }
  • @FormParam binds the value of a form parameter contained within a request entity body. Its usage is displayed in an earlier section.

  • @MatrixParam binds the name/value parameters in the URI path:

    public List<Order> getAll(
        @MatrixParam("start")int from, 
        @MatrixParam("page")int page) {
      //. . .
    }

    And the resource is accessed as:

    http://localhost:8080/store/webresources/orders;
    start=10;
    page=20

    Then 10 is mapped to the from parameter and 20 is mapped to the page parameter.

More details about the application deployment context and the context of individual requests can be obtained using the @Context annotation.

Here is an updated resource definition where more details about the request context are displayed before the method is invoked:

@Path("orders")
public class Orders {

  @Context Application app;
  @Context UriInfo uri; 
  @Context HttpHeaders headers;
  @Context Request request;

  @Context SecurityContext security;
  @Context Providers providers;

  @GET
  @Produces("application/xml")
  public List<Order> getAll(@QueryParam("start")int from, 
                            @QueryParam("end")int to) {
    //. . .(app.getClasses());
    //. . .(uri.getPath());
    //. . .(headers.getRequestHeader(
        HttpHeaders.ACCEPT));
    //. . .(headers.getCookies());
    //. . .(request.getMethod());
    //. . .(security.isSecure());
    //. . .
  }
}

In this code:

  • UriInfo provides access to application and request URI information.

  • Application provides access to application configuration information.

  • HttpHeaders provides access to HTTP header information either as a Map or convenience methods. Note that @HeaderParam can also be used to bind an HTTP header to a resource method parameter, field, or bean property.

  • Request provides a helper to request processing and is typically used with Response to dynamically build the response.

  • SecurityContext provides access to security-related information of the current request.

  • Providers supplies information about runtime lookup of provider instances based on a set of search criteria.

Mapping Exceptions

An application-specific exception may be thrown from within the resource method and propagated to the client. The application can supply checked or exception mapping to an instance of the Response class. Let’s say the application throws the following exception if an order is not found:

public class OrderNotFoundException 
    extends RuntimeException {

  public OrderNotFoundException(int id) {
    super(id + " order not found");
  }
    
}

The method getOrder may look like:

@Path("{id}")
public Order getOrder(@PathParam("id")int id) {
  Order order = null;
  if (order == null) {
    throw new OrderNotFoundException(id);
  }
  //. . .
  return order;  
}

The exception mapper will look like:

@Provider
public class OrderNotFoundExceptionMapper 
    implements ExceptionMapper<OrderNotFoundException> {

  @Override
  public Response toResponse(
OrderNotFoundException exception) {
    return Response
       .status(Response.Status.PRECONDITION_FAILED)
       .entity("Response not found")
       .build();
  }
    
}

This ensures that the client receives a formatted response instead of just the exception being propagated from the resource.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.166.31