JAX-RS client API

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?

Synchronous invocation

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.

Class

Description

AsyncInvoker

This is a Java interface that defines a uniform contract interface for asynchronous invocation.

Client

This is a Java interface that represents the contract of all clients, which are the main entry points to the client side API. The client defines a builder pattern for flexibility.

ClientBuilder

This is the abstract class for the Client API, which the developer configures in order to connect the request URI on the server side. The developer can optionally configure a client with SSL, security key store, and a hostname verifier.

ClientRequestContext

An interface that defines the contract for context data for the purpose of processing the client request.

ClientRequestFilter

An interface that defines the contract for a custom request filter. Implementations of this interface must be annotated with @javax.ws.rs.ext.Provider.

ClientResponseContext

An interface that defines the contract for context data for the purpose of processing the client response.

ClientResponseFilter

An interface that defines the contract for a custom response filter. Implementations of this interface must be annotated with @javax.ws.rs.ext.Provider.

Entity

A final class that encapsulates the message entity including the associate variant.

FactoryFinder

A final class for the JAX-RS time to find the implementation of the client framework.

Invocation

An interface that defines the contract for a client request invocation.

InvocationCallback

An interface that defines the contract callback that the client code implements to respond to events from processing the response.

ResponseProcessingException

An exception thrown if the JAX-RS runtime finds there is a problem in processing the response from the resource, which could be in an error in deserialization or failure to filter the entity.

SyncInvoker

The uniform interface to synchronous invocation.

WebTarget

Represents the resource target URI on the server side.

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.WebTargetinstance.

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.

Asynchronous invocation

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.17.18