Develop a CXF Client - BloomReach Experience - Open Source CMS

This article covers a Hippo CMS version 10. There's an updated version available that covers our most recent release.

15-02-2016

Develop a CXF Client

In the previous step you generated JAXB model classes for the remote service. This page describes how to create a simple CXF client using test-driven development.

Add Maven Dependencies

Add the following property to the properties section of the root pom.xml of your project:

<ehcache.version>2.6.0</ehcache.version>
<wink.version>1.3.0</wink.version>

Add the following dependencies to the  dependencyManagement section of the root pom.xml of your project:

      <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-rs-client</artifactId>
        <version>${cxf.version}</version>
      </dependency>

      <dependency>
        <groupId>net.sf.ehcache</groupId>
        <artifactId>ehcache-core</artifactId>
        <version>${ehcache.version}</version>
      </dependency>

      <dependency>
        <groupId>org.apache.wink</groupId>
        <artifactId>wink-component-test-support</artifactId>
        <version>${wink.version}</version>
      </dependency>    

(Note that ${cxf.version} is already defined in the hippo-cms7-project top level pom outside your project)

Add the following dependencies to site/pom.xml:

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

    <dependency>
      <groupId>net.sf.ehcache</groupId>
      <artifactId>ehcache-core</artifactId>
    </dependency>

    <dependency>
      <groupId>org.apache.wink</groupId>
      <artifactId>wink-component-test-support</artifactId>
      <scope>test</scope>
    </dependency>    

Create a Test Class to Test the Client

Start by creating a test class TestClient and testCXFClient method (it won't compile until you've created the client class as well). The following test will create a direct service call to www.demo.onehippo.com and retrieve a list of the top ten products. It will also test if the returned list is of size 10.

site/src/test/java/org/example/rest/client/ClientTest.java:

package org.example.rest.client;

import junit.framework.Assert;

import org.example.jaxb.Products;
import org.junit.Test;

public class ClientTest {

    @Test
    public void testCXFClient() throws Exception {
        GoGreenClient client = new GoGreenClient();
        final Products topTenProducts = client.getTopTenProducts();
        Assert.assertNotNull(topTenProducts);
        Assert.assertTrue(topTenProducts.getProduct().size() == 10);
    }    

}
You can add the @Ignore annotation to testCXFClient so it won't break your build in case  www.demo.onehippo.com is unreachable.

Create the Client Class

Create a class GoGreenClient in the test section of the site module of your project.

Make sure the client has getters and setters for configuration purposes.

Use org.apache.cxf.jaxrs.client.WebClient#create to create a web client. The WebClient object is chainable by several functions such as: accept type header, path, query, return type, post actions etc.

site/src/test/java/org/example/rest/client:

package org.example.rest.client;

import javax.ws.rs.core.MediaType;

import org.apache.cxf.jaxrs.client.WebClient;
import org.example.jaxb.Products;

public class GoGreenClient {

    private String baseAddress = "https://www.demo.onehippo.com/";
    private String path = "restapi/topproducts";

    public Products getTopTenProducts() {
        return WebClient.create(baseAddress).path(path).accept(MediaType.APPLICATION_XML)
                        .query(typeParam, type).query("_type", "xml").get(Products.class);
    }

    public String getBaseAddress() {
        return baseAddress;
    }

    public void setBaseAddress(String baseAddress) {
        this.baseAddress = baseAddress;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }
}

Also add the following basic CXF configuration:

src/test/resources/cxf.xml

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:sec="http://cxf.apache.org/configuration/security"
  xmlns:http="http://cxf.apache.org/transports/http/configuration"
  xsi:schemaLocation=" http://cxf.apache.org/transports/http/configuration http://cxf.apache.org/schemas/configuration/http-conf.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  <http:conduit name="*.http-conduit">
    <http:tlsClientParameters useHttpsURLConnectionDefaultHostnameVerifier="true" />
  </http:conduit>
</beans>

Now you can compile and run the test, and further develop the client.

Set Timeouts

This is crucial for HST components that use remote service calls. In general it’s a good practice to set a timeout on such a remote connection call. When this timeout is not set and the connection cannot be established, the site will ‘hang’ for the end-user and this can cause an unresponsive website.
The following method can manipulate the receive and connection timeout of a remote service call:

    private void setTimeouts(final WebClient client, final long connectionTimeout, final long receiveTimeout) {
        HTTPConduit conduit = WebClient.getConfig(client).getHttpConduit();
        if (receiveTimeout != 0) {
            conduit.getClient().setReceiveTimeout(receiveTimeout);
        }
        if (connectionTimeout != 0) {
            conduit.getClient().setConnectionTimeout(connectionTimeout);
        }
    }

Add attributes connectionTimeout and receiveTimeout to the GoGreenClient class:

    private long connectionTimeout = 5000L;
    private long receiveTimeout = 5000L;

And their corresponding getters and setters:

    public long getConnectionTimeout() {
        return connectionTimeout;
    }

    public void setConnectionTimeout(long connectionTimeout) {
        this.connectionTimeout = connectionTimeout;
    }

    public long getReceiveTimeout() {
        return receiveTimeout;
    }

    public void setReceiveTimeout(long receiveTimeout) {
        this.receiveTimeout = receiveTimeout;
    }

Modify the getTopTenProducts method:

    public Products getTopTenProducts() {
        WebClient client = WebClient.create(baseAddress).path(path).accept(MediaType.APPLICATION_XML)
                                    .query("_type", "xml");
        setTimeouts(client, connectionTimeout, receiveTimeout);
        return client.get(Products.class);
    }

Add Response Caching

It is important for performance reasons to cache a remote service call response so that the remote service doesn’t experience an unnecessary load for each call.

For caching Hippo recommends using the built-in EhCache.

In the GoGreenClient class declare static strings for the name of the cache and the cache key you are going to use:

    private static final String GO_GREEN_CACHE_NAME = "GoGreenCache";
    private static final String KEY_TOP_TEN_PRODUCTS = "topTenProducts";

Also add a CacheManager attribute:

    // create a singleton CacheManager using defaults
    private CacheManager manager = CacheManager.create();

Add an init method:

    public void init() {
        log.info("(Re-)Initializing GoGreenClient");
        // (re)create cache
        manager.removeCache(GO_GREEN_CACHE_NAME);
        Cache testCache = new Cache(new CacheConfiguration(GO_GREEN_CACHE_NAME, 100));
        manager.addCache(testCache);
    }

Finally modify the getTopTenProducts method so it uses the cache:

    public Products getTopTenProducts() {
        final Cache testCache = manager.getCache(GO_GREEN_CACHE_NAME);
        // try to retrieve element from cache
        final Element element = testCache.get(KEY_TOP_TEN_PRODUCTS);
        if (element != null) {
            return (Products) element.getObjectValue();
        } else {
            // if cache element does not exist retrieve object and place in cache
            final WebClient client = WebClient.create(baseAddress).path(path).accept(MediaType.APPLICATION_XML)
                    .query("_type", "xml")
                    .query("sortby", "hippogogreen:rating").query("max", 10);
            setTimeouts(client, this.connectionTimeout, this.receiveTimeout);
            final Products products = client.get(Products.class);
            testCache.put(new Element(KEY_TOP_TEN_PRODUCTS, products));
            return products;
        }
    }

Make sure ClientTest#testCXFClient calls your client's init method:

    @Test
    public void testCXFClient() throws Exception {
        GoGreenClient client = new GoGreenClient();
        client.init();
        final Products topTenProducts = client.getTopTenProducts();
        Assert.assertNotNull(topTenProducts);
        Assert.assertTrue(topTenProducts.getProduct().size() == 10);
    }    

(Optional) Mocking the Remote Service

Apache Wink provides a MockHttpServer which can be used to test your client without relying on the remote service.

Create a new test method in ClientTest which starts up the mock server and uses model.xml as response:

    @Test
    public void testEmbeddedClient() throws Exception {
        // set up mock server
        MockHttpServer mockServer = new MockHttpServer(3333);
        mockServer.startServer();
        String baseAddress = "http://localhost:" + mockServer.getServerPort() + "/";
        String path = "restapi/topproducts";
        
        // set up mock response
        MockHttpServer.MockHttpServerResponse response = new MockHttpServer.MockHttpServerResponse();
        response.setMockResponseContent(IOUtils.toString(getClass().getResourceAsStream("/model.xml"), "UTF-8"));
        response.setMockResponseContentType(MediaType.APPLICATION_XML);
        response.setMockResponseCode(200);
        mockServer.setMockHttpServerResponses(response);
        
        // set up client
        GoGreenClient client = new GoGreenClient();
        client.init();
        client.setBaseAddress(baseAddress);
        client.setPath(path);
        
        // run client
        final Products topTenProducts = client.getTopTenProducts();
        Assert.assertNotNull(topTenProducts);
        Assert.assertTrue(topTenProducts.getProduct().size() == 10);
        
        // stop mock server
        mockServer.stopServer();
    }

Move Tested Code to Project

Now that you have developed and tested your client code, move the GoGreenClient class from the test section of your project to the main Java section:

site/src/main/java/org/example/rest/client/GoGreenClient.java

Move the CXF configuration as well:

site/src/main/resources/cxf.xml

Also add the following two Spring bean configuration files to the site module of your project so that the REST client is available to the delivery tier.

site/src/main/resources/META-INF/hst-assembly/addon/module.xml

<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="http://www.onehippo.org/schema/hst/hst-addon-module_1_0.xsd">
  <name>org.example.rest.client</name>
  <config-locations>
    <config-location>classpath*:META-INF/hst-assembly/addon/org/example/rest/client/*.xml</config-location>
  </config-locations>
</module>

site/src/main/resources/META-INF/hst-assembly/addon/org/example/rest/client/SpringComponentManager-ignite.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

  <bean id="org.example.rest.client.GoGreenClient" class="org.example.rest.client.GoGreenClient"
    init-method="init">
  </bean>

</beans>

Working with JSON

Serializing to JSON can be quite troublesome because there isn’t always a one-on-one mapping between XML and JSON.

If you would call the same REST service from the www.demo.onehippo.com and the JSON response:

https://www.demo.onehippo.com/restapi/topproducts?_type=json

You will see that there aren’t any wrapping elements. For JAXB it is necessary to have wrapping elements.

For example to support the following snippet from above URL:

[
    {
        name: "organic-cotton-reusable-lunch-bag",
        localizedName: "Organic Cotton Reusable Lunch Bag",
        ...
    },
    {
]

It needs to be converted to the following snippet for Jackson to serialize this as it would serialize the XML variant:

{" products": [
    {
        "product": {
            "name": "organic-cotton-reusable-lunch-bag",
            "localizedName": "Organic Cotton Reusable Lunch Bag",
            ...
        }
    },
    {
]

It’s highly recommended to address the REST services on wrapping elements instead of fixing this with your own Provider.

If by any chance you need to create your own provider you’ll have to create the WebClient with a list of providers at creation: org.apache.cxf.jaxrs.client.WebClient#create.

There is an option to manipulate the org.apache.cxf.jaxrs.provider.json.JSONProvider to drop/ignore with e.g. org.apache.cxf.jaxrs.provider.AbstractJAXBProvider#setOutDropElements and other options.
There are some examples of manipulating the standard jackson provider in the CXF project in org.apache.cxf.jaxrs.provider.json.JSONProviderTest.

Next Step

Client Configuration

Full Source Code

For easy reference the complete sources of the two Java classes are provided below.

GoGreenClient.java

package org.example.rest.client;

import javax.ws.rs.core.MediaType;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import net.sf.ehcache.config.CacheConfiguration;

import org.apache.cxf.jaxrs.client.WebClient;
import org.apache.cxf.transport.http.HTTPConduit;
import org.example.jaxb.Products;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class GoGreenClient {

    private static final Logger log = LoggerFactory.getLogger(GoGreenClient.class);

    private static final String GO_GREEN_CACHE_NAME = "GoGreenCache";
    private static final String KEY_TOP_TEN_PRODUCTS = "topTenProducts";

    private String baseAddress = "https://www.demo.onehippo.com/";
    private String path = "restapi/topproducts";
    private long connectionTimeout = 5000L;
    private long receiveTimeout = 5000L;

    // create a singleton CacheManager using defaults
    private CacheManager manager = CacheManager.create();

    public void init() {
        log.info("(Re-)Initializing GoGreenClient");
        // (re)create cache
        manager.removeCache(GO_GREEN_CACHE_NAME);
        Cache testCache = new Cache(new CacheConfiguration(GO_GREEN_CACHE_NAME, 100));
        manager.addCache(testCache);
    }

    public Products getTopTenProducts() {
        final Cache testCache = manager.getCache(GO_GREEN_CACHE_NAME);
        // try to retrieve element from cache
        final Element element = testCache.get(KEY_TOP_TEN_PRODUCTS);
        if (element != null) {
            return (Products) element.getObjectValue();
        } else {
            // if cache element does not exist retrieve object and place in cache
            final WebClient client = WebClient.create(baseAddress).path(path).accept(MediaType.APPLICATION_XML)
                    .query("_type", "xml")
                    .query("sortby", "hippogogreen:rating").query("max", 10);
            setTimeouts(client, this.connectionTimeout, this.receiveTimeout);
            final Products products = client.get(Products.class);
            testCache.put(new Element(KEY_TOP_TEN_PRODUCTS, products));
            return products;
        }
    }

    public String getBaseAddress() {
        return baseAddress;
    }

    public void setBaseAddress(String baseAddress) {
        this.baseAddress = baseAddress;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public long getConnectionTimeout() {
        return connectionTimeout;
    }

    public void setConnectionTimeout(long connectionTimeout) {
        this.connectionTimeout = connectionTimeout;
    }

    public long getReceiveTimeout() {
        return receiveTimeout;
    }

    public void setReceiveTimeout(long receiveTimeout) {
        this.receiveTimeout = receiveTimeout;
    }

    private void setTimeouts(final WebClient client, final long connectionTimeout, final long receiveTimeout) {
        HTTPConduit conduit = WebClient.getConfig(client).getHttpConduit();
        if (receiveTimeout != 0) {
            conduit.getClient().setReceiveTimeout(receiveTimeout);
        }
        if (connectionTimeout != 0) {
            conduit.getClient().setConnectionTimeout(connectionTimeout);
        }
    }
}

ClientTest.java

package org.example.rest.client;

import javax.ws.rs.core.MediaType;
import junit.framework.Assert;
import org.apache.commons.io.IOUtils;
import org.apache.wink.client.MockHttpServer;
import org.example.jaxb.Products;
import org.junit.Test;

public class ClientTest {

    @Test
    public void testCXFClient() throws Exception {
        GoGreenClient client = new GoGreenClient();
        client.init();
        final Products topTenProducts = client.getTopTenProducts();
        Assert.assertNotNull(topTenProducts);
        Assert.assertTrue(topTenProducts.getProduct().size() == 10);
    }    

    @Test
    public void testEmbeddedClient() throws Exception {
        // set up mock server
        MockHttpServer mockServer = new MockHttpServer(3333);
        mockServer.startServer();
        String baseAddress = "http://localhost:" + mockServer.getServerPort() + "/";
        String path = "restapi/topproducts";
        
        // set up mock response
        MockHttpServer.MockHttpServerResponse response = new MockHttpServer.MockHttpServerResponse();
        response.setMockResponseContent(IOUtils.toString(getClass().getResourceAsStream("/model.xml"), "UTF-8"));
        response.setMockResponseContentType(MediaType.APPLICATION_XML);
        response.setMockResponseCode(200);
        mockServer.setMockHttpServerResponses(response);
        
        // set up client
        GoGreenClient client = new GoGreenClient();
        client.init();
        client.setBaseAddress(baseAddress);
        client.setPath(path);
        
        // run client
        final Products topTenProducts = client.getTopTenProducts();
        Assert.assertNotNull(topTenProducts);
        Assert.assertTrue(topTenProducts.getProduct().size() == 10);
        
        // stop mock server
        mockServer.stopServer();
    }
}
Did you find this page helpful?
How could this documentation serve you better?
On this page
    Did you find this page helpful?
    How could this documentation serve you better?