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

Reply via email to