Hello,
Problem Statement: Unable to implement custom client side exception handler
using Client (javax.ws.rs.client.Client) API, and @Provider class implementing
the ResponseExceptionMapper interface.
Questions: 1. Does Client API not support custom client side providers for
exception handling? 2. Any literature I looked up for this problem statement
uses JAXRSClientFactory implementation; I'm yet to find any using Client API
for this scenario. Would I have to switch my implementation? 3. What is the
difference between Client API and JAXRSClientFactory implementations?
I am working on a cxf Client API implementation in Java, and noticed that for
most http status codes above 300 cxf wraps the Response in either a
WebApplicationException or ProcessingException (depending upon the response
status code). The server in my case has a customized response body indicating
the actual reason for an http status code !200, like below (for response code =
412):
{
"requestError": {
"serviceException": {
"messageId": "SVC4120",
"text": "Invalid Request: Invalid Coupon Code."
}
}
}
Unfortunately the WebApplicationException itself does not render this. Instead
the only message captured in the exception directly is a generic "412
Precondition Failed". I can do something similar to below exception block from
code snippet (includes Client API code snippet):
protected RESPOBJ invoke(String endPointUrl) throws CustomException {
Object reqPOJO = prepareRequest();
try {
if(client == null) {
ClientBuilder builder = ClientBuilder.newBuilder();
//register custom JAX-RS components
builder.register(new CustomMapper());
}
WebTarget target = client.target(endPointUrl);
//do this when queryParams exist
if(!getUriParams().isEmpty()) {
for(Map.Entry<String, String> queryParams :
getUriParams().entrySet()) {
target =
target.queryParam(queryParams.getKey(), queryParams.getValue());
}
}
Invocation.Builder builder = target.request();
//create headers here
MultivaluedMap<String, Object> headers = new
MultivaluedHashMap<>();
if(isBasicAuthRequired()) {
headers.add(AUTH_HEADER_PARAM,
getBasicAuthentication());
}
headers.add(CONTENT_TYPE, getMediaType().toString());
builder.headers(headers);
builder.accept(getMediaType().toString());
//GET or POST
if(HttpMethodType.GET.equals(getHttpMethod())) {
return builder.get(RESPOBJ.class);
}
return builder.post(Entity.entity(reqPOJO, getMediaType()),
RESPOBJ.class);
}
catch (Exception ex) {
if(ex instanceof ResponseProcessingException) {
ResponseProcessingException e =
(ResponseProcessingException) ex;
logger.error("Unmarshalling failed: [" +
e.getResponse().readEntity(String.class) + "]");
}
else if(ex instanceof WebApplicationException) {
WebApplicationException e = (WebApplicationException)
ex;
logger.error("Error Response:
["+e.getResponse().readEntity(String.class) + "]");
}
throw new CustomException(ex);
}
}
Although, I am looking to implement something cleaner, preferably using a
custom Exception handler that implements ResponseExceptionMapper<> interface.
From literature I noticed the only implementations of ResponseExceptionMapper
for custom client side exception handling are using JAXRSClientFactory. My
current implementation however uses the Client API (code snippet below). From a
design aspect I will modify this to have a separate CustomExceptionMapper class
that would be the Provider only for Exception cases, but I do not see why this
Custom class is registered as a Provider (works for 200 status codes as MBR,
and the MBW works always) but does not work for exception cases.
Update: While debugging and observing changes between a 200 vs >300 status code
(412 in my case), I noticed that for 200 case
JAXRSUtils.readFromMessageBodyReader() method gets invoked, which for the 1st
time retrieves the Custom Provider. The code never gets here for status codes
shown below in code snippet which should be the reason for not finding the
CustomMapper. So is there any difference in how I must register my
CustomExceptionMapper? Or does the Client API simply not support this
functionality?
// for failure case the method above returns null (status > 300), whereas for
success 200 case it executes method in last line and gets the provider.
// AbstractClient class that invokes the doReadEntity() method which in turn
invokes and finds the Provider in JAXRSUtils.readFromMessageBodyReader() method
code
protected <T> T readBody(Response r, Message outMessage, Class<T> cls,
Type type, Annotation[] anns) {
if (cls == Response.class) {
return cls.cast(r);
}
int status = r.getStatus();
//this is invoked for failure case
if ((status < 200 || status == 204) && r.getLength() <= 0 || status >=
300) {
return null;
}
//this for 200 status code
return ((ResponseImpl)r).doReadEntity(cls, type, anns);
}
//My custom provider code
@Provider
@Consumes
@Produces(MediaType.APPLICATION_JSON)
public class CustomMapper implements MessageBodyReader<CustomResponse>,
MessageBodyWriter<CustomRequest>, ResponseExceptionMapper<CustomException> {
private Gson gson = new
GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
@Override
public boolean isReadable(Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
return
type.isAssignableFrom(CustomResponse.class);
}
@Override
public CustomResponse readFrom(Class<CustomResponse> type, Type
genericType, Annotation[] annotations,
MediaType mediaType,
MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws
IOException, WebApplicationException {
CustomResponse respObj = new CustomResponse();
//json to pojo code
return respObj;
}
@Override
public long getSize(CustomRequest reqObj, Class<?> type, Type
genericType, Annotation[] annotations, MediaType mediaType) {
return -1;
}
@Override
public boolean isWriteable(Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
return
type.isAssignableFrom(CustomRequest.class);
}
@Override
public void writeTo(CustomRequest reqObj, Class<?> type, Type
genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object>
httpHeaders, OutputStream entityStream) throws IOException,
WebApplicationException {
entityStream.write(gson.toJson(reqObj).getBytes());
}
@Override
public CustomException fromResponse(Response exceptionResponse)
{
//Response obj to my CustomException code
return (CustomException);
}
}
Question: I'm trying to figure out what is done wrong here, and if Client API
does not support custom client side exception handling for any reason? What is
the difference between Client API and JAXRSClientFactory implementations? I
also am looking into possibly using ClientResponseFilter (haven't researched
this fully yet).
Any help appreciated. Thanks.
Sanjeev