Using Spring RestTemplate to consume restful webservice

Introduction

I recently have to write a client to consume a restful webservice using Spring RestTemplate class. The task turns out to be non trivial as there are a few gotchas needed to be handled because of the way the webservice is implemented. I managed to put together a solution after searching through some useful articles online. I summarise what I have done in this article.

Problem 1: SSL

The webservice I am consuming is implemented in another web application but has to be accessed via https. As a result, calling the getObject() method in RestTemplate with the url will return the following exception:

org.springframework.web.client.ResourceAccessException: I/O error: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target; nested exception is javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:453)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:401)
at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:199)
… 32 more

I decide to bypass the certificate validation by adding the following codes:

    public static void trustSelfSignedSSL()
{
try
{
final SSLContext ctx = SSLContext.getInstance(“TLS”);
final X509TrustManager tm = new X509TrustManager()
{

public void checkClientTrusted(final X509Certificate[] xcs, final String string) throws CertificateException
{
// do nothing
}

public void checkServerTrusted(final X509Certificate[] xcs, final String string) throws CertificateException
{
// do nothing
}

public X509Certificate[] getAcceptedIssuers()
{
return null;
}
};
ctx.init(null, new TrustManager[]
{ tm }, null);
SSLContext.setDefault(ctx);
}
catch (final Exception ex)
{
ex.printStackTrace();
}
}

Note the method is static and needs to be called once before calling the webservice. Now calling RestTemplate.getObject will return something about hostname wrong:

org.springframework.web.client.ResourceAccessException: I/O error: HTTPS hostname wrong:  should be <some url>; nested exception is java.io.IOException: HTTPS hostname wrong:  should be <some url>
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:453)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:401)
at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:199)

… 32 more

The above error can be removed by providing a custom http client factory by extending Spring class SimpleClientHttpRequestFactory:

public class MySimpleClientHttpRequestFactory extends SimpleClientHttpRequestFactory
{

private final HostnameVerifier verifier;

public MySimpleClientHttpRequestFactory(final HostnameVerifier verifier)
{
this.verifier = verifier;
}

@Override
protected void prepareConnection(final HttpURLConnection connection, final String httpMethod) throws IOException
{
if (connection instanceof HttpsURLConnection)
{
((HttpsURLConnection) connection).setHostnameVerifier(verifier);
}
super.prepareConnection(connection, httpMethod);
}
}

Note we also need to inject a dummy hostname verifier for the above class to bypass the hostname verification:

public class NullHostnameVerifier implements HostnameVerifier
{
@Override
public boolean verify(final String hostname, final SSLSession session)
{
return true;
}

}

All then left to do is the inject the client http request factory to the resttemplate:

RestTemplate template = new RestTemplate();

final MySimpleClientHttpRequestFactory factory = new MySimpleClientHttpRequestFactory(verifier);
template.setRequestFactory(factory);

Problem 2 : List Generic / Type Erasure

My second problem with accessing the webservice is that it returns an array of JSON object in the form

[{“no”:”00028″,”l”:”201″,”t”:0,”i”:10,”a”:20},…,{“no”:”00028″,”l”:”201″,”t”:5,”i”:15,”a”:25}]

Because of Java type erasure, calling RestTemplate.getObject(url, List.class) will return a list of LinkedHashMap objects. To marshall the returned json objects into domain objects, the default json message converter MappingJacksonHttpMessageConverter has to be extended as follows:

public class MyJsonMessageConverter extends MappingJacksonHttpMessageConverter
{

@Override
protected JavaType getJavaType(final Class<?> clazz)
{
if (List.class.isAssignableFrom(clazz))
{
final TypeFactory typeFactory = getObjectMapper().getTypeFactory();
return typeFactory.constructCollectionType(ArrayList.class, StockInventory.class);
}
else
{
return super.getJavaType(clazz);
}
}

Note the use of TypeFactory.constructCollectionType method to map list type into my domain object type StockInventory.

Problem 3: Custom deserialiser

The last problem I need to solve is to implement a custom deserialiser in my message converter to handle the coded json object. I do this by extending the getObjectMapper method:

 @Override
public void setObjectMapper(final ObjectMapper objectMapper)
{
// add custom json serialiser for ItemInventory response
final SimpleModule module = new SimpleModule(“StockInventoryModule”, new Version(1, 1, 1, null));
module.addDeserializer(StockInventory.class, new JsonDeserializer<StockInventory>()
{

@Override
public StockInventory deserialize(final JsonParser jp, final DeserializationContext cxt) throws IOException,
JsonProcessingException
{
final StockInventory stockInventory = new StockInventory();
//    final DeserializerProvider provider = cxt.getDeserializerProvider();
while (jp.nextToken() != JsonToken.END_OBJECT)
{
final String fieldName = jp.getCurrentName();
jp.nextToken();
if (“no”.equals(fieldName))
{
stockInventory.setNo(jp.getText());
}
else if (“l”.equals(fieldName))
{
stockInventory.setLocation(jp.getText());
}
… // handle other fields
else
{
throw new IllegalStateException(“unrecognized field [” + fieldName + “]”);
}
}
return stockInventory;
}
});
objectMapper.registerModule(module);
super.setObjectMapper(objectMapper);
}

Now the only thing left to do is to use the customer converter in the RestTemplate:

  final List<HttpMessageConverter<?>> list = new ArrayList<HttpMessageConverter<?>>();
final MappingJacksonHttpMessageConverter converter = new MyJsonMessageConverter();
converter.setObjectMapper(new ObjectMapper());
list.add(converter);
template.setMessageConverters(list);
List<StockInventory> stocks = template.getForObject(url, List.class);

That’s it!

Resources

Thanks for the contributors of the following links where I got the codes from:

  1. SSL – http://stackoverflow.com/questions/1725863/ssl-handshake-issue-using-spring-resttemplate
  2. Customer client HTTP request factory codes from http://www.jroller.com/jurberg/entry/using_a_hostnameverifier_with_spring
  3. JSON list – http://stackoverflow.com/questions/7040279/jackson-json-java-generics-get-linkedhashmap
  4. JSON list – http://stackoverflow.com/questions/6173182/spring-json-convert-a-typed-collection-like-listmypojo
Advertisements