RESTful Web Services are defined as JSR 339, and the complete specification can be downloaded.
REST is an architectural style of services that utilizes web standards. Web services designed using REST are called RESTful web services, and their main principles are:
Everything can be identified as a resource, and each resource can be uniquely identified by 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, andDELETE
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 and invoke it. 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 via annotations.
A simple RESTful web service can be defined as a resource
using @Path
:
@Path
(
"orders"
)
public
class
OrderResource
{
@GET
public
List
<
Order
>
getAll
()
{
//. . .
}
@GET
@Path
(
"{oid}"
)
public
Order
getOrder
(
@PathParam
(
"oid"
)
int
id
)
{
//. . .
}
}
@XmlRootElement
public
class
Order
{
int
id
;
//. . .
}
In this code:
OrderResource
is a POJO class and is published as a RESTful resource at theorders
path when we add 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, which provides a list of all orders, is invoked when we access this resource using the HTTPGET
method; we identify it by specifying the@GET
annotation on the method.The
@Path
annotation on thegetOrder
resource method marks it as a subresource that is accessible atorders/{oid}
.The curly braces around
oid
identify it as a template parameter and bind its value at runtime to theid
parameter of thegetOrder
resource method.The
@PathParam
can also be used to bind template parameters to a resource class field.
Typically, a RESTful resource is bundled in a .war file along with other classes and
resources. The Application
class and
@ApplicationPath
annotation are 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
{
}
You can access a list of all the orders by issuing a GET
request to:
http://localhost:8080/store/webresources/orders
You can obtain a specific order 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. The @XmlRootElement
annotation ensures that an
automatic mapping from Java to XML occurs following JAXB mapping and an
XML representation of the resource is returned.
A URI may pass HTTP query parameters using name/value pairs. You
can map these to resource method parameters or fields using the
@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.
By default, a resource method is required to wait and produce a response before returning to the JAX-RS implementation, which then returns control to the client. JAX-RS 2 allows for an asynchronous endpoint that informs the JAX-RS implementation that a response is not readily available upon return but will be produced at a future time. It achieves this by first suspending the client connection and later resuming when the response is available:
@Path
(
"orders"
)
public
class
OrderResource
{
@GET
public
void
getAll
(
@Suspended
final
AsyncResponse
ar
)
{
executor
.
submit
(
new
Runnable
()
{
@Override
public
void
run
()
{
List
<
Order
>
response
=
new
ArrayList
<>();
//. . .
ar
.
resume
(
response
);
}
});
}
}
In this code:
The
getAll
method is marked to produce an asynchronous response. We identify this by injecting a method parameter of the classAsyncResponse
using the new annotation@Suspended
. The return type of this method isvoid
.This method returns immediately after forking a new thread, likely using
ManagedExecutorService
as defined by Concurrency Utilities for Java EE. The client connection is suspended at this time.The new thread executes the long-running operation and resumes the connection by calling
resume
when the response is ready.
You can receive request processing completion events by registering
an implementation of CompletionCallback
:
public
class
OrderResource
{
public
void
getAll
(
@Suspended
final
AsyncResponse
ar
)
{
ar
.
register
(
new
MyCompletionCallback
());
}
class
MyCompletionCallback
implements
CompletionCallback
{
@Override
public
void
onComplete
(
Throwable
t
)
{
//. . .
}
}
}
In this code, the onComplete
method is invoked when the
request processing is finished, after a response is processed and is sent
back to the client, or when an unmapped throwable has been propagated to
the hosting I/O container.
You can receive connection-related life-cycle events by registering
an implementation of ConnectionCallback
:
public
class
OrderResource
{
public
void
getAll
(
@Suspended
final
AsyncResponse
ar
)
{
ar
.
register
(
new
MyCompletionCallback
());
}
class
MyCompletionCallback
implements
CompletionCallback
{
@Override
public
void
onDisconnect
(
AsyncResponse
ar
)
{
//. . .
}
}
}
In this code, the onDisconnect
method is invoked in
case the container detects that the remote client connection associated
with the asynchronous response has been disconnected.
JAX-RS provides support for binding standard HTTP GET
, POST
,
PUT
, DELETE
, HEAD
,
and OPTIONS
methods using the
corresponding annotations described in Table 4-1.
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 as
follows:
@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
metainformation 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 the 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.
By default, a RESTful resource is published or consumed with
the */*
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.
JAX-RS 2.0 allows you to indicate a media preference on the server
side using the qs
(short for “quality on server”)
parameter.
qs
is a floating-point number with a value in the range
of 0.000 through 1.000 and indicates the relative quality of a
representation compared to the others available, independent of the
client’s capabilities. A representation with a qs
value of
0.000 will never be chosen. A representation with no qs
parameter value is given a qs
factor of 1.0:
@POST
@Path
(
"{oid}"
)
@Consumes
({
"application/xml; qs=0.75"
,
"application/json; qs=1"
})
public
Order
getOrder
(
@PathParam
(
"oid"
)
int
id
)
{
.
.
.
}
If a client issues a request with no preference for a particular
representation or with an Accept
header
of application/*;
, then the server will select a
representation with a higher qs
value—application/json
in this case.
qs
values are relative and, as
such, are only comparable to other qs
values within the same @Produces
annotation instance.
You can define a mapping between a custom representation and a
corresponding Java type by implementing the MessageBodyReader
and
MessageBodyWriter
interfaces and annotating with @Provider
.
By default, a new resource is created for each request to
access a resource. The resource method parameters, fields, or bean
properties are bound by way of
annotations added during
object creation time. In addition to xxx
Param@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 parametersessionid
.@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 Binding HTTP Methods.@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 thefrom
parameter and20
is mapped to thepage
parameter.
You can obtain more details about the application deployment
context and the context of individual requests 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
OrderResource
{
@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 aMap
or as 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 withResponse
to dynamically build the response.SecurityContext
provides access to security-related information about the current request.Providers
supplies information about runtime lookup of provider instances based on a set of search criteria.
JAX-RS defines entity providers that supply
mapping services between on-the-wire representations and their associated
Java types. The entities, also known as “message payload” or “payload,”
represent the main part of an HTTP message. These are specified as method
parameters and return types of resource methods. Several standard Java
types—such as String
, byte[]
,
javax.xml.bind.JAXBElement
, java.io.InputStream
,
java.io.File
, and others—have a predefined mapping and are
required by the specification. Applications may provide their own mapping
to custom types using the MessageBodyReader
and
MessageBodyWriter
interfaces. This allows us to extend the
JAX-RS runtime easily to support our own custom entity providers.
The MessageBodyReader
interface defines the
contract for a provider that supports the conversion of a stream to a Java
type. Conversely, the MessageBodyWriter
interface defines the
contract for a provider that supports the conversion of a Java type to a
stream.
If we do not specify @XmlRootElement
on
OrderResource
, then we need to define the mapping between XML
to Java and vice versa. Java API for XML Processing can be used to define
the mapping between the Java type to XML and vice versa. Similarly, Java
API for JSON Processing can be used to define the two-way mapping between
Java and JSON:
@Provider
@Produces
(
"application/json"
)
public
class
OrderReader
implements
MessageBodyReader
<
Order
>
{
//. . .
@Override
public
void
writeTo
(
Order
o
,
Class
<?>
type
,
Type
t
,
Annotation
[]
as
,
MediaType
mt
,
MultivaluedMap
<
String
,
Object
>
mm
,
OutputStream
out
)
throws
IOException
,
WebApplicationException
{
JsonGeneratorFactory
factory
=
Json
.
createGeneratorFactory
();
JsonGenerator
gen
=
factory
.
createGenerator
(
out
);
gen
.
writeStartObject
()
.
write
(
"id"
,
o
.
getId
())
.
writeEnd
();
}
}
This code shows the implementation of the
MessageBodyWriter.writeTo
method, which is responsible for
writing the object to an HTTP response. The method is using the Streaming
API defined by Java API for JSON Processing to write Order
o
to the underlying OutputStream
out
for the HTTP
entity. The implementation class needs to be marked with
@Provider
to make it discoverable by the JAX-RS runtime on
the server side. The providers need to be explicitly registered on the
client side. @Produces
ensures that this entity provider will
only support the specified media type:
@Provider
@Consumes
(
"application/json"
)
public
class
OrderReader
implements
MessageBodyReader
<
Order
>
{
//. . .
@Override
public
Order
readFrom
(
Class
<
Order
>
type
,
Type
t
,
Annotation
[]
as
,
MediaType
mt
,
MultivaluedMap
<
String
,
String
>
mm
,
InputStream
in
)
throws
IOException
,
WebApplicationException
{
Order
o
=
new
Order
();
JsonParser
parser
=
Json
.
createParser
(
in
);
while
(
parser
.
hasNext
())
{
switch
(
parser
.
next
())
{
case
KEY_NAME:
String
key
=
parser
.
getString
();
parser
.
next
();
switch
(
key
)
{
case
"id"
:
o
.
setId
(
parser
.
getIntValue
());
break
;
default
:
break
;
}
break
;
default
:
break
;
}
}
return
o
;
}
}
This code shows the implementation of the
MessageBodyReader.readFrom
method, which is responsible for
reading the object from the HTTP request. This method is using the
Streaming API defined by Java API for JSON Processing to read Order
o
from the InputStream
in
for the HTTP entity.
The implementation class needs to be marked with @Provider
to
make it discoverable by the JAX-RS runtime. The providers need to be
explicitly registered on the client side. @Consumes
ensures
that this entity provider will only support the specified media type.
JAX-RS 2 adds a new Client API that can be used to access web
resources and provides integration with JAX-RS providers. Without this
API, users must use a low-level HttpUrlConnection
to access
the REST endpoint:
Client
client
=
ClientBuilder
.
newClient
();
Order
order
=
client
.
target
(
"http://localhost:8080/store/webresources/orders"
)
.
path
(
"{oid}"
)
.
resolveTemplate
(
"oid"
,
1
)
.
request
()
.
get
(
Order
.
class
);
This code uses the fluent builder pattern and works as follows:
ClientBuilder
is the entry point to the client API. It is used to obtain an instance ofClient
that uses method chaining to build and execute client requests in order to consume the responses returned.Client
s are heavyweight objects that manage the client-side communication infrastructure. Initialization as well as disposal of aClient
instance may be a rather expensive operation. It is therefore recommended that you construct only a small number ofClient
instances in the application.We create
WebTarget
by specifying the URI of the web resource. We then use these targets to prepare client request invocation by resolving the URI template, using theresolveTemplate
method for different names. We can specify additional query or matrix parameters here using thequeryParam
andmatrixParam
methods, respectively.We build the client request by invoking the
request
method.We invoke the HTTP
GET
method by calling theget
method. The Java type of the response entity is specified as the parameter to the invoked method.
The fluency of the API hides its complexity, but a better understanding of the flow allows us to write better code.
We can make HTTP POST
or PUT
requests by using the post
or
put
methods, respectively:
Order
order
=
client
.
target
(...)
.
request
()
.
post
(
Entity
.
entity
(
new
Order
(
1
),
"application/json"
),
Order
.
class
);
In this code, a new message entity is created with the specified
media type, a POST
request is created,
and a response of type Order
is expected. There are other
variations of the Entity.entity
method that would allow us to
manipulate the request. The Entity
class defines variants of
the most popular media types. It also allows us to POST
an HTML form using the form
method.
We can make an HTTP DELETE
request by identifying the resource with the URI and using the
delete
method:
client
.
target
(
"..."
)
.
target
(
"{oid}"
)
.
resolveTemplate
(
"oid"
,
1
)
.
request
()
.
delete
();
We need to explicity register entity providers using the
register
method:
Order
o
=
client
.
register
(
OrderReader
.
class
)
.
register
(
OrderWriter
.
class
)
.
target
(...)
.
request
()
.
get
(
Order
.
class
);
By default, the client is invoked synchronously but can be
invoked asynchronously if we call the async
method as part of
the fluent builder API and (optionally) register an instance of
InvocationCallback
:
Future
<
Order
>
f
=
client
.
target
(
"http://localhost:8080/store/webresources/orders"
)
.
path
(
"{oid}"
)
.
resolveTemplate
(
"oid"
,
1
)
.
request
()
.
async
()
.
get
();
//. . .
Order
o
=
f
.
get
();
In this code, the call to get
after async
is called returns immediately without blocking the caller’s thread. The
response is a Future
instance that can be used to monitor or
cancel asynchronous invocation or retrieve results.
Optionally, InvocationCallback
can be registered to receive the events from the
asynchronous invocation processing:
client
.
target
(
"http://localhost:8080/store/webresources/orders/{oid}"
)
.
resolveTemplate
(
"oid"
,
1
)
.
request
()
.
async
()
.
get
(
new
InvocationCallback
<
Order
>()
{
@Override
public
void
completed
(
Order
o
)
{
//. . .
}
@Override
public
void
failed
(
Throwable
t
)
{
//. . .
}
});
The completed
method is called on successful completion
of invocation, and the response data is made available in the parameter
o
. The failed
method is called when the
invocation is failed for any reason, and the parameter t
contains failure details.
A more generic client request can be prepared and executed at a later time. This enables a separation of concerns between the creator and the submitter:
Invocation
i1
=
client
.
target
(...).
request
().
buildGet
();
Invocation
i2
=
client
.
target
(...)
.
request
()
.
post
(
Entity
.
entity
(
new
Order
(
1
),
"application/json"
));
//. . .
Response
r
=
i1
.
invoke
();
Order
o
=
i2
.
invoke
(
Order
.
class
);
In this code, a GET
request is
prepared and stored as i1
, and a POST
request is prepared and stored as
i2
. These requests are then executed at a later time via the
invoke
method and the result retrieved. In the first case, a
more generic Response
is returned, which can then be used to
extract the result and other metadata about it. In the second case, an
Order
instance is returned because of the type specified
before.
You can submit these as asynchronous requests using the
submit
method:
Future
<
Response
>
f1
=
i1
.
submit
();
Future
<
Order
>
f2
=
i2
.
submit
(
Order
.
class
);
//. . .
Response
r1
=
f1
.
get
();
Order
r11
=
r1
.
readEntity
(
Order
.
class
);
Order
r2
=
f2
.
get
();
In this code, we submit the Invocation
s for
asynchronous execution using the submit
method. A
Future
response object is returned in both cases. After
waiting for some time, we can obtain the first result by calling the
get
method on the returned Future
. In the first
case, we need to extract the exact result by calling the
readEntity
method. In the second case, an Order
response is returned directly because of the type specified during
the submit
invocation.
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.
JAX-RS 2 defines extension points to customize the request/response processing on both the client and server side. These are used to extend an implementation in order to provide capabilities such as logging, confidentiality, and authentication. The two kinds of extension points are: filters and entity interceptors.
Filters are mainly used to modify or process incoming and outgoing request or response headers. Entity interceptors are mainly concerned with marshaling and unmarshaling of HTTP message bodies.
Filters can be configured on the client and server, giving us four extension points for filters, defined by four interfaces:
ClientRequestFilter
ClientResponseFilter
ContainerRequestFilter
ContainerResponseFilter
As the names indicate, ClientRequestFilter
and ClientResponseFilter
are client-side filters, and
ContainerRequestFilter
and
ContainerResponseFilter
are server-side filters. Similarly,
ClientRequestFilter
and ContainerRequestFilter
operate on the request, and ContainerResponseFilter
and
ClientResponseFilter
operate on the response.
The client-side or server-side filters may be implemented by the same class or different classes:
public
class
ClientLoggingFilter
implements
ClientRequestFilter
,
ClientResponseFilter
{
@Override
public
void
filter
(
ClientRequestContext
crc
)
throws
IOException
{
String
method
=
crc
.
getMethod
();
String
uri
=
crc
.
getUri
();
for
(
Entry
e
:
crc
.
getHeaders
().
entrySet
())
{
...
=
e
.
getKey
();
...
=
e
.
getValue
();
}
}
@Override
public
void
filter
(
ClientRequestContext
crc
,
ClientResponseContext
crc1
)
throws
IOException
{
for
(
Entry
e
:
crc1
.
getHeaders
().
entrySet
())
{
...
=
e
.
getKey
();
...
=
e
.
getValue
();
}
}
}
This code shows a simple client-side filter that will log the
headers sent as part of the request and received in the response message.
ClientRequestContext
provides request-specific information for the filter, such
as the request URI, message headers, and message entity or request-scoped
properties. ClientResponseContext
provides response-specific information for the filter, such
as message headers, the message entity, or response-scoped
properties.
On the client side, you can register this filter using the client-side API:
Client
client
=
ClientBuilder
.
newClient
();
client
.
register
(
ClientLoggingFilter
.
class
);
WebTarget
target
=
client
.
target
(...);
A server-side filter that will log the headers received as part of the request and sent in the response message can be implemented similarly:
@Provider
public
class
ServerLoggingFilter
implements
ContainerRequestFilter
,
ContainerResponseFilter
{
@Override
public
void
filter
(
ContainerRequestContext
crc
)
throws
IOException
{
String
method
=
crc
.
getMethod
();
String
uri
=
crc
.
getUriInfo
().
getAbsolutePath
();
for
(
String
key
:
crc
.
getHeaders
().
keySet
())
{
...
=
key
;
...
=
crc
.
getHeaders
().
get
(
key
);
}
}
@Override
public
void
filter
(
ContainerRequestContext
crc
,
ContainerResponseContext
crc1
)
throws
IOException
{
for
(
String
key
:
crc1
.
getHeaders
().
keySet
())
{
...
=
key
;
....
=
crc1
.
getHeaders
().
get
(
key
);
}
}
}
In this code, the filter is a provider class and thus must be
marked with @Provider
. This ensures that the filters are
automatically discovered. If the filter is not marked with
@Provider
, it needs to be explicitly registered in the
Application
class:
@ApplicationPath
(
"webresources"
)
public
class
MyApplication
extends
Application
{
@Override
public
Set
<
Class
<?>>
getClasses
()
{
Set
<
Class
<?>>
resources
=
new
java
.
util
.
HashSet
<>();
resources
.
add
(
org
.
sample
.
filter
.
ServerLoggingFilter
.
class
);
return
resources
;
}
}
ContainerRequestFilter
comes in two flavors: pre-match and
post-match. A pre-match filter is applied globally on
all resources before the resource is matched with the incoming HTTP
request. A pre-match filter is typically used to update the HTTP method in
the request and is capable of altering the matching algorithm. A
post-match filter is applied after the resource method has been matched.
You can convert any ContainerRequestFilter
to a
pre-match filter by adding the @PreMatching
annotation.
On the server side, the filters can be registered in four different ways:
- Globally bound to all resources and all methods in them
By default, if no annotation is specified on the filter, then it is globally bound—that is, it all applies to methods on all resources in an application.
- Globally bound to all resources and methods via the
meta-annotation
@NameBinding
The annotation may be specified on the
Application
class and then the filter becomes globally enabled on all methods for all resources:@ApplicationPath
(
"webresources"
)
@ServerLogged
public
class
MyApplication
extends
Application
{
//. . .
}
- Statically bound to a specific resource/method via the
meta-annotation
@NameBinding
A filter may be statically targeted to a resource class or method via the meta-annotation
@NameBinding
as follows:@NameBinding
@Target
({
ElementType
.
TYPE
,
ElementType
.
METHOD
})
@Retention
(
value
=
RetentionPolicy
.
RUNTIME
)
public
@interface
ServerLogged
{}
This annotation then needs to be specified on the filter implementation and the resource class and/or method:
@Provider
@ServerLogged
public
class
ServerLoggingFilter
implements
ContainerRequestFilter
,
ContainerResponseFilter
{
//. . .
}
@Path
(
"orders"
)
@ServerLogged
public
class
MyResource
{
//. . .
}
If the annotation is specified on a resource, then the filter is applied to all methods of the resource. If the annotation is specified on a specific resource method, then the filter is applied only when that particular method is invoked.
@NameBinding
annotation is ignored on filters marked with@PreMatch
.- Dynamically bound to a specific resource/method via
DynamicFeature
A non−globally bound filter (i.e., a filter annotated with
@NameBinding
) can be dynamically bound to a resource or a method within a resource viaDynamicFeature
. For example, the following feature binds all resource methods inMyResource
that are annotated with@GET
:@Provider
public
class
DynamicServerLogggingFilterFeature
implements
DynamicFeature
{
@Override
public
void
configure
(
ResourceInfo
ri
,
Configurable
c
)
{
if
(
MyResource
.
class
.
isAssignableFrom
(
ri
.
getResourceClass
())
&&
ri
.
getResourceMethod
().
isAnnotationPresent
(
GET
.
class
))
{
c
.
register
(
new
ServerLoggingFilter
());
}
}
}
In this code,
ServerLoggingFilter
is configured on the methods ofMyResource
marked with theGET
annotation. This feature is marked with@Provider
and is thus automatically discovered by the JAX-RS runtime. Dynamic binding is ignored on filters marked with@PreMatch
.
Multiple filters may be implemented at each extension point and
arranged in filter chains. Filters in a chain are
sorted based on their priorities and are executed in order. Priorities are
defined through the @javax.annotation.Priority
annotation and
represented by integer numbers. The Priorities
class defines
built-in priorities for security, decoders/encoders, and more. The default
binding priority is Priorities.USER
.
The priorities for ClientRequestFilter
and
ContainerRequestFilter
are sorted in ascending order; the
lower the number, the higher the priority. The priorities for ContainerResponseFilter
and
ClientResponseFilter
are sorted in descending order; the
higher the number, the higher the priority. These rules ensure that
response filters are executed in reverse order of request filters.
The priority of ClientLoggingFilter
defined previously
can be changed as shown:
@Provider
@Priority
(
Priorities
.
HEADER_DECORATOR
)
public
class
ClientLoggingFilter
implements
ClientRequestFilter
,
ClientResponseFilter
{
//. . .
}
Filters are executed without method invocation wrapping (i.e., filters execute in their own silos and do not directly invoke the next filter in the chain). The JAX-RS runtime decides whether to invoke the next filter or not.
ClientRequestFilter
and
ContainerRequestFilter
can stop the execution of their
corresponding chains by calling abortWith(Response)
.
Entity interceptors are mainly concerned with marshaling and
unmarshaling of HTTP message bodies. They implement
ReaderInterceptor
or WriterInterceptor
, or
both.
WriterInterceptor
operates on the outbound request on
the client side and on the outbound response on the server side:
public
class
MyWriterInterceptor
implements
WriterInterceptor
{
@Override
public
void
aroundWriteTo
(
WriterInterceptorContext
wic
)
throws
IOException
,
WebApplicationException
{
wic
.
setOutputStream
(
new
FilterOutputStream
(
wic
.
getOutputStream
())
{
final
ByteArrayOutputStream
baos
=
new
ByteArrayOutputStream
();
@Override
public
void
write
(
int
b
)
throws
IOException
{
baos
.
write
(
b
);
super
.
write
(
b
);
}
@Override
public
void
close
()
throws
IOException
{
System
.
out
.
println
(
"MyClientWriterInterceptor --> "
+
baos
.
toString
());
super
.
close
();
}
});
wic
.
proceed
();
}
}
ReaderInterceptor
operates on the outbound response on
the client side and on the inbound request on the server side:
public
class
MyReaderInterceptor
implements
ReaderInterceptor
{
@Override
public
Object
aroundReadFrom
(
ReaderInterceptorContext
ric
)
throws
IOException
,
WebApplicationException
{
final
InputStream
old
=
ric
.
getInputStream
();
ByteArrayOutputStream
baos
=
new
ByteArrayOutputStream
();
int
c
;
while
((
c
=
old
.
read
())
!=
-
1
)
{
baos
.
write
(
c
);
}
...
=
baos
.
toString
();
ric
.
setInputStream
(
new
ByteArrayInputStream
(
baos
.
toByteArray
()));
return
ric
.
proceed
();
}
}
As with filters, there is an interceptor
chain for each kind of entity interceptor. Entity interceptors
in a chain are sorted based on their priorities and are executed in order.
Priorities are defined via the @javax.annotation.Priority
annotation and represented by integer numbers. The Priorities
class defines built-in priorities
for security, decoders/encoders, and more. The default binding priority is
Priorities.USER
. The priorities are sorted in ascending
order; the lower the number, the higher the priority.
Filters and entity interceptors may be specified on a client and resource. Figure 4-1 shows the invocation sequence on both client and server.
Bean Validation 1.1 allows declarative validation of resources. The constraints can be specified in any location in which the JAX-RS binding annotations are allowed, with the exception of constructors and property setters:
@Path
(
"/names"
)
public
class
NameResource
{
@NotNull
@Size
(
min
=
1
)
@FormParam
(
"firstName"
)
private
String
firstName
;
@NotNull
@Size
(
min
=
1
)
@FormParam
(
"lastName"
)
private
String
lastName
;
@FormParam
(
"email"
)
public
void
setEmail
(
String
)
{
this
.
=
;
}
public
String
getEmail
()
{
return
;
}
//. . .
}
In this code:
firstName
andlastName
are fields initialized via injection. These fields cannot benull
and must be at least one character.email
is a resource class property, and the constraint annotation is specified on the corresponding getter.
You can specify cross-field and cross-property constraints by declaring annotations on the class:
@NotNullAndNonEmptyNames
public
class
NameResource
{
@FormParam
(
"firstName"
)
private
String
firstName
;
@FormParam
(
"lastName"
)
private
String
lastName
;
//. . .
}
In this code, @NotNullAndNonEmptyNames
is a custom
constraint that requires the first and last name to be not null
and both the names to be at least one
character long.
You can map request entity bodies to resource method parameters and validate them by specifying the constraint on the resource method parameter:
public
class
NameResource
{
@POST
@Consumes
(
"application/json"
)
public
void
addAge
(
@NotNull
@Min
(
16
)
@Max
(
25
)
int
age
)
{
//. . .
}
}
In this code, the request entity body is mapped to the
age
parameter and must be not null
and between the values of 16 and 25.
Alternatively, if the request entity is mapped to a bean that is decorated
with constraint annotations already, then @Valid
can be used
to trigger the validation:
public
class
NameResource
{
@POST
@Consumes
public
void
addName
(
@Valid
Name
name
)
{
//. . .
}
}
JAX-RS constraint validations are carried out in the
Default
validation group only. Processing other validation
groups is not required.
Get Java EE 7 Essentials now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.