JAX-RS 2.0 introduces the client framework for the first time, which also supports callbacks and asynchronous request and response. The really nice feature of this API, improves on the writing invocations of the JAX-RS servers by hand. As you saw in the section called Test-Driven Development with JAX-RS, writing URL code and the I/O in standard Java can be, how can I say, laborious?
The client API lies in the javax.ws.js.client package, which contains useful classes such as AsyncInvoker , Client , ClientBuilder , Entity , SyncInvoker and WebTarget.
The following table outlines the responsibilities of these classes.
It is very straight forward to connect to resource URI using the JAX-RS Client API. The first class to examine is the javax.ws.js.client.ClientBuilder
, which has a static method called newBuilder()
. This method returns a ClientBuilder
that the developer can configure independently with javax.net.ssl.SSLContext
and also supply java.security.KeyStore
for encryption. The overloaded methods on the client builder keyStore()
and SSLContext()
provide the configuration.
If your application is not using security at the moment through SSL, then you can invoke the static method newClient()
and obtain a javax.ws.js.client.Client
instance. With this object, you can configure the target, the resource URI that will be called with the method target()
, which returns a javax.ws.js.client.WebTarget
instance.
With WebTarget
, you configure additional path, query parameters, and matrix parameters. Invoking the method request()
on the web target returns a javax.ws.js.client.Invocation.Builder
instance.
Finally, as the developer, you get to invoke the request to the server, the remote resource URI with the call to get()
, put()
, post()
, or delete()
.
On the face of it, going through this chain of object classes, might appear to be confusing and complicated, but actually it is quite an elegant design and a clear definition of separation of concerns. By the way, the Invocation.Builder
interface is an extension of the javax.ws.js.client.SyncInvoker
interface.
Let us rewrite the first unit test client that we saw for the book list to use this new JAX-RS client side API. The following is the new class RestfulBookServiceClientTest
in its entirety:
package je7hb.jaxrs.basic; /* imports ommitted */ import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.Response; public class RestfulBookServiceClientTest { private static SimpleEmbeddedRunner runner; private static WebArchive webArchive; @BeforeClass public static void assembleDeployAndStartServer() throws Exception { /* See the book's source code .. */ } /* ... */ @Test public void shouldRetrieveBookList() { WebTarget target = ClientBuilder.newClient() .target("http://localhost:8080/mywebapp/rest/books"); Response response = target.request().get(); assertNotNull(response); String text = response.readEntity( String.class ); String arr[] = text.split(" "); assertEquals("Sherlock Holmes and the Hounds of the Baskervilles", arr[0] ); assertEquals("Da Vinci Code", arr[1] ); assertEquals("Great Expectations", arr[2]); assertEquals( "Treasure Island", arr[3] ); } }
In this integration test RestfulBookServiceClientTest
, we make use of ShinkWrap
in order to create a virtual WAR file. We then launch an embedded GlassFish instance and deploy the WAR file to it. The new code is the ClientBuilder
invocation, which creates a Client
instance and then the WebTarget
instance. The unit test invokes the request URI on the server and it retrieves a javax.ws.js.coreResponse
object.
All we need to do with the response is retrieve the content and we do that by reading the entity as a String. Behind the scenes the method
readEntity()
opens java.io.InputStream
and performs more or less the same code in the older unit test, except since the JAX-RS 2.0 does this, means that our code is much cleaner.
With the content as a Java String, we just split it to an array by the delimited new line characters and run the assertions to complete the test.
What happens if there is an issue with the server? The target resource at the URI fails to generate an OK response, HTTP Response Code 200. If there is an error the JAX-RS runtime will do its best to map the error code to an exception under the package javax.ws.js.ext
. This package defines exceptions that correspond to the HTTP response error codes and the classes are named like BadRequestException
, ForbiddenException
, InternalServerErrorException
and ServiceUnavailableException
to name a few.
The client JAX-RS also has a means for generating an asynchronous request. Now this is potentially useful for building a type of non-blocking request and response architecture. The design of the JAX-RS API, again, makes this avenue remarkably simple.
Asynchronous client request can rely on a java.util.concurrent.Future
or an Invocation
callback method that the developer provides. Let's look at the Future option first.
The following is a new unit test RestfulBookServiceAsyncClientTest
:
public class RestfulBookServiceAsyncClientTest { /* ... as before ... */ @Test public void shouldRetrieveBookListAsynchronously() throws Exception { WebTarget target =ClientBuilder.newClient() .target( "http://localhost:8080/mywebapp/rest/books"); Future<Response> future =target.request().async().get(); Response response = future.get(3, TimeUnit.SECONDS ); String text = response.readEntity(String.class); String arr[] = text.split(" "); assertEquals("Sherlock Holmes and the Hounds ofthe Baskervilles", arr[0]); assertEquals("Da Vinci Code", arr[1]); assertEquals("Great Expectations", arr[2]); assertEquals( "Treasure Island", arr[3]); } }
The essential difference in the asynchronous version compared to the synchronous one is the addition of the async()
method call after the request()
method. This method call returns an instance of javax.ws.js.client.AsyncInvoker
. The difference with this type is all of the overloaded method calls on it such as get()
, put()
, post()
, and delete()
return Future objects, which means the request to the remote server does not block the calling Java thread.
In order to retrieve the response from the server wrap in the Future object, we invoke the get()
method and in the unit test example we supply a timeout value. Of course, this call will block the calling Java thread during that duration, and then there is the possibility of the value being ready or not. Still, the call duration is useful for situations where you require some execution time limit, and once the Future
has been retrieved it becomes immutable, you cannot reuse it. Instead, you must make another invocation of the web service.
The JAX-RS Client API provides another way to find out the response of an asynchronous invocation. The programmer, in this case, creates and supplies a callback object of the type InvocationCallback<Response>
.
The following is a further example in the asynchronous unit test class:
public class RestfulBookServiceAsyncClientTest { /* ... as before ... */ private static class MyCallback implements InvocationCallback<Response> { public CountDownLatch ready = new CountDownLatch(1); public volatile String text = ""; public volatile Throwable failure = null; @Override public void completed(Response response) { text = response.readEntity( String.class ); ready.countDown(); } @Override public void failed(Throwable throwable) { failure = throwable; ready.countDown(); } } @Test public void shouldRetrieveBookListAsyncWithCallback() { WebTarget target =ClientBuilder.newClient() .target("http://localhost:8080/mywebapp/rest/books"); MyCallback callback = new MyCallback(); Future<Response> future =target.request().async().get(callback); try { callback.ready.await(3, TimeUnit.SECONDS); if ( callback.failure != null ) callback.failure.printStackTrace(System.err); assertNull(callback.failure); String arr[] = callback.text.split(" "); assertEquals("Sherlock Holmes and the Hounds of "+"the Baskervilles", arr[0] ); assertEquals("Da Vinci Code", arr[1] ); assertEquals("Great Expectations", arr[2]); assertEquals( "Treasure Island", arr[3] ); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } } }
The class MyCallback
implements the javax.ws.js.client.InvocationCallback
interface. We use a java.util.concurrency.CountDownLatch
so that we can ensure that this class is actually invoked by the JAX-RS run time in either the success or failure capacity. JAX-RS invokes the completed()
method if the data is fully available. On an error, JAX-RS invokes the failed()
method. In either case, we count down the latch to zero and record the salient information for later. It is important to note, that the callback executes on a different thread to the unit test method, which is why we must be careful in our concurrency synchronization. It is so very easy to get multi-threading badly wrong in Java.
The method shouldRetrieveBookListAsyncWithCallback()
is largely the same as before. Instead, we invoke the invocation builder with get()
call and pass an instance of our callback MyCallback
to it. Incidentally, this call returns a future object, however we are not using it in this unit test method.
We await the countdown latch to hit zero inside the unit test method. When it does, we know that the callback has been invoked. If the callback was invoked because of failure, we print the stack trace to the standard error channel. On normal execution in the unit test method thread, we can retrieve the text string and perform the assertions.
This example does illustrate that may be a developer should separate the business model logic of validating data from the infrastructure of JAX-RS request and response.
3.145.17.18