There are various ways to implement communication between heterogeneous applications. There are standards focusing on web services based on SOAP, WSDL, and WS* specifications; alongside these standards there is an emerging lightweight solution based on plain HTTP referred to as Representational State Transfer (REST).
REST is identified by the principles of addressable resources, constrained interfaces using HTTP verbs, representation, and statelessness.
The key principles of REST are:
Associating IDs to resources
Using standard HTTP methods
Multiple formats of data sent by a resource
Statelessness
This chapter starts with the basic concept of building RESTful Web Services using the JAX-RS 2.0 API and covers the following sections:
Getting started with JAX-RS 2.0
Converting POJOs to RESTful endpoints using JAX-RS 2.0 annotations
@Produces
,@Consumes
annotationsClient API for JAX-RS 2.0
Sample showing all verbs
Custom entity providers for serializing and deserializing user defined classes using JAX-RS
Utilizing the Bean Validation API for validation with JAX-RS 2.0
The REST architectural style is based on request and response messages transferred between clients and servers without any of the participating node keeping track of the state of previous sessions..
REST uses nouns and verbs for readability. Resources are identified in requests. The representation of the resource that is sent to the client depends on the request and how the server sends the data.
A RESTful Web Service is a service whose interface and accessing mechanism are aligned with the REST principles . The URIs identify the resources. For example, a RESTful resource for a book can be identified as http://foo.org/book.
A resource for a book identified by ISBN could be http://foo.org/book/isbn/1234459. This shows a human-readable URI that is easy to understand and identify.
A client has enough metadata of a resource to modify or delete it as long as it is authorized to do so. To get a resource the client would send a HTTP GET
request. To update the resource the client would send a PUT
request. To delete a resource the client would send a DELETE
request. To create a new resource, and for arbitrary processing, the client sends a HTTP POST
request. The next section covers these verbs in more detail.
Some of the requests used in REST are as follows:
GET
: TheGET
request retrieves a representation of a resource from server to clientPOST
: ThePOST
request is used to create a resource on the server based on the representation that the client sendsPUT
: ThePUT
request is used to update or create a reference to a resource on serverHEAD
: TheHEAD
requests checks for a resource without retrieving it
The next section will introduce the notion of safety and idempotence, two important terms associated with REST.
When it comes to REST, a safe method, by definition, is a HTTP method that does not modify the state of the resource on the server. For example, invoking a GET
or a HEAD
method on the resource URL should never change the resource on the server. PUT
is considered not safe since it usually creates a resource on the server. DELETE
is also considered not safe since it will delete the resource on the server. POST
is not safe since it will change the resource on the server.
Idempotent method is a method that can be called multiple times yet the outcome will not change.
GET
and HEAD
are idempotent, which means that even though the same operation is done multiple times the result does not vary. PUT
is idempotent; calling the PUT
method multiple times will not change the result and the resource state is exactly the same.
DELETE
is idempotent because once the resource is deleted it is gone, and calling the same operation multiple times will not change the outcome.
In contrast, POST
is not idempotent and calling POST
multiple times can have different outcomes.
Tip
The idempotence and safety of the HTTP verbs are a convention, meaning that when someone is using your API they will assume that GET
/PUT
/POST
/DELETE
have the same idempotency characteristics that are previously described; and the implementation of the business logic behind each verb should support these characteristics.
The response sent by the server could be in XML, JSON, or any other MIME type as long as the server supports the requested format. In case the server cannot support the requested MIME type, it can return with a status code of 406 (not acceptable).
When we are developing with RESTful principles in mind, each message should have enough information to let the server understand the purpose of the message and how to process that message, to produce the response the message is meant for, and finally to ensure visibility and statelessness.
Summarizing, these are the components of RESTful Web Services:
The Java API for Representational State Transfer (JAX-RS) specification defines a set of Java APIs for building web services conforming to the REST style.
This specification defines how to expose POJOs as web resources, using HTTP as the network protocol. Applications using these APIs can be deployed to an application server in a portable manner.
Some of the key features that are introduced in the JAX-RS 2.0 specification are as follows:
In the subsequent sections we will cover the following topics in relation to JAX-RS 2.0:
Converting POJOs to RESTful resources
More on JAX-RS annotations
Client API for JAX-RS
Entities in JAX-RS
Custom entity providers in JAX-RS
Using the Bean Validation API with JAX-RS
A resource class is a POJO that uses the JAX-RS annotations. A resource class needs to have at least one method annotated with @Path
or a request method. Resources are our so-called web services and incoming requests target these resources.
Steps to convert POJOs to RESTful endpoints:
JAX-RS provides very rich client and server APIs that work on any Java EE application server. Using JAX-RS API, any POJO can be annotated to build the RESTful resources. Begin with a simple POJO BookResource
and annotate it with the JAX-RS APIs.
@Path("books")
public class BooksResource {
}
This is a root resource class, which is annotated with @Path annotation
. The value "books"
will indicate that the resource will be available at a location similar to the following URI http://host:port/appname/books
.
Later on we add the methods to this resource so that, when a request with GET
, PUT
, and so on hits this resource, a particular method in the class is invoked to produce the response.
To add a method to this resource, we annotate the method with @GET
, @PUT
, @DELETE
, or @HEAD
. In the following example, we chose to annotate using a @GET annotation
:
@GET
public String getGreeting() {
return "Hello from Book resource"
}
The @GET
annotation specifies that the getGreeting()
method handles the HTTP GET
requests.
To specify the MIME type that can be handled by the resource, we should annotate the resource method with @Produces
and @Consumes
:
@Produces("text/plain")
@GET
public String getGreeting() {
return "Hello from Book resource"
}
The @Produces
specifies that the media type this method will produce is "text/plain"
. Support for other media types, and how to map from Java to a specific format and vice versa, is covered in detail in the entity provider's section. Thus, this is the initial introduction to having a first JAX-RS resource ready. The next section covers the details of the Application
subclass.
The Application
class is a portable way to configure application-level details such as specifying the name, and registering various components of a JAX-RS application. This includes the different JAX-RS resources and the JAX-RS providers in the application.
Similarly, application-wide properties can be set using a subclass of Application
. The Application
subclass should to be placed in either in WEB-INF/classes
or WEB-INF/lib
in a WAR file. Application class has the following methods that can be overridden:
public Set<Class<?>> getClasses() ; public Map<String, Object> getProperties(); public Set<Object> getSingletons();
Here is an example of a subclass of Application for our case:
@ApplicationPath("/library/")
public class HelloWorldApplication extends Application {
@Override
public Set<Class<?>> getClasses() {
Set<Class<?>> classes = new HashSet<Class<?>>();
classes.add(BooksResource.class);
return classes;
}
}
In this code we create a HelloWorldApplication
, which is a subclass of javax.ws.rs.core.Application
. With Servlet 3.0 there is no need of a web.xml
file and the servlet container uses the value specified in the @ApplicationPath
as the servlet mapping. The getClasses()
method of the Application
class is overridden to add BooksResource.class
.
A basic JAX-RS resource is now ready to use. When the sample is deployed to an application server such as GlassFish, you can use curl to send a request.
Here is an example on how to send a curl -X GET
request:
curl -X GET http://localhost:8080/helloworld/books
The output in the terminal window should be:
Hello from book resource
Chapter 5, RESTful Web Services by Example, will show how to use the Application class in a web.xml
file.
Resource classes can partially process some part of the request and provide another subresource to process the remaining part of the request.
For example, here is a snippet of a root resource Library
and another resource Book
.
@Path("/") public class Library { @Path("/books/{isbn}") public Book getBook(@PathParam("isbn") String isbn){ //return book } } public class Book { @Path("/author") public String getAuthor(){ } }
Subresource locators are resource methods that have @Path
annotation but no HTTP methods.
In the preceding example, Library
is a root resource as it is annotated with @Path
. The method getBook()
is a subresource locator whose job is to provide an object that can process the request.
The @PathParam
is an annotation that allows you to map URI path fragments in the method call. In this example, the isbn
URI parameter is passed to provide information about the book.
If a client sends a request using the URI:
GET /books/123456789
The Library.getBook()
method will be invoked.
If a client sends a request using the URI:
GET /books/123456789/author
The Library.getBook()
method will be invoked first. A Book
object is returned and then the getAuthor()
method is invoked.
The @Produces
annotation is used to define the type of output the method in the resource produces. The @Consumes
annotation is used to define the type of input, the method in the resource consumes.
Here is a method in a resource for a POST
request:
@POST @Consumes(MediaType.APPLICATION_XML) @Produces(MediaType.APPLICATION_XML) public Response addBook(Book book) { BooksCollection.addBook(book); return Response.ok(book). type(MediaType.APPLICATION_XML_TYPE).build(); }
As shown in this snippet we have the @POST
annotation that indicates this method accepts POST
request.
The @Produces(MediaType.APPLICATION_XML)
indicates that the "application/xml"
media type is produced by the addBook()
method of this resource.
The @Consumes(MediaType.APPLICATION_XML)
indicates that the "application/xml"
media type is consumed by the addBook()
method of this resource.
The Response.ok(book)
method builds an ok response of the type MediaType.APPLICATION_XML_TYPE
Other supported media types @Produces
and @Consumes
are "text/xml"
, "text/html"
, "application/json"
, and so on.
If there is no media type specified in the @Produces
or @Consumes
annotations, support for any media type is assumed by default.
Here is a snippet of code that shows the @DELETE
annotation.
@DELETE
@Path("/{isbn}")
public Book deleteBook(@PathParam("isbn")String isbn) {
return BooksCollection.deleteBook(isbn);
}
The @PathParam
annotation allows you to map the URI path fragments in the method call. In this example, the isbn
URI parameter is passed to provide information about the book.
The ISBN uniquely identifies the Book resource so that it can be deleted.
The following table summarizes important JAX-RS 2.0 annotations included in Java EE 7 and used throughout this book.
Chapter 5, RESTful Web Services by Example, covers the different JAX-RS APIs in detail and ties them together with other Java EE APIs to build a real-world library application.
JAX-RS 2.0 provides a rich client API to access the web resources. Here is the code on how to use the client API for the BooksResource
we built earlier:
Client client = ClientBuilder.newClient(); WebTarget target = client.target(URI);
The default instance of the javax.ws.rs.client.Client
object can be obtained using the ClientBuilder.newClient()
API. The BooksResource
can be identified by URI. The WebTarget
object is used to build the URI.
String book = target.request().get(String.class);
The target.request().get(String.class)
method builds an HTTP GET
request and gets an object of type String
in the response. More samples of the client API with other verbs are shown in the next section.
The main part of an HTTP interaction consists of the request and response entities. Entities are also referred to as the payload or message body in some contexts.
Entities are sent via a request, usually an HTTP POST
and PUT
method is used, or they are returned in a response, this is relevant for all the HTTP methods. The Content-Type
HTTP header is used to indicate the type of entity being sent. Common content types are "text/plain"
, "text/xml"
, "text/html"
, and "application/json"
.
Media types are used in the Accept
header to indicate what type of resource representation the client wants to receive.
The following snippet shows how to use the client API to create a POST
request. This invocation takes an entity for a user-defined class Book
and a MediaType.APPLICATION_XML_TYPE
parameter.
Here is the client code to invoke the POST
method:
Response response = target.request() post(Entity.entity(new Book("Getting Started with RESTful Web Services","111334444","Enterprise Applications"), MediaType.APPLICATION_XML_TYPE));
In the preceding snippet, the WebTarget#request()
method returns a Response
object.
Here is the client API code to invoke the delete
method:
response = target.path("111334444") request( MediaType.APPLICATION_XML_TYPE) .delete();
The next section will show how the entity providers that implement the JAX-RS API map to and from Java types request and response entities.
JAX-RS enables developers to add custom entity providers to the application. The custom entity providers can be used for dealing with user-defined classes in the requests as well as responses.
Adding a custom entity provider provides a way to deserialize user-defined classes from the message bodies and serialize any media type to your user specific class.
There are two types of entity providers:
MessageBodyReader
MessageBodyWriter
Using the @Provider
annotation, application-specific provider classes can be discovered. Entity providers provide mapping between the representation and associated type. There is a sample included with the book that demonstrates the use of entity providers.
An application can provide an implementation of the MessageBodyReader
interface by implementing the isReadable()
method and the readFrom()
method to map the entity to the desired Java type.
The following figure shows how the MessageBodyReader
reads an InputStream
object and converts it to a user-defined Java object.
The following code shows how to provide an implementation of MessageBodyReader
and uses Java Architecture for XML Binding (JAXB) with JAX-RS. JAXB provides a fast and convenient way to bind XML schemas and Java representations, making it easy for Java developers to incorporate the XML data and processing functions in Java applications. As a part of this process, JAXB provides methods for unmarshalling (reading) XML instance documents into Java content trees, and then marshalling (writing) Java content trees back into XML instance documents.
Here is a JAXB root element called Book
. Book
has properties such as name and ISBN.
@XmlRootElement public class Book { public String name; public String isbn; public String getName() { return name; } public String getIsbn() { return isbn; } public Book(String name, String isbn) { this.name=name; this.isbn=isbn; } //JAXB requires this public Book() { } }
The MessageBodyReader
implementation class can provide support to read from an inputStream
object and convert to the Book
object. The following table shows the methods that need to be implemented:
Method of MessageBodyReader |
Description |
---|---|
|
To check if the |
|
To read a type from the |
Here is the code for SampleMessageBodyReader
class that is the implementation of the MessageBodyReader
interface:
@Provider public class SampleMessageBodyReader implements MessageBodyReader<Book> { }
The @Provider
annotation indicates that this is a provider and the implementing class can also use @Produces
and @Consumes
annotations to restrict the media types they support.
Here is the implementation of isReadable()
method:
public boolean isReadable(Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType) { return true; }
The isReadable()
method returns true
to indicate that this SampleMessageBodyReader
class can process the mediaType
parameter.
This is an implementation of the readFrom()
method of the SampleMessageBodyReader
class. The mediaType
parameter can be checked here and different actions can be taken based on the media type.
public Book readFrom(Class<Book> bookClass, Type type, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> stringStringMultivaluedMap, InputStream inputStream) throws IOException, WebApplicationException { try { Book book = (Book)unmarshaller.unmarshal(inputStream) ; return book; } catch (JAXBException e) { e.printStackTrace(); } return null; } }
The book
object, which is the method's return value, is then unmarshalled using JAXB Unmarshaller using the provided inputStream
object as the parameter.
The MessageBodyWriter
interface represents a contract for a provider that supports the conversion from a Java type to a stream.
The following figure shows how MessageBodyWriter
can take a user-defined class, Book,
and marshal it to an outputStream
object.
The following table shows the methods of MessageBodyWriter
that must be implemented along with a short description of each of its method.
Method of MessageBodyWriter |
Description |
---|---|
To check if the | |
To check the length of bytes if the size is known or -1. | |
To write from a type to the stream. |
Here are the methods of the MessageBodyWriter
interface that need to be implemented:
public boolean isWriteable(Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType) {
return true;
}
The isWritable()
method of the MessageBodyWriter
interface can be customized to check if this implementation of MessageBodyWriter
supports the type or not.
public long getSize(Book book, Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType) {
return -1;
}
The getSize()
method is called before the writeTo()
method to ascertain the length of bytes in the response.
public void writeTo(Book book, Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> map, OutputStream outputStream) throws IOException, WebApplicationException { try { Marshaller marshaller = jaxbContext.createMarshaller(); marshaller.marshal(book, outputStream); } catch (Exception e) { e.printStackTrace(); } }
The writeTo()
method marshals the Book
to the Outputstream
.
Tip
Tips for debugging errors with
MessageBodyReader
and
MessageBodyWriter
:
Look for the
@Provider
annotation.MessageBodyReader
implementation class andMessageBodyWriter
implementation class need the@Provider
annotation.Confirm if the implementation classes of
MessageBodyReader
andMessageBodyWriter
interfaces are added in thegetClasses()
method of the Application subclass.Check if the implementation of
MessageBodyReader.isReadable()
method returnstrue
.Check if the implementation of
MessageBodyWriter.isWritable()
method returnstrue
.Confirm the
MessageBodyWriter.getSize()
method is-1
if the size of response is unknown or set it to the right value if the size is known.
Client client = ClientBuilder.newClient(); client.register(MessageBodyReaderWriter.class).register(BooksResource.class); Response response = target .request() .post(Entity.entity(new Book("Getting Started with RESTful Web Services","13332233"), MediaType.APPLICATION_XML_TYPE)); Book = response.readEntity(Book.class);
The client.register()
method is used to register the MessageBodyReaderWriter.class
and BooksResource.class
.
The application class, Book
is extracted from the response using response.readEntity(Book.class)
.
Validation is the process of verifying that the given inputs are complying with the defined constraints. The Bean Validation specification defines the API to validate JavaBeans. This section shows how to validate the JAX-RS 2.0 resources using the Bean Validation API.
Validation can be used to ensure that fields in the JAX-RS resources follow certain constraints. For example, to check that a field is not null
or if the ISBN follows a pattern. Using Bean Validation, a user can write custom validators and annotate the JAX-RS resources and their components using the custom validators.
The sample included along with the book will show how to use Bean Validation with JAX-RS 2.0 resources.
Here is a code snippet showing how to enforce validation along with defining a constraint and adding a user-defined message to it:
@Path("books") @ValidateOnExecution(ExecutableType.GETTER_METHODS) public class BooksResource { @GET @Path("{isbn}") @Consumes(MediaType.APPLICATION_XML) @Produces(MediaType.APPLICATION_XML) @NotNull(message="Book does not exist for the ISBN requested") public Book getBook( @PathParam("isbn")String isbn) { return BooksCollection.getBook(isbn); } }
The @ValidateOnExecution
annotation can be used to selectively enable and disable the validation. In this snippet, the getBook()
method gets validated because the @ValidateOnExecution
annotation enables the validation for the ExecutableType.GETTER_METHODS
value.
When the sample code is executed, if the book value is not null then, the book object is returned. If the book value is null, there is a validation error with a message shown on the screen as "Book does not exist for the ISBN requested"
. This is the message that is provided with the @NotNull
annotation shown previously.
Getting validation errors from the response is not enabled by default. The sample included in the book will demonstrate how to get the validation errors from the response. The user needs to set BV_SEND_ERROR_IN_RESPONSE
property to Boolean value true
using Application
class by overriding the getProperties()
method.
Here is the getProperties()
method of the Application
subclass.
@override public Map<String,Object> getProperties() { Map<String,Object> properties = new HashMap<String,Object>() ; properties.put(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true); return properties; }
The getProperties()
method returns the Map<String,Object>
object with the String property ServerProperties.BV_SEND_ERROR_IN_RESPONSE
set to the Boolean value true
.
Tip
Downloading the example code
You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.
After the application class is configured to set the String property ServerProperties.BV_SEND_ERROR_IN_RESPONSE
to the Boolean value true
, the following code in the servlet class will read the validation errors from the response.
This is how the code looks on the client side:
List<ValidationError> errors = response.readEntity(new GenericType<List<ValidationError>>() {});
The response.readEntity()
method takes a list of GenericType<ValidationError>
parameters. From the List<ValidationError>
errors
, returned by the response.readEntity()
method, we can extract the validation error and get the validation message. On running the sample, the following message will be shown:
"There was 1 error when validating the request
Book does not exist for the ISBN requested"
This chapter started with a brief introduction to REST and the key principles of RESTful Web Services development, followed by converting a POJO to a JAX-RS resource, a RESTful endpoint along with discussing different HTTP verbs and their use.
After the introduction, the chapter dives deeper into the JAX-RS API by introducing the client API to send requests to the resources developed using the JAX-RS APIs. We also covered customizing the entity providers to produce different output formats using MessageBodyReader
and MessageBodyWriters
. We learned how to validate JAX-RS 2.0 resources using Bean Validation.
In the next chapter, we will cover the different polling techniques, compare and contrast them with Server-sent events (SSE) and WebSockets, followed by a closer look at how Java EE 7 provides support for SSE and WebSockets.