To get started we need a factory that produces WebClients.
public class ClientFactory {
private Map<String, WebClient> cache = new HashMap<>();
public enum RESTClient {
PORTAL;
}
public WebClient fetchRestClient(RESTClient restClient) {
if (this.cache.containsKey(restClient)) {
return WebClient.fromClient(this.cache.get(rc));
}
if (RESTClient.enum.equals(rc)) {
List<Object> providers = new ArrayList<Object>();
providers.add(new GsonMessageBodyProvider());
WebClient webClient = WebClient.create("https://blah.com", providers);
HTTPConduit conduit = WebClient.getConfig(webClient).getHttpConduit();
conduit.getClient().setReceiveTimeout(recieveTimeout);
conduit.getClient().setConnectionTimeout(connectionTimout);
this.cache.put(RESTClient.CAT_DEVELOPER_PORTAL.name(), webClient);
return WebClient.fromClient(webClient);// thread safe
}
}
}
Now we have a WebClient that is ready to go. Lets set up the rest of the call.
public Person fetchPerson(Long id) {
long timer t = System.currentTimeMillis();
Person person = null;
try {
wc = this.factory.findWebClient(RESTClient.PORTAL);
wc.header(AUTH_HEADER, SUBSCRIPTION_KEY);
wc.header(HttpHeaders.ACCEPT, "application/person-v1+json");
wc.path("person").path("base");
wc.query("id", String.valueOf(id));
person = wc.get(Person.class);
}
catch (WebApplicationException wae) {
// we wanna skip these. They will show up in the "finally" logs.
}
catch (Exception e) {
log.error(MessageFormat.format("Error fetching Person: id:{0} ", id), e);
}
finally {
log.info("GET HTTP:{} - Time:[{}ms] - URL:{} - Content-Type:{}", wc.getResponse().getStatus(), (System.currentTimeMillis() - timer), wc.getCurrentURI(), wc.getResponse().getMetadata().get("content-type"));
wc.close();
}
return p;
}
So, what about that "accept" header: application/person-v1+json How will CXF know how to parse it? CXF comes with some built in JSON parsers but I wanted to use Gson. Many of the other implementations of Json parsers need some kind of annotation but not Gson. It is stupid easy to use.
public class GsonMessageBodyProvider<T> implements MessageBodyReader<T>, MessageBodyWriter<T> {
private Gson gson = new GsonBuilder().create();
@Override
public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return StringUtils.endsWithIgnoreCase(mediaType.getSubtype(), "json");
}
@Override
public T readFrom(Class<T> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException, WebApplicationException {
try {
return gson.fromJson(new BufferedReader(new InputStreamReader(entityStream, "UTF-8")), type);
}
catch (Exception e) {
throw new IOException("Trouble reading into:" + type.getName(), e);
}
}
@Override
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return StringUtils.containsIgnoreCase(mediaType.getSubtype(), "json");
}
@Override
public long getSize(T t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return 0;
}
@Override
public void writeTo(T t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException {
try {
JsonWriter writer = new JsonWriter(new OutputStreamWriter(entityStream, "UTF-8"));
writer.setIndent(" ");
gson.toJson(t, type, writer);
writer.close();
}
catch (Exception e) {
throw new IOException("Trouble marshalling:" + type.getName(), e);
}
}
}
The code is straight forward. You implement two interfaces: MessageBodyReader and MessageBodyWriter (or just one) and then add it to the "providers" when creating the WebClient. CXF figures it out from there. One option is to return "true" in the "isReadable()" "isWriteable()" methods. This will assure that CXF uses this class instead of all the built in ones. The added providers will be checked first.