Chapter 2. Create a RESTful web service in a servlet container

2.1.  Create a web service using JAX-RS, refer to Jersey implementation for examples

2.1.1.  Annotate a class with a @Path annotation to respond to URI templates.

blah-blah

2.1.2.  Annotate the class's methods to respond to HTTP requests using the corresponding JAX-RS annotations (@GET, @POST, etc.).

blah-blah

2.1.3.  Use the JAX-RS @Consumes and @Produces annotations to specify the input and output formats for the RESTful web service.

blah-blah

2.1.4.  Use @PathParam, @QueryParam, @MatrixParam and @HeaderParam to extract request data.

@javax.ws.rs.PathParam

@PathParam allows you to inject the value of named URI path parameters that were defined in @Path expressions:

@Path("/employee")
public class EmployeeResource {

    @Path("{id}")
    @GET
    public Employee getEmployee(@PathParam("id") int id) {
        ...
    }
}
						

In the example above, we want to route HTTP GET requests to the relative URI pattern /employee/{id}. The getEmployee(...) method extracts an Employee ID from the URI using @PathParam annotation. The value of the @PathParam annotation, "id", matches the path parameter, {id}, that was defined in the @Path expression of getEmployee(...).

While {id} represents a string segment in the request's URI, JAX-RS runtime automatically converts the value to an int before it invokes the getEmployee(...) method. If the URI path parameter cannot be converted to an integer, the request is considered a client error and the client will receive an HTTP 404, "Not Found" status code from the server.

@javax.ws.rs.QueryParam

The @QueryParam annotation allows you to inject individual URI query parameters into your Java parameters. For example, let's say you wanted to query a employee database and retrieve a subset of all employees in the database. Your URI might look like this:

GET /employees?start=30&size=10
						

The start query parameter represents the employee index you want to start with and the size query parameter represents how many employees you want returned. The JAX-RS service that implemented this might look like this:

@Path("/employees")
public class EmployeeResource {

    @GET
    @Produces("application/xml")
    public String getEmployees(@QueryParam("start") int start,
                               @QueryParam("size")  int size) {
        ...
    }
}
						

The @QueryParam annotation was used to inject the URI query parameters "start" and "size" into the Java parameters start and size. As with other annotation injection, JAX-RS runtime automatically converts the query parameter's string into an integer.

@javax.ws.rs.MatrixParam

The @MatrixParam annotation extracts information from URL path segments.

@javax.ws.rs.HeaderParam

The @HeaderParam annotation is used to inject HTTP request header values. For example, what if your application was interested in the web page that referred to or linked to your web service? You could access the HTTP Referer header using the @HeaderParam annotation:

@Path("/myresource")
public class MyResource {

    @GET
    public String getData(@HeaderParam("Referer") String referer) {
        ...
    }
}
						

The @HeaderParam annotation is pulling the Referer header directly from the HTTP request and injecting it into the referer method parameter.

2.1.5.  Use the UriInfo and UriBuilder to create URIs that refer to resources in the service.

A very important aspects of REST is hyperlinks, URIs, in representations that clients can use to transition the web service to new application states (this is otherwise known as "hypermedia as the engine of application state"). HTML forms present a good example of this in practice.

Using Java API for RESTful Web Services (JAX-RS), you can use the UriInfo object to access request headers. The UriInfo object provides methods to enable you to find or build URI information of a request.

Using an injected UriInfo object by the JAX-RS runtime environment, the relative and absolute uniform resource identifier (URI) information is known and available for modification. The @javax.ws.rs.core.Context annotation indicates that a context object is injected. The javax.ws.rs.core.UriInfo interface is the interface of the object that you want to inject. You can use the UriInfo object to build absolute and relative URLs using the UriBuilder class.

If a resource method signature can be modified, add the @javax.ws.rs.core.Context javax.ws.rs.core.UriInfo parameter to the method. When the resource method is invoked, the JAX-RS runtime environment passes an object that implements the UriInfo object; for example:

@Path("/resource")
public class RootResource {

    @GET
    public String getResource(@Context UriInfo uriInfo) {

        // Client used this URI to reach this resource method
        return  uriInfo.getAbsolutePath().toASCIIString();

    }
}
						

If a resource method signature cannot be modified and the class is a root resource, add the @javax.ws.rs.core.Context javax.ws.rs.core.UriInfo field. When the resource is instantiated for a request, an object that implements UriInfo is injected; for example:

@Path("/contextexample")
public class RootResource {

    @Context
    UriInfo uriInfo;

    @GET
    public String getResource() {

        // Client used this URI to reach this resource method
        return  uriInfo.getAbsolutePath().toASCIIString();

    }
}
						

Building URIs and building them safely is not easy with java.net.URI, which is why JAX-RS has the UriBuilder class that makes it simple and easy to build URIs safely.

UriBuilder can be used to build new URIs or build from existing URIs. For resource classes it is more than likely that URIs will be built from the base URI the web service is deployed at or from the request URI. The class UriInfo provides such information (in addition to further information, see next section).

The following example shows URI building with UriInfo and UriBuilder:

@Path("/users/")
public class UsersResource {

    @Context UriInfo uriInfo;

    ...

    @GET
    @Produces("application/json")
    public JSONArray getUsersAsJsonArray() {
        JSONArray uriArray = new JSONArray();
        for (UserEntity userEntity : getUsers()) {
            UriBuilder ub = uriInfo.getAbsolutePathBuilder();
            URI userUri = ub
                .path(userEntity.getUserid())
                .build();
            uriArray.put(userUri.toASCIIString());
        }
        return uriArray;
    }
}
						

An instance of UriInfo can be injected into a class field or method parameter using the @Context annotation. UriInfo provides both static and dynamic, per-request information, about the components of a request URI. E.g. the following would return the names of any query parameters in a request:

@GET
@Produces{"text/plain"}
public String listQueryParamNames(@Context UriInfo info) {
    StringBuilder buf = new StringBuilder();
    for (String param: info.getQueryParameters().keySet()) {
        buf.append(param);
        buf.append("\n");
    }
    return buf.toString();
}
						

UriInfo can be used to obtain URIs and associated UriBuilder instances for the following URIs: the base URI the application is deployed at; the request URI; and the absolute path URI, which is the request URI minus any query components.

The getUsersAsJsonArray method constructs a JSONArrray where each element is a URI identifying a specific user resource. The URI is built from the absolute path of the request URI by calling uriInfo.getAbsolutePathBuilder(). A new path segment is added, which is the user ID, and then the URI is built. Notice that it is not necessary to worry about the inclusion of '/' characters or that the user ID may contain characters that need to be percent encoded. UriBuilder takes care of such details.

UriBuilder can be used to build/replace query or matrix parameters. URI templates can also be declared, for example the following will build the URI:

http://localhost/segment?name=value
						

UriBuilder.fromUri("http://localhost/").
    path("{a}").
    queryParam("name", "{value}").
    build("segment", "value");
						

2.1.6.  Use ResponseBuilder to create response with customized status and additional metadata.

The HTTP specification defines what HTTP response codes should be on a successful request. For example, GET should return 200, OK and PUT should return 201, CREATED. You can expect JAX-RS to return the same default response codes.

Sometimes, however, you need to specify your own response codes, or simply to add specific headers or cookies to your HTTP response. JAX-RS provides a Response class for this:

@Path("/orders")
public class OrderEntryService {
    @GET
    @Path("{id}")
    public Response getOrder(@PathParm("id") int orderId) {
        Order order = OrderDAO.get(orderId);
        ResponseBuilder builder = Response.ok(order);
        Date expirationDate =  new Date(System.currentTimeMillis() + 3000); // 3 secs
        builder.expires(expirationDate);
        return builder.build();
    }
}
						

In this example, we still want to return a JAXB object with a 200 status code, but we want to add an Expires header to the response. You use the ResponseBuilder class to build up the response, and ResponseBuilder.build() to create the final Response instance.

2.1.7.  Implement a MessageBodyReader and MessageBodyWriter to add support for custom request and response data types

Java API for RESTful Web Services (JAX-RS) enables developers to add a custom entity provider to the application. Use custom entity providers when you want to use Java types to represent incoming request message bodies as well as represent outgoing response message bodies. By adding a custom entity provider, you can deserialize custom Java types from message bodies and serialize any media type as message bodies.

A custom entity provider is created by annotating a class with a javax.ws.rs.ext.Provider annotation. The class must implement the javax.ws.rs.ext.MessageBodyReader interface or the javax.ws.rs.ext.MessageBodyWriter interface, or both. You must add the provider class to the list of classes returned in the javax.ws.rs.core.Application subclass getClasses() method.

The MessageBodyReader interface represents a contract for a provider that supports the conversion of a stream to a Java type. To add a MessageBodyReader implementation, annotate the implementation class with @Provider. A MessageBodyReader implementation may be annotated with @Consumes to restrict the media types for which it will be considered suitable:


package javax.ws.rs.ext;

import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;

public interface MessageBodyReader<T extends Object> {

    public boolean isReadable(Class<?> type,
        Type genericType,
        Annotation[] annotations,
        MediaType mediaType);

    public T readFrom(Class<T> type,
        Type genericType,
        Annotation[] annotations,
        MediaType mediaType,
        MultivaluedMap<String, String> httpHeaders,
        InputStream entityStream) throws IOException, WebApplicationException;
}

						

The MessageBodyWriter interface represents a contract for a provider that supports the conversion of a Java type to a stream. To add a MessageBodyWriter implementation, annotate the implementation class with @Provider. A MessageBodyWriter implementation may be annotated with Produces to restrict the media types for which it will be considered suitable:


package javax.ws.rs.ext;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;

public interface MessageBodyWriter<T extends Object> {

    public boolean isWriteable(Class<?> type,
        Type genericType,
        Annotation[] annotations,
        MediaType mediaType);

    public long getSize(T t,
        Class<?> type,
        Type genericType,
        Annotation[] annotations,
        MediaType mediaType);

    public void writeTo(T t,
        Class<?> type,
        Type genericType,
        Annotation[] annotations,
        MediaType mediaType,
        MultivaluedMap<String, Object> httpHeaders,
        OutputStream entityStream) throws IOException, WebApplicationException;
}

						

  1. Create a new Java class that is your custom entity provider. In this procedure, the example code creates a reader and a writer for by.boot.java.Order types so that you can use the by.boot.java.Order type as an incoming request entity parameter and as a return type to contain the response entity:

    public class OrderEntityProvider {
    
    }
    									

  2. Add the @javax.ws.rs.ext.Provider annotation. Adding this annotation indicates to the JAX-RS runtime environment that this class is a JAX-RS provider. If this Provider annotation is not specified, the runtime environment does not detect that the class is a custom provider:

    @javax.ws.rs.ext.Provider
    public class OrderEntityProvider {
    
    }
    									

  3. [optional] Add a @javax.ws.rs.Consumes and/or @javax.ws.rs.Produces annotation if you want to limit the media types that the entity provider supports. In the following code snippet, the provider is only invoked when the incoming Content-Type or outgoing Content-Type is application/json:

    @javax.ws.rs.ext.Provider
    @javax.ws.rs.Consumes("application/json")
    @javax.ws.rs.Produces("application/json")
    public class OrderEntityProvider {
    
    }
    									

  4. Implement javax.ws.rs.ext.MessageBodyReader<T> if the entity provider needs to deserialize a message body.

    You can use the generic type <T> to limit the types supported by the entity provider.

    By defining the message body reader as javax.ws.rs.ext.MessageBodyReader<by.boot.java.Order>, the JAX-RS runtime environment knows that only by.boot.java.Order objects can be produced:

    
    @javax.ws.rs.ext.Provider
    @javax.ws.rs.Consumes("application/json")
    @javax.ws.rs.Produces("application/json")
    public class OrderEntityProvider implements MessageBodyReader<by.boot.java.Order> {
    
        public boolean isReadable(Class<?> type,
                                  Type genericType,
                                  Annotation[] annotations,
                                  MediaType mediaType) {
            return by.boot.java.Order.class == type;
        }
    
    
        public by.boot.java.Order readFrom(Class<by.boot.java.Order> type,
                                           Type genericType,
                                           Annotation[] annotations,
                                           MediaType mediaType,
                                           MultivaluedMap<String, String> httpHeaders,
                                           InputStream entityStream) throws IOException, WebApplicationException {
    
            // This code reads from the entityStream and constructs the object.
            by.boot.java.Order retObj = by.boot.java.Order.parse(entityStream);
            return retObj;
        }
    }
    
    									

    If an entity provider needs to support a complex set of types, consider implementing javax.ws.rs.ext.MessageBodyReader<Object>.

  5. Implement javax.ws.rs.ext.MessageBodyWriter<T> if the entity provider needs to serialize a message body. You can implement the MessageBodyReader<T> and MessageBodyWriter<T> interfaces in the same Java class. In the following code snippet, only the MessageBodyWriter implementation is shown:

    
    @javax.ws.rs.ext.Provider
    public class OrderEntityProvider implements MessageBodyWriter<by.boot.java.Order> {
    
        public long getSize(by.boot.java.Order t,
                            Class<?> type,
                            Type genericType,
                            Annotation[] annotations,
                            MediaType mediaType) {
    
            // Return -1 if the content length cannot be determined
            return -1;
        }
    
        public boolean isWriteable(Class<?> type,
                                   Type genericType,
                                   Annotation[] annotations,
                                   MediaType mediaType) {
            return by.boot.java.Order.class == type;
        }
    
        public void writeTo(by.boot.java.Order t,
                            Class<?> type,
                            Type genericType,
                            Annotation[] annotations,
                            MediaType mediaType,
                            MultivaluedMap<String, Object> httpHeaders,
                            OutputStream entityStream) throws IOException, WebApplicationException {
            entityStream.write(t.getBytes());
        }
    }
    
    									

  6. Add the custom entity provider to the javax.ws.rs.core.Application subclass and add the provider to the set of classes returned from the getClasses() method. You can add multiple custom entity providers to the set of classes returned:

    
    public class MyApplication extends javax.ws.rs.core.Application {
        public Set<Class<?>> getClasses() {
            Set<Class<?>> classes = new HashSet<Class<?>>();
            classes.add(OrderEntityProvider.class);
            return classes;
        }
    }
    
    									

2.1.8.  Implement ExceptionMapper to map a custom Exception to a response.

JAX-RS has a RuntimeException class, WebApplicationException, that allows you to abort your JAX-RS service method. It can take an HTTP status code or even a Response object as one of its constructor parameters. For example:


@Path("/orders")
public class OrderEntryService {
    @GET
    @Path("{id}")
    @Produces("application/xml")
    public Order getOrder(@PathParm("id") int orderId) {
        Order order = OrderDAO.get(orderId);
        if (order == null) {
            ResponseBuilder builder = Response.status(Status.NOT_FOUND);
            builder.type("text/html");
            builder.entity("<h3>Order Not Found</h3>");
            throw new WebApplicationException(builder.build();
        }
        return order;
    }
}

						

In this example, if the order is null, send a HTTP response code of NOT_FOUND with a HTML encoded error message.

Beyond WebApplicationException, you can map non-JAX-RS exceptions that might be thrown by your application to a Response object by registering implementations of the ExceptionMapper class:


public interface ExceptionMapper<E extends Throwable> {
    Response toResponse(E exception);
}

						

For example, lets say we were using JPA to locate our Order objects. We could map javax.persistence.EntityNotFoundException to return a NOT_FOUND status code:


@Provider
public class EntityNotFoundMapper implements ExceptionMapper<EntityNotFoundException> {
    Response toResponse(EntityNotFoundException exception) {
        return Response.status(Status.NOT_FOUND);
    }
}

						

or


@Provider
public class EntityNotFoundMapper implements ExceptionMapper<EntityNotFoundException> {
    Response toResponse(EntityNotFoundException exception) {
        return Response.status(Status.NOT_FOUND).
            entity(exception.getMessage()).
            type("text/plain").
            build();
    }
}

						

The above class is annotated with @Provider, this declares that the class is of interest to the JAX-RS runtime. Such a class should be added to the set of classes of the Application instance that is configured. When an application throws an EntityNotFoundException the toResponse method of the EntityNotFoundMapper instance will be invoked.

2.1.9.  Use Request to add support for HTTP preconditions.

blah-blah

2.1.10.  Implement the functionality of the JAX-RS resource's methods.

blah-blah

2.1.11.  Use @Path on a method to define a subresource.

Methods of a resource class that are annotated with @Path are either sub-resource methods or sub-resource locators. Sub-resource methods handle an HTTP request directly whilst sub-resource locators return an object that will handle an HTTP request. The presence or absence of a request method designator (e.g. @GET) differentiates between the two:

  • Request method designator (@GET, @POST) presents:

    Such methods, known as sub-resource methods, are treated like a normal resource method except the method is only invoked for request URIs that match a URI template created by concatenating the URI template of the resource class with the URI template of the method.

    Example:

    @Path("widgets")
    public class WidgetsResource {
    
        @GET
        @Path("offers")
        public WidgetList getDiscounted() {...}
    
    }
    									

    In the above a GET request for the widgets/offers resource is handled directly by the getDiscounted() sub-resource method of the resource class WidgetsResource.

  • Request method designator (@GET, @POST) absent:

    Such methods, known as sub-resource locators, are used to dynamically resolve the object that will handle the request. Any returned object is treated as a resource class instance and used to either handle the request or to further resolve the object that will handle the request. An implementation MUST dynamically determine the class of object returned rather than relying on the static sub-resource locator return type since the returned instance may be a subclass of the declared type with potentially different annotations. Sub-resource locators may have all the same parameters as a normal resource method except that they MUST NOT have an entity parameter.

    Example:

    @Path("widgets")
    public class WidgetsResource {
    
        @Path("{id}")
        public WidgetSubResource findWidget(@PathParam("id") String id) {
            return new WidgetSubResource(id);
        }
    }
    									

    public class WidgetSubResource {
    
        public WidgetSubResource(String id) {...}
    
        @GET
        public Widget getDetails() {...}
    
    }
    									

    In the above a GET request for widgets/xxx is handled by the getDetails() method of the WidgetSubResource resource class.

2.1.12.  Configure deployment descriptor (web.xml) for base URL pattern, HTTP security (via security-constraints in web.xml)

blah-blah

2.1.13.  Compile and package

blah-blah

2.1.14.  Deploy the web service in a Java EE servlet container

blah-blah

Professional hosting         'Oracle Certified Expert Web Services Developer 6' Quiz     Free SCDJWS 5.0 Guide


webhosting provided by ixvar inc