7/20/2015

Ehcache Tutorial: Dynamically configuration and Cache Abstraction(Since Spring 3.1)

There are basically two ways to config Ehcache in java project.

1. Dynamically configuration
2. Through XML

Before getting started, we need to add the dependency of Ehcache. For example, if use maven, add below dependency into pom.xml:

<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>2.10.0</version>
</dependency>

1. Dynamically configuration

More flexible than XML configuration. But instead, you need to write and manage more code.

Cache Initialization:

public abstract class CommonDao{

    @Value("${cache.enable:true}")
    protected boolean cacheEnable;

    @Value("${cache.timetolive:21600}")
    private int cacheTimeToLive;
    
    @Value("${cache.timetoidle:21600}")
    private int cacheTimeToIdle;
    
    protected CacheManager cacheManager;
    
    protected static final String userCacheName = "userCache";
    protected static final String orgCacheName = "orgCache";

    @PostConstruct
    void initCache(){
        Configuration config = new Configuration();
        CacheConfiguration defaultCacheConfiguration = new CacheConfiguration();
        defaultCacheConfiguration.setEternal(false);
        defaultCacheConfiguration.setTimeToIdleSeconds(cacheTimeToIdle);
        defaultCacheConfiguration.setTimeToLiveSeconds(cacheTimeToLive);
        config.addDefaultCache(defaultCacheConfiguration);
        cacheManager = CacheManager.create(config);
        cacheManager.addCacheIfAbsent(userCacheName);
        cacheManager.addCacheIfAbsent(orgCacheName);
    }
    
    protected void putCacheElement(String cacheName, String key, Object value){
        
        Cache cache = null;
        if(BooleanUtils.isTrue(cacheEnable)){
            cache = cacheManager.getCache(cacheName);
            if(cache != null){
                cache.put(new Element(key, value));
            }
        }
    }
    
    protected void putCacheElement(Cache cache, String key, Object value){
        
        if(BooleanUtils.isTrue(cacheEnable)){
            if(cache != null){
                cache.put(new Element(key, value));
            }
        }
    }
    
    protected Object getCacheElement(String cacheName, String key){
        
        Cache cache = null;
        if(BooleanUtils.isTrue(cacheEnable)){
            cache = cacheManager.getCache(cacheName);
            if(cache != null){
                Element element = cache.get(key);
                return element == null ? null : element.getObjectValue();
            }
        }
        return null;
    }
    
    protected Object getCacheElement(Cache cache, String key){
        
        if(BooleanUtils.isTrue(cacheEnable)){
            if(cache != null){
                Element element = cache.get(key);
                return element == null ? null : element.getObjectValue();
            }
        }
        return null;
    }
    
    protected void removeCacheElement(String cacheName, String key){
        
        Cache cache = null;
        if(BooleanUtils.isTrue(cacheEnable)){
            cache = cacheManager.getCache(cacheName);
            if(cache != null){
                cache.remove(key);
            }
        }
    }
    
    protected void removeCacheElement(Cache cache, String key){
        
        if(BooleanUtils.isTrue(cacheEnable)){
            if(cache != null){
                cache.remove(key);
            }
        }
    }
    
    protected CacheManager getCacheManager(){
        return cacheManager;
    }
}

Dynamically update:

If in the system, you have the requirement to run time update the caching configuration:

Cache cache = dao.getCacheManager().getCache("userCache"); 
CacheConfiguration config = cache.getCacheConfiguration(); 
config.setTimeToIdleSeconds(60); 
config.setTimeToLiveSeconds(120); 
config.setmaxEntriesLocalHeap(10000); 
config.setmaxEntriesLocalDisk(1000000);

Reference:
http://ehcache.org/generated/2.10.0/html/ehc-all/index.html#page/Ehcache_Documentation_Set/co-cfgbasics_dynamically_changing_cache_config.html#wwconnect_header

2. XML Configuration

a.  Config the ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true" monitoring="autodetect">
    <defaultCache
        eternal="false"
        maxElementsInMemory="1000"
        overflowToDisk="false"
        diskPersistent="false"
        timeToIdleSeconds="3600"
        timeToLiveSeconds="3600"
        memoryStoreEvictionPolicy="LRU" />
    <cache
        name="userCache"
        eternal="false"
        maxElementsInMemory="200"
        overflowToDisk="false"
        diskPersistent="false"
        timeToIdleSeconds="3600"
        timeToLiveSeconds="3600"
        memoryStoreEvictionPolicy="LRU" />
    <cache
        name="orgCache"
        eternal="false"
        maxElementsInMemory="200"
        overflowToDisk="false"
        diskPersistent="false"
        timeToIdleSeconds="3600"
        timeToLiveSeconds="3600"
        memoryStoreEvictionPolicy="LRU" />
</ehcache>

In above example, we configured two cache named "userCache" and "orgCache".
The file can be put in classpath or any location.


Tip:

If the system is developed using Spring, there is another way to configure the cache, which is more preferable. Example below.


b. Create spring context file for Ehcache and added it into spring context.

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

    <cache:annotation-driven cache-manager="cacheManager" />

    <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cache-manager-ref="ehcache" />

    <bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:config-location="classpath:ehcache.xml" />
        
    <bean id="realCacheManager" factory-bean="cacheManager" factory-method="getCacheManager" />
    
    <!-- If cache is not configured in ehcache.xml, can also be created here -->
    <bean id="userCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
        <property name="cacheManager" ref="realCacheManager" />
        <property name="eternal" value="false" />
        <property name="maxElementsInMemory" value="${user_cache_max_elements_in_memory}" />
        <property name="overflowToDisk" value="false" />
        <property name="diskPersistent" value="false" />
        <property name="timeToIdle" value="${user_cache_time_to_idle}" />
        <property name="timeToLive" value="${user_cache_time_to_live}" />
        <property name="memoryStoreEvictionPolicy" value="LRU" />
    </bean>

    <bean id="orgCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
        <property name="cacheManager" ref="realCacheManager" />
        <property name="eternal" value="false" />
        <property name="maxElementsInMemory" value="${org_cache_max_elements_in_memory}" />
        <property name="overflowToDisk" value="false" />
        <property name="diskPersistent" value="false" />
        <property name="timeToIdle" value="${org_cache_time_to_idle}" />
        <property name="timeToLive" value="${org_cache_time_to_live}" />
        <property name="memoryStoreEvictionPolicy" value="LRU" />
    </bean>
</beans>

Reference:
http://ehcache.org/generated/2.10.0/html/ehc-all/#page/Ehcache_Documentation_Set%2Fco-cfgbasics_xml_configuration.html%23

c. Cache Abstraction (Since Spring 3.1)

Since 3.1, Spring added support for transparently adding caching into an existing Spring application. 
Similar to other feature in Spring, it supports annotation-based and xml-based configuration. Here we just include example for annotation-based configuration.

@Repository
public interface UserDao extends CrudRepository<User, String> {

    @Cacheable(value="userCache", key="#p0", unless="#result == null")
    Channel findByUserName(String userName);
}
@Repository
public interface OrganizationDao extends CrudRepository<Organization, String> {

    @Cacheable(value="orgCache", key="#p0", unless="#result == null")
    Organization findByName(String name);

    @CacheEvict(value="orgCache", key="#p0")
    Long deleteByName(String name);
}
Tips:

1. The key in above example is using p0 to represent the first param of the method. This is in case:

"Name of any of the method argument. If for some reason the names are not available (ex: no debug information), the argument names are also available under the a<#arg> where #arg stands for the argument index (starting from 0)."

Details on how to customize the configuration will not be included here in this tutorial, because you could almost find everything in the official document. Just do a little bit research based on your own requirement.

Spring expression language can also be used in the configuration.

Reference:


Good luck!


4/24/2015

Runtime Setting of SSL Params when invoking web services(apache cxf client)

By default, when we config the SSL Key for WS call, we use below system parameters:

System.setProperty("javax.net.ssl.keyStoreType", "pkcs12");
System.setProperty("javax.net.ssl.keyStore", "keystore.p12");
System.setProperty("javax.net.ssl.keyStorePassword", "1234");
System.setProperty("javax.net.ssl.trustStore", "cacerts");

But it is not flexible enough if the application has gateway structure and needs different SSL configuration for each different channels. So we need a workaround.

Below example is based on the client stub that generated with Apache CXF.(Recommanded)
Personally I prefer to use Apache CXF to generate client stub instead of Apache Axis. It is more flexible and more user friendly.

Maven Config to generate client stub:

<plugin>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-codegen-plugin</artifactId>
    <version>3.0.4</version>
    <executions>
        <execution>
            <id>generate-sources</id>
            <phase>generate-sources</phase>
            <configuration>
                <sourceRoot>${project.basedir}/src/main/java</sourceRoot>
                <wsdlOptions>
                    <wsdlOption>
                        <wsdl>${project.basedir}/src/main/resources/schemas/sample-ws.wsdl</wsdl>
                        <bindingFiles>
                            <bindingFile>${project.basedir}/src/main/resources/schemas/bindings/binding.xjb</bindingFile>
                        </bindingFiles>
                        <extraargs>
                            <extraarg>-impl</extraarg>
                            <extraarg>-client</extraarg>
                            <extraarg>-verbose</extraarg>
                            <extraarg>-p</extraarg>
                            <extraarg>com.sample.ws</extraarg>
                            <extraarg>-xjc-Xvalue-constructor</extraarg>
                        </extraargs>
                    </wsdlOption>
                </wsdlOptions>
            </configuration>
            <goals>
                <goal>wsdl2java</goal>
            </goals>
        </execution>
    </executions>
    <dependencies>
        <dependency>
            <groupId>org.jvnet.jaxb2_commons</groupId>
            <artifactId>jaxb2-value-constructor</artifactId>
            <version>3.0</version>
        </dependency>
        <dependency>
            <groupId>org.jvnet.jaxb2_commons</groupId>
            <artifactId>jaxb2-basics</artifactId>
            <version>0.6.4</version>
        </dependency>
    </dependencies>
</plugin>

After the client stub is generated, if we want to make web service call, the client code is like below:
public SampleResponse authenticate(SampleRequest request) {
    
    SampleServiceInterface port = this.getPort();
    return port.authenticate(request);
}

public SampleServiceInterface getPort() {

    SampleServiceInterfaceService ss = new SampleServiceInterfaceService();
    SampleServiceInterface port = ss.getSampleServiceAPI();
    
    return port;
}

To customize the SSL configuration, we need to write the customized SSLSocketFactory. To implement this, we can create one SSLSocketFactoryGenerator, like below:

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.KeyStore;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;

import org.apache.commons.lang.StringUtils;


public class SSLSocketFactoryGenerator {

    
    private byte[] keyStore = null;
    private byte[] trustStore = null;
    
    private String alias = null;
    private String keyStoreType = null;
    private String keyStorePassword = null;
    
    private String trustStoreType = null;
    private String trustStorePassword = null;

    public SSLSocketFactoryGenerator(ClientConfig config) {
        
        this.alias = config.getKeyAlias();
        this.keyStore = config.getKeyStoreBytes();
        this.trustStore = config.getTrustStoreBytes();
        this.keyStoreType = config.getKeyStoreType();
        this.keyStorePassword = config.getKeystorePassword();
        this.trustStoreType = config.getTrustStoreType();
        this.trustStorePassword = config.getTruststorePassword();
    }

    public SSLSocketFactory getSSLSocketFactory() throws IOException,
            GeneralSecurityException {

        KeyManager[] keyManagers = getKeyManagers();
        TrustManager[] trustManagers = getTrustManagers();

        SSLContext context = SSLContext.getInstance("SSL");
        context.init(keyManagers, trustManagers, null);

        SSLSocketFactory ssf = context.getSocketFactory();
        return ssf;
    }

    public String getAlias() {
        return alias;
    }

    public void setAlias(String alias) {
        this.alias = alias;
    }

    public byte[] getKeyStore() {
        return keyStore;
    }

    public void setKeyStore(byte[] keyStore) {
        this.keyStore = keyStore;
    }

    public byte[] getTrustStore() {
        return trustStore;
    }

    public void setTrustStore(byte[] trustStore) {
        this.trustStore = trustStore;
    }

    public String getKeyStoreType() {
        return keyStoreType;
    }

    public void setKeyStoreType(String keyStoreType) {
        this.keyStoreType = keyStoreType;
    }

    public String getKeyStorePassword() {
        return keyStorePassword;
    }

    public void setKeyStorePassword(String keyStorePassword) {
        this.keyStorePassword = keyStorePassword;
    }

    public String getTrustStoreType() {
        return trustStoreType;
    }

    public void setTrustStoreType(String trustStoreType) {
        this.trustStoreType = trustStoreType;
    }

    public String getTrustStorePassword() {
        return trustStorePassword;
    }

    public void setTrustStorePassword(String trustStorePassword) {
        this.trustStorePassword = trustStorePassword;
    }

    private KeyManager[] getKeyManagers() throws IOException,
            GeneralSecurityException {

        String alg = KeyManagerFactory.getDefaultAlgorithm();
        KeyManagerFactory kmFact = KeyManagerFactory.getInstance(alg);

        InputStream fis = new ByteArrayInputStream(getKeyStore());
        
        KeyStore ks = KeyStore.getInstance(getKeyStoreType());
        
        ks.load(fis, getKeyStorePassword().toCharArray());
        fis.close();

        kmFact.init(ks, StringUtils.isEmpty(getKeyStorePassword())?null:getKeyStorePassword().toCharArray());

        KeyManager[] kms = kmFact.getKeyManagers();
        return kms;
    }

    protected TrustManager[] getTrustManagers() throws IOException,
            GeneralSecurityException {

        String alg = TrustManagerFactory.getDefaultAlgorithm();
        TrustManagerFactory tmFact = TrustManagerFactory.getInstance(alg);

        InputStream fis = new ByteArrayInputStream(getTrustStore());
        KeyStore ks = KeyStore.getInstance(getTrustStoreType());
        ks.load(fis, StringUtils.isEmpty(getTrustStorePassword())?null:getTrustStorePassword().toCharArray());
        fis.close();

        tmFact.init(ks);

        TrustManager[] tms = tmFact.getTrustManagers();
        return tms;
    }
}

This is a quite basic sample, for different scenarios, it should be quite easy to make complementary or revision. In above sample, we can create customized SSLSocketFactory by passing in different configuration data(ClientConfig.java).

In this case, we also  need to revise the previous getPort function to use this customized SSLSocketFactory.
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Properties;

import javax.annotation.PostConstruct;
import javax.naming.ConfigurationException;
import javax.xml.ws.BindingProvider;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

public class SampleServiceImpl implements SampleService {

    private static final Logger logger = Logger.getLogger(SampleServiceImpl.class);

    private static final String URI = "/auth";

    protected ClientConfig clientConfig;

    public SampleServiceImpl() throws ConfigurationException, IOException {
        super();
    }

    void loadDefaultIfEmptyClientConfig() throws IOException, ConfigurationException {
        if (this.getClientConfig() != null)
            return;

        Properties properties = new Properties();
        properties.load(this.getClass().getClassLoader().getResourceAsStream("config/sample.properties"));
        setClientConfig(new ClientConfig(properties));
    }

    public SampleServiceImpl(Properties properties) throws ConfigurationException, IOException {
        super();
        this.setClientConfig(new ClientConfig(properties));
    }
    
    public SampleServiceImpl(ClientConfig clientConfig) throws ConfigurationException {
        super();
        this.setClientConfig(clientConfig);
    }

    public SampleServiceInterface getPort(String relUrl) {

        SampleServiceInterfaceService ss = new SampleServiceInterfaceService();
        SampleServiceInterface port = ss.getSampleServiceAPI();
        
        BindingProvider bindingProvider = (BindingProvider) port; 
        try {
            bindingProvider.getRequestContext().put("com.sun.xml.internal.ws.transport.https.client.SSLSocketFactory", 
                    new SSLSocketFactoryGenerator(clientConfig).getSSLSocketFactory());
            
            if(StringUtils.isNotEmpty(relUrl)){
                bindingProvider.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, String.format("%s%s", this.clientConfig.getServiceUrl(), relUrl));
            }
        } catch (IOException e) {
            logger.error("Error happened when initializing SSLFactory.", e);
            throw new IllegalArgumentException(e);
        } catch (GeneralSecurityException e) {
            logger.error("Error happened when initializing SSLFactory.", e);
            throw new IllegalArgumentException(e);
        } 
        return port;
    }
    
    public ClientConfig getClientConfig() {
        return clientConfig;
    }

    public void setClientConfig(ClientConfig clientConfig) throws ConfigurationException {

        this.clientConfig = clientConfig;
    }
    
    @Override
    public SampleResponse authenticate(SampleRequest request) {
    
        SampleServiceInterface port = this.getPort();
        return port.authenticate(request);
    }

}
In this case, for each different channels that need different SSL configuration. We can create different service.




9/26/2014

Spring RestTemplate does not capture response body when error code 40x is returned

When consuming REST web services using SpringTemplate default configuration, when the response returns error HTTP status code, SpringTemplate will throw HttpClientErrorException . But you can still get the response body by calling:

clientEx.getResponseBodyAsByteArray();

OR

clientEx.getResponseBodyAsString();

But there is one special case, when error code is 40x, it fails to capture the response body.

Reason is described in ticket SPR-9999

While solution is quite simple as some one mentioned in the ticket. Use the "org.springframework.http.client.HttpComponentsClientHttpRequestFactory" instead of the default one.

So the configuration should be:

<bean id="clientHttpRequestFactory" class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory"/>
    
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
     <property name="requestFactory" ref="clientHttpRequestFactory"/>
</bean>




9/10/2014

Spring Test: Inject Mock Object into proxy-based Spring bean.(Like bean with @Transactional or @Aync ...)

Spring Test, which I have to say, is the perfect lib for testing projects build on Spring.

There is one very useful class called "ReflectionTestUtils". With that, we dont need to put any setter or getter  for autowired fields inside our services and we can partially replace one autowired field/service to our defined mock object. Very convenient and flexible.

Example:

FooDao mockFooDao = Mockito.mock(FooDao.class);
Mockito.when(mockFooDao.foo()).thenReturn("Var");
ReflectionTestUtils.setField(fooService, "fooDao", mockFooDao);

There is one thing we need to pay attention, is, component bean may be wrapped by proxy class when we use annotations from spring like @Transactional or @Aync. In this case, we need to unwrap the bean before we inject the mock object, otherwise, the "ReflectionTestUtils" cannot inject the mock object, because the "fooService" is proxy object, does not have the "fooDao" field inside it!

So we need to unwrap the bean like this:

protected Object unwrapService(Object service) throws Exception {
   if(AopUtils.isAopProxy(service) && service instanceof Advised) {
 Object target = ((Advised) service).getTargetSource().getTarget();
 return target;
   }
   return null;
}
And:
ReflectionTestUtils.setField(unwrapService(fooService), "fooDao", mockFooDao);

In this way, it works perfect.

Enjoy.



8/15/2014

MongoDB escape special characters in a collection

Regarding how to escape dots in map key, please refer to below page:

MongoDB-Escape dots '.' in map key


If we have a collection named "test-core". then we do the operation using script directly, like:
db.test-core.ensureIndex( { userid: 1 } )
The server will throw exception: ReferenceError: core is not defined

This is because the character '-' inside the collection name is not correctly read.

The way to resolve is : 
db["test-core"].ensureIndex( { userid: 1 } )

6/10/2014

Unit Test for Web Services Client who use Spring Template

Recently was doing one client project which will call the web services provided by our core platform. That project is using SpringTemplate to call the Web services. How to write Junit tests for the client code brought up my interest.

After doing some research, the most commonly used method is to use Mock, of course. Some uses Mockito or EasyMock, which are all fine to me. But what I thought was we are using Spring framework anyway, there should be some Mock classes specially for SpringTemplate, and it should already be there. Come on, it's SPRING! In this case, we do not need to write Mock classed for SpringTemplate ourselves. There it is, as I thought, there are plenty of Mock classes sitting there for easy usage.

I made several test cases, although setup the dependencies costs me some time, but that is because of our project's limitation, which is not...you know. All the test cases are easy to implement, and covers well. I'm pretty satisfied. So just gives a record here, because I think it's interesting and the problems that I have encounted may have been faced by other devs too.

Dependencies:

The lib that I use is

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test-mvc</artifactId>
    <version>1.0.0.M2</version>
    <scope>test</scope>
</dependency>

Actually this library has already been integrated into spring-test in Spring version 3.2.X, but our projects are all using Spring 3.1.3.RELEASE. So I have to add it saparately. And be cautious on the dependency conflict(This will really cause trouble...when there are multiple versions of lib exist or same name of class exist but with different content and if the wrong lib/class is loaded, the class not found exception/method not found exception will be thrown, you dont want to see this...).

There are several ways to check the dependencies.

1. Use mvn dependency:tree/mvn dependency:analyze. It will print the structure of the dependencies.
2. You can check on the website: http://mvnrepository.com/artifact/junit/junit/4.10. It will normally tells you which dependencies the jar depends on.
3. You could do Ctrl+shift+T in Eclipse to check how many classes with same name exist in your work space.

The best practice is to make sure there is no dependency conflict. You will never know when it will gives you that exception.

Junit 4.10 and Mockito-all:1.9.5 CANNOT be used, because they all include hamcrest-core:1.1 inside its package which will not work with spring-test-mvc:1.0.0.M2 and there is no way to exclude them. So use Junit 4.11 and Mockito-core:1.9.5 instead.

But, Junit 4.11 and Mockito-core:1.9.5 both have dependency org.hamcrest:hamcrest-core, but they require different version of them. So they are not compatible, and we need to exclude hamcrest-core in both of them and use org.hamcrest:hamcrest-all:1.3

So the dependencies that I use is like:

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11/version>
    <exclusions>
        <exclusion>
            <artifactId>hamcrest-core</artifactId>
            <groupId>org.hamcrest</groupId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>1.9.5</version>
    <exclusions>
        <exclusion>
            <artifactId>hamcrest-core</artifactId>
            <groupId>org.hamcrest</groupId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest-all</artifactId>
    <version>1.3</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test-mvc</artifactId>
    <version>1.0.0.M2</version>
    <scope>test</scope>
</dependency>

So if you are using different versions of the dependencies, check before you put it!

Ok, we can go to the main part.

Code to be tested: 

The code that I was testing is:

    @SuppressWarnings("rawtypes")
    @Override
    public long count(UserRef ref) throws IntegrationException {
        try {
            UserMessageCountCriteria criteria = new UserMessageCountCriteria();
            criteria.setIncludeAgentMessage(true);
            criteria.setOrgName(ref.getLoginOrganizationName());
            criteria.setReadStatus(MessageReadStatusType.unRead);
            criteria.setLabels(new ArrayList<String>());
            criteria.getLabels().add(serviceInfo.getMessageCountLabel());
            criteria.setUserName(ref.getUser().getUserName());
            
            ResponseEntity<Map> responseEntity = null;
            HttpEntity<Map<String, Object>> entity = new HttpEntity<Map<String, Object>>(this.getUserAuthRequestMap(criteria, ref), this.getHttpHeaders());
            responseEntity = restTemplate.exchange(serviceInfo.getServiceMessageCountUrl(), HttpMethod.POST, entity, Map.class);
            
            return (Long) getResponseBody(responseEntity, Long.class);
        } catch (Exception ex) {
            ServiceExceptionHandler.handle(ex, this.getClass().getSimpleName());
        }
        
        return 0L;
    }

protected Object getResponseBody(ResponseEntity<Map> responseEntity, Class clazz) throws IntegrationException {

        if (LOGGER.isDebugEnabled()) {
            try {
                LOGGER.debug(JsonCodec.marshal(responseEntity));
            } catch (Exception ex) {
                throw new IllegalArgumentException(ex);
            }
        }

        if (responseEntity == null) {
            throw new RuntimeException("Response is empty");
        }

        HttpHeaders headers = responseEntity.getHeaders();
        if (headers == null) {
            throw new RuntimeException("Response Header is empty");
        }

        Map<String, Object> responseBody = responseEntity.getBody();
        Map<String, String> status = (Map<String, String>) responseBody.get("status");
        String statusCode = status.get("code");
        String statusMsg = status.get("message");
        if (!StringUtils.equals(statusCode, serviceInfo.getAPISuccessCode())) {
            throw new IntegrationException(statusCode, statusMsg);
        }

        Object object = responseBody.get("content");
        if (clazz == null) {
            return object;
        }
        
        if(object == null)
            return null;

        try {
            String json = JsonCodec.marshal(object);
            if (StringUtils.isEmpty(json)) {
                return null;
            }
            return JsonCodec.unmarshal(clazz, json);
        } catch (Exception ex) {
            throw new IllegalArgumentException(ex);
        }
    }

It's pretty basic.

Test Case:

First I create one Base class to setup my Mock server, this class can be extended by all the other service test class.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:spring/test-portalintegration-servlet.xml" })
public class ServiceClientImplTestBase {

    private static final Logger logger = Logger.getLogger(ServiceClientImplTestBase.class);
    
    protected MockRestServiceServer mockServer;
    
    @Autowired
    private ApplicationContext appContext;
    
    @Autowired
    protected MessageServiceClient messageServiceClient;
    
    @Autowired
    protected ServiceInfo serviceInfo;
    
    @Before
    public void setUp() {
        mockServer = MockRestServiceServer.createServer((RestTemplate) appContext.getBean("restTemplate")); // (1)
        
    }
    
    protected String loadFile(String path){
        
        byte[] content;
        try {
            content = IOUtils.toByteArray(this.getClass().getClassLoader().getResourceAsStream(path));
        } catch (IOException e) {
            logger.error(String.format("failed to load file from classpath: %s", path), e);
            return null;
        }
        
        return new String(content);
    }
}

Then I create one class to test one of the service client.

public class MessageServiceClientImplTest extends ServiceClientImplTestBase{
    @Test
    public void countTest() throws Exception {
        mockServer.expect(RequestMatchers.requestTo(serviceInfo.getServiceMessageCountUrl()))
        .andExpect(RequestMatchers.method(HttpMethod.POST))
        .andRespond(ResponseCreators.withSuccess(loadFile("testdata/sample-response/message-count-success-response.json"), MediaType.APPLICATION_JSON)); // (2)

        UserRef userRef = this.getUserRef("dummy"); //this method just helps me create one dto. Nothing special.
        
        long result = messageServiceClient.count(userRef);
        
        Assert.assertEquals(1, result); // (3)
        mockServer.verify(); // (4)
    }
}

Then you can run the countTest() and it works perfect.

This example is just a very basic Mock, showing you how it works. Depends on your service and client requirement, you need to customize the expect rule to verify the functionality of your client impl.

This is really easy. I like it.

End.