Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

 

 

 

 

 

Span
stylefont-size:2em;font-weight:bold
JAX-RS : Client API
 

 

 

 

 

Table of Contents

Maven Dependency

Code Block
xml
xml

<dependency>
  <groupId>org.apache.cxf</groupId>
  <artifactId>cxf-rt-rs-client</artifactId>
  <version>3.0.0-milestone1</version>
</dependency>

...

CXF Apache HttpClient based transport is required to get the asynchronous invocations working correctly:

Code Block
xml
xml

<dependency>
  <groupId>org.apache.cxf</groupId>
  <artifactId>cxf-rt-transports-http-hc</artifactId>
  <!-- 2.7.8 or 3.0.0-milestone1 --> 
  <version>${cxf.version}</version>
</dependency>

...

CXF 3.0.0 implements JAX-RS 2.0 Client API. Internally it is implemented in terms of CXF specific WebClient.

The javax.ws.rs.client provides a short overview of how JAX-RS 2.0 Client API works.

Typically, one starts from ClientBuilder in order to create a Client.
Next WebTarget is created and further customized as needed.

Next, Invocation.Builder is initialized and the request can be made immediately using one of the SyncInvoker methods, with the builder directly implementing SyncInvoker.

Code Block
java
java

Client client = ClientBuilder.newBuilder().newClient();
WebTarget target = client.target("http://localhost:8080/rs");
target = target.path("service").queryParam("a", "avalue");

Invocation.Builder builder = target.request();
Response response = builder.get();
Book book = builder.get(Book.class);

The above sequence can be easily collapsed into a single code sequence if preferred.
Note that SyncInvoker (and AsyncInvoker) expects Entity to represent the request body.

Invocation.Builder has a shortcut to Invocation via its build(...) methods to further customize the invocation.

...

Client and WebTarget are all can be individually configured, the implement Configurable interface which can accept the providers and properties and return Configuration. Configuring the Client directly or indirectly via ClientBuilder.withConfig method affects all the WebClients spawned by a given Client.

...

WebClient offers shortcuts to JAX-RS 2.0 AsyncInvoker and SyncInvoker interfaces.

WebClient.getConfig(Object client) supports JAX-RS 2.0 WebTarget and Invocation.Builder for 2.0 clients to be able to get to the lower-level CXF configuration and set up the properties such as 'receiveTimeout', etc.

...

For example, given these class definitions:

Code Block
java
java

@Path("/bookstore")
public interface BookStore {
   @GET
   Books getAllBooks();
   
   @Path("{id}")
   BookResource getBookSubresource(@PathParam("id") long id) throws NoBookFoundException;
}

public class BookStoreImpl implements BookStore {
   public Books getAllBooks() {}
   
   public Book getBookSubresource(long id) throws NoBookFoundException {}
}

public interface BookResource {
   @GET
   Book getBook();
}

public class BookResourceImpl implements BookResource {
   Book getBook() {}
}

the following client code retrieves a Book with id '1' and a collection of books:

Code Block
java
java

BookStore store = JAXRSClientFactory.create("http://bookstore.com", BookStore.class);
// (1) remote GET call to http://bookstore.com/bookstore
Books books = store.getAllBooks();
// (2) no remote call
BookResource subresource = store.getBookSubresource(1);
// {3} remote GET call to http://bookstore.com/bookstore/1
Book b = subresource.getBook();

...

Proxies end up implementing not only the interface requested at proxy creation time but also a Client interface. In many cases one does not need to explicitly specify commonly used HTTP headers such as Content-Type or Accept as this information will likely be available from @Consumes or @Produces annotations. At the same time you may explicitly set either of these headers, or indeed some other header. You can use a simple WebClient utility method for converting a proxy to a base client:

Code Block
java
java

BookStore proxy = JAXRSClientFactory.create("http://books", BookStore.class);
WebClient.client(proxy).accept("text/xml");
// continue using the proxy    

...

Using proxies is just one way to consume a service. Proxies hide away the details of how URIs are being composed while HTTP-centric WebClients provide for an explicit URI creation. Both proxies and http clients rely on the same base information such as headers and the current URI so at any moment of time you can create a WebClient instance out of the existing proxy:

Code Block
java
java

BookStore proxy = JAXRSClientFactory.create("http://books", BookStore.class);
Client client = WebClient.client(proxy);
WebClient httpClient = WebClient.fromClient(client);
// continue using the http client    

At any moment of time you can convert an http client into a proxy too:

Code Block
java
java

BookStore proxy1 = JAXRSClientFactory.create("http://books", BookStore.class);
Client client = WebClient.client(proxy1);
BookStore proxy2 = JAXRSClientFactory.fromClient(client, BookStore.class);

...

If no ResponseExceptionMapper is available when a remote invocation failed then an instance of javax.ws.rs.WebApplicationException will be thrown (Note org.apache.cxf.jaxrs.client.ServerWebApplicationException is used to represent the server exceptions before CXF 2.7.0.). At this point of time you can check the actual Response and proceed from there:

Code Block
java
java

BookStore proxy = JAXRSClientFactory.create("http://books", BookStore.class);
try {
    proxy.getBook();
} catch(WebApplicationException ex) {
  Response r = ex.getResponse();
  String message = ex.getMessage();
}

...

When creating a proxy with JAXRSClientFactory, you can pass a Spring configuration location as one of the arguments. Or you can create a default bus using Spring configuration and all proxies will pick it up:

Code Block
java
java

SpringBusFactory bf = new SpringBusFactory();
Bus bus = bf.createBus("org/apache/cxf/systest/jaxrs/security/jaxrs-https.xml");
BusFactory.setDefaultBus(bus);
// BookStore proxy will get the configuration from Spring
BookStore proxy = JAXRSClientFactory.create("http://books", BookStore.class);

...

For injecting proxies via a spring context, use the jaxrs:client element like:

Code Block
xml
xml

  <jaxrs:client id="restClient"
         address="http://localhost:${testutil.ports.BookServerRestSoap}/test/services/rest"
         serviceClass="org.apache.cxf.systest.jaxrs.BookStoreJaxrsJaxws"
         inheritHeaders="true">
         <jaxrs:headers>
             <entry key="Accept" value="text/xml"/>
         </jaxrs:headers>
  </jaxrs:client>  

...

When a proxy method returning a JAX-RS Response is invoked, the returned Response.getEntity() will return a response InputStream by default. Starting with CXF 2.3.2 one can register an org.apache.cxf.jaxrs.client.ResponseReader provider and cast the Response.getEntity() to more specific application classes:

Code Block
java
java

ResponseReader reader = new ResponseReader();
reader.setEntityClass(Book.class);
        
BookStore bs = JAXRSClientFactory.create("http://localhost:8080/books", BookStore.class,
                                         Collections.singletonList(reader));
Response r1 = bs.getBook("123");
Book book = (Book)r1.getEntity();

reader.setEntityClass(Author.class);
Response r2 = bs.getBookAuthor("123");
Author book = (Author)r2.getEntity();

...

Proxies can be created with the external user model being applied to a proxy class, for example:

Code Block
java
java

JAXRSClientFactory.createFromModel("http://books", BookNoAnnotations.class, "classpath:/resources/model.xml", null);

...

HTTP centric clients are WebClient instances which also implement the Client interface. In addition to setting various Client request properties, you can also make an explicit HTTP invocation with an HTTP verb being the name of a given operation :

Code Block
java
java

WebClient client = WebClient.create("http://books");
Book book = client.path("bookstore/books").accept("text/xml").get(Book.class);

You can choose to get an explicit JAX-RS Response instead and check the response code, headers or entity body if any:

Code Block
java
java

WebClient client = WebClient.create("http://books");
client.path("bookstore/books");
client.type("text/xml").accept("text/xml")
Response r = client.post(new Book());
Book b = r.readEntity(Book.class);

WebClient lets you get back to a base URI or to a previous path segment and move forward, it can be handy for getting a number of individual entries from a service with ids embedded in path segments :

Code Block
java
java

WebClient client = WebClient.create("http://books");
List<Book> books = getBooks(client, 1L, 2L, 3L)

private List<Book> getBooks(WebClient client, Long ...ids) {
   List<Book> books = new ArrayList<Book>(); 
   for (Long id : ids) {
       books.add(client.path(id).get(Book.class));
       client.back(); 
   } 
   return books;
}

...

If the request URI can be parameterized then you may want to use the following code:

Code Block
java
java

Book book = WebClient.create("http://books").path("{year}/{id}", 2010, 123).get(Book.class);
// as opposed to
// WebClient.create("http://books").path(2010).path(123).get(Book.class);

...

WebClient also has few collection-aware methods, example:

Code Block
java
java


Collection<? extends Book> books = WebClient.getCollection(Book.class);
Collection<? extends Book> books = WebClient.postAndGetCollection(new ArrayList<Book>(), Book.class);

...

Like proxies, HTTP clients can be created using a number of WebClient static utility methods: you can pass a location to a Spring configuration bean if needed or you can set up a default bus as shown above. For example:

Code Block
xml
xml

<bean id="myJsonProvider" 
class="org.apache.cxf.jaxrs.provider.JSONProvider" > 
        <property name="supportUnwrapped" value="true" /> 
        <property name="wrapperName" value="nodeName" /> 
    </bean> 

<util:list id="webClientProviders"> 
    <ref bean="myJsonProvider"/> 
</util:list> 

<bean id="myWebClient" class="org.apache.cxf.jaxrs.client.WebClient" 
factory-method="create"> 
        <constructor-arg type="java.lang.String" 
value="http://some.base.url.that.responds/" /> 
        <constructor-arg ref="webClientProviders" /> 
</bean> 

Note, starting from CXF 2.7.5 it is possible to set-up WebClient instances the same way as proxies, using jaxrs:client:

Code Block
xml
xml

<jaxrs:client id="webClient"
         address="https://localhost:${port}/services/rest"
         serviceClass="org.apache.cxf.jaxrs.client.WebClient">
         <jaxrs:headers>
             <entry key="Accept" value="text/xml"/>
         </jaxrs:headers>
  </jaxrs:client>

...

XML-centric clients are WebClients using an XMLSource utility class. XMLSource has a number of methods facilitating the retrieval of JAXB beans, individual properties or links with the help of XPath expressions. For example:

Code Block
java
java

WebClient wc = WebClient.create("http://aggregated/data");
XMLSource source = wc.get(XMLSource.class);
source.setBuffering(true);
Book b1 = source.getNode("/books/book[position() = 1]", Book.class);
Book b2 = source.getNode("/books/book[position() = 2]", Book.class);

...

A single client doing multiple invocations without changing the current URI or headers is thread-safe. The only limitation in this case applies to proxies, in that they can not get "out of band" headers without synchronizing, ex :

Code Block
java
java

// get some response headers passed to us 'out of band', which is not thread-safe for a plain proxy: 
String bookHeader = WebClient.toClient(injectedBookStoreProxy).getHeaders().getFirst("BookHeader"); 

...

If a number of incoming threads is limited then one option is just do nothing, while the other option is to reset the thread local state :

Code Block
java
java

try { 
   webClient.path("bar") 
   webClient.header("bar", baz); 
   webClient.invoke(...); 
} finally { 
   // if using a proxy: WebClient.client(proxy).reset(); 
   webClient.reset(); 
} 

...

Sometimes, you may want to configure a client instance after it is been created. For example, one may want to configure HTTPConduit programmatically, as opposed to setting its properties using Spring. ClientConfiguration represents a client-specific configuration state and can be accessed like this :

Code Block
java
java

Book proxy = JAXRSClientFactory.create("http://books", Book.class);
ClientConfiguration config = WebClient.getConfig(proxy);
HTTPConduit conduit1 = (HTTPConduit)config.getConduit();

WebClient webclient = WebClient.create("http://books");
HTTPConduit conduit2 = (HTTPConduit)WebClient.getConfig(webclient).getConduit();

...

Example :

Code Block
java
java

JAXRSClientFactoryBean sf = new JAXRSClientFactoryBean();
sf.setResourceClass(CustomerService.class);
sf.setAddress("http://localhost:9000/");
BindingFactoryManager manager = sf.getBus().getExtension(BindingFactoryManager.class);
JAXRSBindingFactory factory = new JAXRSBindingFactory();
factory.setBus(sf.getBus());
manager.registerBindingFactory(JAXRSBindingFactory.JAXRS_BINDING_ID, factory);
CustomerService service = sf.create(CustomerService.class);
WebClient wc = sf.createWebClient();

...

It is possible to have an HTTPConduit configuration which will apply to all clients using different request URIs or only to those with using a specific URI. For example:

Code Block
xml
xml

<http:conduit name="http://books:9095/bookstore.*"/> 

...

Alternatively you can just do:

Code Block
xml
xml

<http:conduit name="*.http-conduit"/> 

...

If you work with proxies then you can have the proxy-specific configuration using the expanded QName notation:

Code Block
xml
xml

<http:conduit name="{http://foo.bar}BookService.http-conduit"/> 

...

Similarly, for WebClients you can do:

Code Block
xml
xml

<http:conduit name="{http://localhost:8080}WebClient.http-conduit"/> 

...

Proxies and HTTP-centric clients can have the HTTP Authorization header set up explicitly:

Code Block
java
java


// Replace 'user' and 'password' by the actual values
String authorizationHeader = "Basic " 
    + org.apache.cxf.common.util.Base64Utility.encode("user:password".getBytes());

// proxies
WebClient.client(proxy).header("Authorization", authorizationHeader);

// web clients
webClient.header("Authorization", authorizationHeader);

or by providing a username and password pair at client creation time, for example:

Code Block
java
java

BookStore proxy = JAXRSClientFactory.create("http://books", BookStore.class, "username", "password", "classpath:/config/https.xml");

WebClient client = WebClient.create("http://books", "username", "password", "classpath:/config/https.xml");

...