7.5.  Use the Jersey client API to access a JAX-RS resource.

The Jersey client API is a high-level Java based API for interoperating with RESTful Web services. It makes it very easy to interoperate with RESTful Web services and enables a developer to concisely and efficiently implement a reusable client-side solution that leverages existing and well established client-side HTTP implementations.

The Jersey client API can be utilized to interoperate with any RESTful Web service, implemented using one of many frameworks, and is not restricted to services implemented using JAX-RS. However, developers familiar with JAX-RS should find the Jersey client API complementary to their services, especially if the client API is utilized by those services themselves, or to test those services.

The goals of the Jersey client API are threefold:

  1. Encapsulate a key constraint of the REST architectural style, namely the Uniform Interface Constraint and associated data elements, as client-side Java artifacts;

  2. Make it as easy to interoperate with RESTful Web services as JAX-RS makes it easy to build RESTful Web services; and

  3. Leverage artifacts of the JAX-RS API for the client side. Note that JAX-RS is currently a server-side only API.

The Jersey Client API supports a pluggable architecture to enable the use of different underlying HTTP client implementations. Two such implementations are supported and leveraged: the Http(s)URLConnection classes supplied with the JDK; and the Apache HTTP client.

Uniform Interface Constraint

The uniform interface constraint bounds the architecture of RESTful web services so that a client, such as a browser, can utilize the same interface to communicate with any service. This is a very powerful concept in software engineering that makes Web-based search engines and service mash-ups possible. It induces properties such as:

  1. simplicity, the architecture is easier to understand and maintain; and

  2. modifiability or loose coupling, clients and services can evolve over time perhaps in new and unexpected ways, while retaining backwards compatibility.

Further constraints are required:

  1. every resource is identified by a URI;

  2. a client interacts with the resource via HTTP requests and responses using a fixed set of HTTP methods;

  3. one or more representations can be retured and are identified by media types; and

  4. the contents of which can link to further resources.

The above process repeated over and again should be familiar to anyone who has used a browser to fill in HTML forms and follow links. That same process is applicable to non-browser based clients.

Many existing Java-based client APIs, such as the Apache HTTP client API or java.net.HttpURLConnection supplied with the JDK place too much focus on the Client-Server constraint for the exchanges of request and responses rather than a resource, identified by a URI, and the use of a fixed set of HTTP methods.

A resource in the Jersey client API is an instance of the Java class WebResource, and encapsulates a URI. The fixed set of HTTP methods are methods on WebResource or if using the builder pattern (more on this later) are the last methods to be called when invoking an HTTP method on a resource. The representations are Java types, instances of which, may contain links that new instances of WebResource may be created from.

Ease of use and reusing JAX-RS artifacts

Since a resource is represented as a Java type it makes it easy to configure, pass around and inject in ways that is not so intuitive or possible with other client-side APIs.

The Jersey Client API reuses many aspects of the JAX-RS and the Jersey implementation such as:

  1. URI building using UriBuilder and UriTemplate to safely build URIs;

  2. Support for Java types of representations such as byte[], String, InputStream, File, DataSource and JAXB beans in addition to Jersey specific features such as JSON support and MIME Multipart support.

  3. Using the builder pattern to make it easier to construct requests.

Some APIs, like the Apache HTTP client or java.net.HttpURLConnection, can be rather hard to use and/or require too much code to do something relatively simple.

This is why the Jersey Client API provides support for wrapping HttpURLConnection and the Apache HTTP client. Thus it is possible to get the benefits of the established implementations and features while getting the ease of use benefit.

It is not intuitive to send a POST request with form parameters and receive a response as a JAXB object with such an API. For example with the Jersey API this is very easy:

Form f = new Form();
f.add("x", "foo");
f.add("y", "bar");

Client c = Client.create();
WebResource r = c.resource("http://localhost:8080/form");

JAXBBean bean = r
	.type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)
	.accept(MediaType.APPLICATION_JSON_TYPE)
	.post(JAXBBean.class, f);
					

In the above code a Form is created with two parameters, a new WebResource instance is created from a Client then the Form instance is POSTed to the resource, identified with the form media type, and the response is requested as an instance of a JAXB bean with an acceptable media type identifying the Java Script Object Notation (JSON) format. The Jersey client API manages the serialization of the Form instance to produce the request and de-serialization of the response to consume as an instance of a JAXB bean.

If the code above was written using HttpURLConnection then the developer would have to write code to serialize the form sent in the POST request and de-serialize the response to the JAXB bean. In addition further code would have to be written to make it easy to reuse the same resource "http://localhost:8080/form" that is encapsulated in the WebResource type.

Overview of the API

To utilize the client API it is first necessary to create an instance of a Client, for example:

Client c = Client.create();
					

The client instance can then be configured by setting properties on the map returned from the getProperties methods or by calling the specific setter methods, for example the following configures the client to perform automatic redirection for appropriate responses:

c.getProperties().put(ClientConfig.PROPERTY_FOLLOW_REDIRECTS, true);
					

which is equivalent to the following:

 c.setFollowRedirects(true);
					

Alternatively it is possible to create a Client instance using a ClientConfig object for example:

ClientConfig cc = new DefaultClientConfig();
cc.getProperties().put(ClientConfig.PROPERTY_FOLLOW_REDIRECTS, true);
Client c = Client.create(cc);
					

Once a client instance is created and configured it is then possible to obtain a WebResource instance, which will inherit the configuration declared on the client instance. For example, the following creates a reference to a Web resource with the URI "http://localhost:8080/xyz":

WebResource r = c.resource("http://localhost:8080/xyz");
					

and redirection will be configured for responses to requests invoked on the Web resource.

Client instances are expensive resources. It is recommended a configured instance is reused for the creation of Web resources. The creation of Web resources, the building of requests and receiving of responses are guaranteed to be thread safe. Thus a Client instance and WebResource instances may be shared between multiple threads.

In the above cases a WebResource instance will utilize HttpUrlConnection or HttpsUrlConnection, if the URI scheme of the WebResource is "http" or "https" respectively.

Requests to a Web resource are built using the builder pattern (RequestBuilder) where the terminating method corresponds to an HTTP method (UniformInterface). For example:

String response = r.accept(
    MediaType.APPLICATION_JSON_TYPE,
    MediaType.APPLICATION_XML_TYPE).
    header("X-FOO", "BAR").
    get(String.class);
					

The above sends a GET request with an Accept header of application/json, application/xml and a non-standard header X-FOO of BAR.

If the request has a request entity (or representation) then an instance of a Java type can be declared in the terminating HTTP method, for PUT, POST and DELETE requests. For example, the following sends a POST request:

String request = "content";
String response = r.accept(
    MediaType.APPLICATION_JSON_TYPE,
    MediaType.APPLICATION_XML_TYPE).
    header("X-FOO", "BAR").
    post(String.class, request);
					

where the String "content" will be serialized as the request entity.

The Content-Type of the request entity may be declared using the type builder method as follows:

String response = r.accept(
    MediaType.APPLICATION_JSON_TYPE,
    MediaType.APPLICATION_XML_TYPE).
    header("X-FOO", "BAR").
    type(MediaType.TEXT_PLAIN_TYPE).
    post(String.class, request);
					

or alternatively the request entity and type may be declared using the entity method as follows:

String response = r.accept(
    MediaType.APPLICATION_JSON_TYPE,
    MediaType.APPLICATION_XML_TYPE).
    header("X-FOO", "BAR").
    entity(request, MediaType.TEXT_PLAIN_TYPE).
    post(String.class);
					

If the response has a entity (or representation) then the Java type of the instance required is declared in the terminating HTTP method. In the above examples a response entity is expected and an instance of String is requested. The response entity will be de-serialized to a String instance.

If response meta-data is required then the Java type ClientResponse can be declared from which the response status, headers and entity may be obtained. For example, the following gets both the entity tag and response entity from the response:

ClientResponse response = r.get(ClientResponse.class);
EntityTag e = response.getEntityTag();
String entity = response.getEntity(String.class);
					

If the ClientResponse type is not utilized and the response status is greater than or equal to 300 then the runtime exception UniformInterfaceException is thrown. This exception may be caught and the ClientResponse obtained as follows:

try {
    String entity = r.get(String.class);
} catch (UniformInterfaceException ue) {
    ClientResponse response = ue.getResponse();
}
					

A new WebResource can be created from an existing WebResource by building from the latter's URI. Thus it is possible to build the request URI before building the request. For example, the following appends a new path segment and adds some query parameters:


WebResource r = c.resource("http://localhost:8080/xyz");

MultivaluedMap<String, String> params = MultivaluedMapImpl();
params.add("foo", "x");
params.add("bar", "y");

String response = r.path("abc").
    queryParams(params).
    get(String.class);

					

that results in a GET request to the URI "http://localhost:8080/xyz/abc?foo=x&bar=y".

All the Java types for representations supported by the Jersey server side for requests and responses are also supported on the client side. This includes the standard Java types as specified by JAX-RS in section 4.2.4 in addition to JSON, Atom and Multipart MIME as supported by Jersey.

To process a response entity (or representation) as a stream of bytes use InputStream as follows:

InputStream in = r.get(InputStream.class);
// Read from the stream
...
in.close();
					

Note that it is important to close the stream after processing so that resources are freed up.

To POST a file use File as follows:

File f = ...
String response = r.post(String.class, f);
					

Adding support for new representations

The support for new application-defined representations as Java types requires the implementation of the same provider-based interfaces as for the server side JAX-RS API, namely MessageBodyReader and MessageBodyWriter, respectively, for request and response entities (or inbound and outbound representations).

Classes or implementations of the provider-based interfaces need to be registered with a ClientConfig and passed to the Client for creation. The following registers a provider class MyReader which will be instantiated by Jersey:

ClientConfig cc = new DefaultClientConfig();
cc.getClasses().add(MyReader.class);
Client c = Client.create(cc);
					

The following registers an instance or singleton of MyReader:

ClientConfig cc = new DefaultClientConfig();
MyReader reader = ...
cc.getSingletons().add(reader);
Client c = Client.create(cc);
					

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