Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (15.81 MB, 597 trang )
Chapter 15 ■ RESTful Web Services
Table 15-4. Main JAX-RS Packages
Package
Description
javax.ws.rs
High-level interfaces and annotations used to create RESTful web service
javax.ws.rs.client
Classes and interfaces of the new JAX-RS client API
javax.ws.rs.container
Container-specific JAX-RS API
javax.ws.rs.core
Low-level interfaces and annotations used to create RESTful web resources
javax.ws.rs.ext
APIs that provide extensions to the types supported by the JAX-RS API
Reference Implementation
Jersey is the reference implementation of JAX-RS. It is an open source project under dual CDDL and GPL licenses.
Jersey also provides a specific API so that you can extend Jersey itself.
Other implementations of JAX-RS are also available such as CXF (Apache), RESTEasy (JBoss), and Restlet
(a senior project that existed even before JAX-RS was finalized).
Writing RESTful Web Services
Some of the low-level concepts (such as the HTTP protocol) might have you wondering how the code would look
when developing a RESTful web service. The good news is that you don’t have to write plumbing code to digest HTTP
requests, nor create HTTP responses by hand. JAX-RS is a very elegant API allowing you to describe a RESTful web
service with only a few annotations. RESTful web services are POJOs that have at least one method annotated with @
javax.ws.rs.Path. Listing 15-3 shows a typical resource.
Listing 15-3. A Simple Book RESTful Web Service
@Path("/book")
public class BookRestService {
@GET
@Produces("text/plain")
public String getBookTitle() {
return "H2G2";
}
}
The BookRestService is a Java class annotated with @Path, indicating that the resource will be hosted at the
URI path /book. The getBookTitle() method is marked to process HTTP GET requests (using @GET annotation)
and produces text (the content is identified by the MIME Media text/plain; I could have also used the constant
MediaType.TEXT_PLAIN). To access this resource, you need an HTTP client such as a browser to point to the URL
http://www.myserver.com/book.
JAX-RS is HTTP-centric by nature and has a set of clearly defined classes and annotations to deal with HTTP
and URIs. A resource can have several representations, so the API provides support for a variety of content types and
uses JAXB to marshall and unmarshall XML representations from/into objects. JAX-RS is also independent of the
container, so resources can be deployed in GlassFish, of course, but also in a variety of servlet containers.
508
www.it-ebooks.info
Chapter 15 ■ RESTful Web Services
Anatomy of a RESTful Web Service
From Listing 15-3, you can see that the REST service doesn’t implement any interface nor extend any class; the only
mandatory annotation to turn a POJO into a REST service is @Path. JAX-RS relies on configuration by exception, so it
has a set of annotations to configure the default behavior. Following are the requirements to write a REST service:
•
The class must be annotated with @javax.ws.rs.Path (in JAX-RS 2.0 there is no XML
equivalent to meta-data as there is no deployment descriptor).
•
The class must be defined as public, and it must not be final or abstract.
•
Root resource classes (classes with a @Path annotation) must have a default public constructor.
Non-root resource classes do not require such a constructor.
•
The class must not define the finalize() method.
•
To add EJB capabilities to a REST service, the class has to be annotated with
@javax.ejb.Stateless or @javax.ejb.Singleton (see Chapter 7).
•
A service must be a stateless object and should not save client-specific state across method calls.
CRUD Operations on a RESTful Web Service
Listing 15-3 shows how to write a very simple REST service that returns a String. But most of the time you need to
access a database, retrieve or store data in a transactional manner. For this you can have a REST service and add
stateless session beans functionalities by adding the @Stateless annotation. This will allow transactional access to a
persistent layer (JPA entities), as shown in Listing 15-4.
Listing 15-4. A Book RESTful Web Service Creating, Deleting, and Retrieving Books from the Database
@Path("book")
@Stateless
public class BookRestService {
@Context
private UriInfo uriInfo;
@PersistenceContext(unitName = "chapter15PU")
private EntityManager em;
@GET
@Produces(MediaType.APPLICATION_XML)
public Books getBooks() {
TypedQuery
Books books = new Books(query.getResultList());
return books;
}
@POST
@Consumes(MediaType.APPLICATION_XML)
public Response createBook(Book book) {
em.persist(book);
URI bookUri = uriInfo.getAbsolutePathBuilder().path(book.getId().toString()).build();
return Response.created(bookUri).build();
}
509
www.it-ebooks.info
Chapter 15 ■ RESTful Web Services
@DELETE
@Path("{id}")
public Response deleteBook(@PathParam("id") Long bookId) {
em.remove(em.find(Book.class, bookId));
return Response.noContent().build();
}
}
The code in Listing 15-4 represents a REST service that can consume and produce an XML representation of
a book. The getBooks() method retrieves the list of books from the database and returns an XML representation
(using content negotiation) of this list, accessible through a GET method. The createBook() method takes an XML
representation of a book and persists it to the database. This method is invoked with an HTTP POST and returns a
Response with the URI (bookUri) of the new book as well as the created status. The deleteBook method takes a book
id as a parameter and deletes it from the database.
The code in Listing 15-4 follows a very simple JAX-RS model and uses a set of powerful annotations. Let’s now
take a deeper look at all the concepts shown in the code.
URI Definition and Binding URIs
The @Path annotation represents a relative URI that can annotate a class or a method. When used on classes, it is
referred to as the root resource, providing the root of the resource tree and giving access to subresources. Listing 15-5
shows a REST service that can be access at http://www.myserver.com/items. All the methods of this service will have
/items as root.
Listing 15-5. Root Path to an Item Resource
@Path("/items")
public class ItemRestService {
@GET
public Items getItems() {
// ...
}
}
You can then add subpaths to your methods, which can be useful to group together common functionalities for
several resources as shown in Listing 15-6 (you may ignore for the moment the @GET, @POST, and @DELETE annotations
in the listing, as they will be described later in the “HTTP Method Matching” section).
Listing 15-6. Several Subpaths in the ItemRestService
@Path("/items")
public class ItemRestService {
@GET
public Items getItems() {
// URI : /items
}
510
www.it-ebooks.info
s
@GET
@Path("/cds")
public CDs getCDs() {
// URI : /items/cds
}
@GET
@Path("/books")
public Books getBooks() {
// URI : /items/books
}
@POST
@Path("/book")
public Response createBook(Book book) {
// URI : /items/book
}
}
Listing 15-6 represents a RESTful web service that will give you methods to get all the items (CDs and books) from
the CD-BookStore Application. When requesting the root resource /items, the only method without sub @Path will
be selected (getItems()). Then, when @Path exists on both the class and method, the relative path to the method is a
concatenation of both. For example, to get all the CDs, the path will be /items/cds. When requesting /items/books,
the getBooks() method will be invoked. To create a new book you need to point at /items/book.
If @Path("/items") only existed on the class, and not on any methods, the path to access each method would
be the same. The only way to differentiate them would be the HTTP verb (GET, PUT, etc.) and the content negotiation
(text, XML, etc.), as you’ll later see.
Extracting Parameters
Having nice URIs by concatenating paths to access your resource is very important in REST. But paths and subpaths
are not enough: you also need to pass parameters to your RESTful web services, extract and process them at runtime.
Listing 15-4 showed how to get a parameter out of the path with @javax.ws.rs.PathParam. JAX-RS provides a rich set
of annotations to extract the different parameters that a request could send (@PathParam, @QueryParam, @MatrixParam,
@CookieParam, @HeaderParam, and @FormParam).
Listing 15-7 shows how the @PathParam annotation is used to extract the value of a URI template parameter.
A parameter has a name and is represented by a variable between curly braces or by a variable that follows a regular
expression. The searchCustomers method takes any String parameter while getCustomerByLogin only allows
lowercase/uppercase alphabetical letters ([a-zA-Z]*) and getCustomerById only digits (\\d+).
Listing 15-7. Extracting Path Parameters and Regular Expressions
@Path("/customer")
@Produces(MediaType.APPLICATION_JSON)
public class CustomerRestService {
@Path("search/{text}")
public Customers searchCustomers(@PathParam("text") String textToSearch) {
// URI : /customer/search/smith
}
511
www.it-ebooks.info
Chapter 15 ■ RESTful Web Services
@GET
@Path("{login: [a-zA-Z]*}")
public Customer getCustomerByLogin(@PathParam("login") String login) {
// URI : /customer/foobarsmith
}
@GET
@Path("{customerId : \\d+}")
public Customer getCustomerById(@PathParam("customerId") Long id) {
// URI : /customer/12345
}
}
The @QueryParam annotation extracts the value of a URI query parameter. Query parameters are key/value pairs
separated by an & symbol such as http://www.myserver.com/customer?zip=75012&city=Paris. The @MatrixParam
annotation acts like @QueryParam, except it extracts the value of a URI matrix parameter (; is used as a delimiter
instead of ?). Listing 15-8 shows how to extract both query and matrix parameters from URIs.
Listing 15-8. Extracting Query and Matrix Parameters
@Path("/customer")
@Produces(MediaType.APPLICATION_JSON)
public class CustomerRestService {
@GET
public Customers getCustomersByZipCode(@QueryParam("zip") Long zip,
@QueryParam("city") String city) {
// URI : /customer?zip=75012&city=Paris
}
@GET
@Path("search")
public Customers getCustomersByName(@MatrixParam("firstname") String firstname,
@MatrixParam("surname") String surname) {
// URI : /customer/search;firstname=Antonio;surname=Goncalves
}
}
Two other annotations are related to the innards of HTTP, things you don’t see directly in URIs: cookies and
HTTP headers @CookieParam extracts the value of a cookie, while @HeaderParam extracts the value of a header field.
Listing 15-9 extracts the session ID from the cookie and the User Agent from the HTTP header.
Listing 15-9. Extracting Values From the Cookie and HTTP Header
@Path("/customer")
@Produces(MediaType.TEXT_PLAIN)
public class CustomerRestService {
@GET
public String extractSessionID(@CookieParam("sessionID") String sessionID) {
// ...
}
512
www.it-ebooks.info
Chapter 15 ■ RESTful Web Services
@GET
public String extractUserAgent(@HeaderParam("User-Agent") String userAgent) {
// ...
}
}
The @FormParam annotation specifies that the value of a parameter is to be extracted from a form in a request
entity body. @FormParam is not required to be supported on fields or properties.
With all these annotations, you can add a @DefaultValue annotation to define the default value for a parameter
you’re expecting. The default value is used if the corresponding parameter is not present in the request. Listing 15-10
sets default values to query and matrix parameters. For example, in the method getCustomersByAge, if the query
parameter age is not in the request, the default value is set to 50.
Listing 15-10. Defining Default Values
@Path("/customer")
public class CustomerRestService {
@GET
public Customers getCustomersByAge(@DefaultValue("50") @QueryParam("age") int age) {
// ...
}
@GET
public Customers getCustomersByCity(@DefaultValue("Paris") @MatrixParam("city")
String city) {
// ...
}
}
Consuming and Producing Content Types
With REST, the same resource can have several representations; a book can be represented as a web page, a PDF,
or an image showing the book cover. JAX-RS specifies a number of Java types that can represent a resource such as
String, InputStream and JAXB beans. The @javax.ws.rs.Consumes and @javax.ws.rs.Produces annotations may
be applied to a resource where several representations are possible. It defines the media types of the representation
exchanged between the client and the server. JAX-RS has a javax.ws.rs.core.MediaType class that acts like an
abstraction for a MIME type. It has several methods and defines the constants listed in Table 15-5.
513
www.it-ebooks.info
Chapter 15 ■ RESTful Web Services
Table 15-5. MIME Types Defined in MediaType
Constant name
MIME type
APPLICATION_ATOM_XML
“application/atom+xml”
APPLICATION_FORM_URLENCODED
“application/x-www-form-urlencoded”
APPLICATION_JSON
“application/json”
APPLICATION_OCTET_STREAM
“application/octet-stream”
APPLICATION_SVG_XML
“application/svg+xml”
APPLICATION_XHTML_XML
“application/xhtml+xml”
APPLICATION_XML
“application/xml”
MULTIPART_FORM_DATA
“multipart/form-data”
TEXT_HTML
“text/html”
TEXT_PLAIN
“text/plain”
TEXT_XML
“text/xml”
WILDCARD
“*/*”
Using the @Consumes and @Produces annotations on a method overrides any annotations on the resource class for
a method argument or return type. In the absence of either of these annotations, support for any media type (*/*) is
assumed. By default, CustomerRestService produces plain text representations that are overridden in some methods
(see Listing 15-11). Note that the getAsJsonAndXML produces an array of representations (XML or JSON).
Listing 15-11. A Customer Resource with Several Representations
@Path("/customer")
@Produces(MediaType.TEXT_PLAIN)
public class CustomerRestService {
@GET
public Response getAsPlainText() {
// ...
}
@GET
@Produces(MediaType.TEXT_HTML)
public Response getAsHtml() {
// ...
}
@GET
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Response getAsJsonAndXML() {
// ...
}
514
www.it-ebooks.info
Chapter 15 ■ RESTful Web Services
@PUT
@Consumes(MediaType.TEXT_PLAIN)
public void putName(String customer) {
// ...
}
}
If a RESTful web service is capable of producing more than one media type, the targeted method will correspond
to the most acceptable media type, as declared by the client in the Accept header of the HTTP request. For example,
if the Accept header is:
Accept: text/plain
and the URI is /customer, the getAsPlainText() method will be invoked. But the client could have used the
following HTTP header:
Accept: text/plain; q=0.8, text/html
This header declares that the client can accept media types of text/plain and text/html but prefers the latter
using the quality factor (or preference weight) of 0.8 (“I prefer text/html, but send me text/plain if it is the best
available after an 80% markdown in quality”). By including such header and pointing at the /customer URI, the
getAsHtml() method will be invoked.
Returned Types
So far you’ve seen mostly how to invoke a method (using parameters, media type, HTTP methods . . .) without caring
about the returned type. What can a RESTful web service return? Like any Java class, a method can return any
standard Java type, a JAXB bean or any other object as long as it has a textual representation that can be transported
over HTTP. In this case, the runtime determines the MIME type of the object being returned and invokes the
appropriate Entity Provider (see later) to get its representation. The runtime also determines the appropriate HTTP
return code to send to the consumer (204-No Content if the resource method's return type is void or null; 200-OK if
the returned value is not null). But sometimes you want finer control of what you are returning: the response body
(a.k.a. an entity) of course, but also the response code and/or response headers or cookies. That’s when you return a
Reponse object. It is a good practice to return a javax.ws.rs.core.Response with an entity since it would guarantee a
return content type. Listing 15-12 shows you different return types.
Listing 15-12. A Customer Service Returning Data Types, a JAXB Bean, and a Response
@Path("/customer")
public class CustomerRestService {
@GET
public String getAsPlainText() {
return new Customer("John", "Smith", "jsmith@gmail.com", "1234565").toString();
}
@GET
@Path("maxbonus")
public Long getMaximumBonusAllowed() {
return 1234L;
}
515
www.it-ebooks.info
Chapter 15 ■ RESTful Web Services
@GET
@Produces(MediaType.APPLICATION_XML)
public Customer getAsXML() {
return new Customer("John", "Smith", "jsmith@gmail.com", "1234565");
}
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response getAsJson() {
return Response.ok(new Customer("John", "Smith", "jsmith@gmail.com", "1234565"),
MediaType.APPLICATION_JSON).build();
}
}
The getAsPlainText method returns a String representation of a customer and the getMaximumBonusAllowed
returns a numerical constant. The defaults will apply so the return HTTP status on both methods will be 200-OK (if no
exception occurs). The getAsXML returns a Customer JAXB POJO meaning that the runtime will marshall the object
into an XML representation.
The getAsJson method doesn’t return an entity but instead a javax.ws.rs.core.Response object. A Response
wraps the entity that is returned to the consumer and it’s instantiated using the ResponseBuilder class as a factory.
In this example, we still want to return a JAXB object (the Customer) with a 200-OK status code (the ok() method), but
we also want to specify the MIME type to be JSON. Calling the ResponseBuilder.build() method creates the final
Response instance.
It is recommended to return a custom Response for all requests rather than the entity itself (you can then set a
specific status code if needed). Table 15-6 shows a subset of the Response API.
Table 15-6. The Response API
Method
Description
accepted()
Creates a new ResponseBuilder with an accepted status
created()
Creates a new ResponseBuilder for a created resource (with its URI)
noContent()
Creates a new ResponseBuilder for an empty response
notModified()
Creates a new ResponseBuilder with a not-modified status
ok()
Creates a new ResponseBuilder with an ok status
serverError()
Creates a new ResponseBuilder with an server error status
status()
Creates a new ResponseBuilder with the supplied status
temporaryRedirect()
Creates a new ResponseBuilder for a temporary redirection
getCookies()
Gets the cookies from the response message
getHeaders()
Gets the headers from the response message
getLinks()
Get the links attached to the message as header
getStatus()
Get the status code associated with the response
readEntity()
Read the message entity as an instance of specified Java type using a
MessageBodyReader that supports mapping the message onto the requested type
516
www.it-ebooks.info
Chapter 15 ■ RESTful Web Services
The Response and ResponseBuilder follow the fluent API design pattern. Meaning you can easily write a
response by concatenating methods. This also makes the code more readable. Here are some examples of what you
can write with this API:
Response.ok().build();
Response.ok().cookie(new NewCookie("SessionID", "5G79GDIFY09")).build();
Response.ok("Plain Text").expires(new Date()).build();
Response.ok(new Customer ("John", "Smith"), MediaType.APPLICATION_JSON).build();
Response.noContent().build();
Response.accepted(new Customer("John", "Smith", "jsmith@gmail.com", "1234565")).build();
Response.notModified().header("User-Agent", "Mozilla").build();
HTTP Method Matching
You’ve seen how the HTTP protocol works with its requests, responses, and action methods (GET, POST, PUT, etc.). JAXRS defines these common HTTP methods using annotations: @GET, @POST, @PUT, @DELETE, @HEAD, and @OPTIONS. Only
public methods may be exposed as resource methods. Listing 15-13 shows a customer RESTful web service exposing
CRUD methods: @GET methods to retrieve resources, @POST methods to create a new resource, @PUT methods to update
an existing resource, and @DELETE methods to delete a resource.
Listing 15-13. A Customer Resource Exposing CRUD Operations and Retuning Responses
@Path("/customer")
@Produces(MediaType.APPLICATION_XML)
@Consumes(MediaType.APPLICATION_XML)
public class CustomerRestService {
@GET
public Response getCustomers() {
// ..
return Response.ok(customers).build();
}
@GET
@Path("{customerId}")
public Response getCustomer(@PathParam("customerId") String customerId) {
// ..
return Response.ok(customer).build();
}
@POST
public Response createCustomer(Customer customer) {
// ..
return Response.created(createdCustomerURI).build();
}
@PUT
public Response updateCustomer(Customer customer) {
// ..
return Response.ok(customer).build();
}
517
www.it-ebooks.info
Chapter 15 ■ RESTful Web Services
@DELETE
@Path("{customerId}")
public Response deleteCustomer(@PathParam("customerId") String customerId) {
// ..
return Response.noContent().build();
}
}
The HTTP specification defines what HTTP response codes should be on a successful request. You can expect
JAX-RS to return the same default response codes:
•
GET methods retrieve whatever information (in the form of an entity) is identified by the
requested URI. GET should return 200-OK.
•
The PUT method refers to an already existing resource that needs to be updated. If an existing
resource is modified, either the 200-OK or 204-No Content response should be sent to indicate
successful completion of the request.
•
The POST method is used to create a new resource identified by the request URI. The response
should return 201-CREATED with the URI of this new resource or 204-No Content if it does not
result in a resource that can be identified by a URI.
•
The DELETE method requests that the server deletes the resource identified by the requested
URI. A successful response should be 200-OK if the response includes an entity, 202-Accepted
if the action has not yet been enacted, or 204-No Content if the action has been enacted but
the response does not include an entity.
Building URIs
Hyperlinks are a central aspect of REST applications. In order to evolve through the application states, RESTful web
services need to be agile at managing transition and building URIs. JAX-RS provides a javax.ws.rs.core.UriBuilder
that aims at replacing java.net.URI for making it easier to build URIs in a safe manner. UriBuilder has a set of
methods that can be used to build new URIs or build from existing URIs. Listing 15-14 gives you some examples of
how you can use the UriBuilder to create any kind of URI with path, query, or matrix parameters.
Listing 15-14. Using UriBuilder
public class URIBuilderTest {
@Test
public void shouldBuildURIs() {
URI uri =
UriBuilder.fromUri("http://www.myserver.com").path("book").path("1234").build();
assertEquals("http://www.myserver.com/book/1234", uri.toString());
uri = UriBuilder.fromUri("http://www.myserver.com").path("book")
.queryParam("author", "Goncalves").build();
assertEquals("http://www.myserver.com/book?author=Goncalves", uri.toString());
uri = UriBuilder.fromUri("http://www.myserver.com").path("book")
.matrixParam("author", "Goncalves").build();
assertEquals("http://www.myserver.com/book;author=Goncalves", uri.toString());
518
www.it-ebooks.info