ResponseExceptionMapper in cxf client - java

I am trying to handle exceptions using the ResponseExceptionMapper class for my cxf client.
ExceptionHandlingCode:
public class MyServiceRestExceptionMapper implements ResponseExceptionMapper<Exception> {
private static final Logger LOGGER = LoggerFactory.getLogger(MyServiceRestExceptionMapper .class);
public MyServiceRestExceptionMapper () {
}
#Override
public Exception fromResponse(Response response) {
LOGGER.info("Executing MyServiceRestExceptionMapper class");
Response.Status status = Response.Status.fromStatusCode(response.getStatus());
LOGGER.info("Status: ", status.getStatusCode());
switch (status) {
case BAD_REQUEST:
throw new InvalidServiceRequestException(response.getHeaderString("exception"));
case UNAUTHORIZED:
throw new AuthorizationException(response.getHeaderString("exception"));
case FORBIDDEN:
throw new AuthorizationException(response.getHeaderString("exception"));
case NOT_FOUND:
throw new
EmptyResultDataAccessException(response.getHeaderString("exception"));
default:
throw new InvalidServiceRequestException(response.getHeaderString("exception"));
}
}
}
CXF Client Code:
String url1=
WebClient client = createWebClient(url1).path(/document);
client.headers(someHeaders);
Response response = client.post(byteArry);
For success scenarios, I am getting the correct response code of 200, but for failure scenarios, I never get a response code.
Also is there a better way of handling exceptions in cxf client.
Could someone please help on this.

How have you registered the ResponseExceptionMapper to the WebClient?
You need something like this
List<Object> providers = new ArrayList<Object>();
providers.add(new MyServiceRestExceptionMapper()
WebClient client = WebClient.create(url, providers);
I suggest to use a WebApplicationException insteadof Exception because default behaviour will raise this kind of exception if no ResponseExceptionMapper is registered. Also, return the exception, do not throw it. The exception mapper should looks like this.
public class MyServiceRestExceptionMapper implements ResponseExceptionMapper<WebApplicationException>
public MyServiceRestExceptionMapper () {
}
#Override
public WebApplicationException fromResponse(Response response) {
//Create your custom exception with status code
WebApplicationException ex = ...
return ex;
}
}

Related

How can I get a custom message from WebApplicationException?

I have a service A that makes post request to another controller (B). This is my service making post request. Controller B is not in the same project as service A, so B throws Bad request (400) and service A turns 400 request to WebApplicationException:
WebClient client = tokenAuth.addAuthentication(WebClient.create(url))
.type(AccelaradMediaType.SMR_IMAGE_SHARE_V3_JSON)
.accept(AccelaradMediaType.SMR_SHARE_RESULT_JSON);
String response = client.post(body, String.class);
catch (WebApplicationException e) {
//get message from exception and print
}
And this is other controller(B) that my service is making post request:
#POST
#Path("/shares")
#Consumes({AccelaradMediaType.SMR_IMAGE_SHARE_V3_JSON, AccelaradMediaType.SMR_IMAGE_SHARE_V3_XML})
#Produces({AccelaradMediaType.SMR_SHARE_RESULT_JSON, AccelaradMediaType.SMR_SHARE_RESULT_XML})
public ShareResult shareV3() {
ShareResult result = null;
try {
result = shareStudies();
}
catch (StudyShareException e) {
LOG.error(e.getMessage(), e);
throw new BadRequestException(e.getMessage());
}
return result;
}
public ShareResult shareStudies() {
try {
//some logic
}
catch (InvitationException e) {
String message = "Invitation is pending";
throw new StudyShareException(message, e);
}
}
And here are StudyShareException class and BadRequestException class:
public class StudyShareException extends Exception {
public StudyShareException(String message, Throwable cause) {
super(message, cause);
}
}
public class BadRequestException extends WebApplicationException {
private static final long serialVersionUID = 1L;
public BadRequestException(String message) {
this(message, null);
}
public BadRequestException(String message, Throwable cause) {
super(cause, Response.status(Response.Status.BAD_REQUEST).entity(message).type(MediaType.TEXT_PLAIN).build());
}
}
When service A makes a post request, it does go into the catch block and controller B prints out error in the stack trace with "Invitation is pending" message.
My goal is to print out "Invitation is pending" from service A as well. I tried e.getResponse() or e.getResponse().getEntity() or e.getMessage(), nothing has worked. Is it even possible to get custom message from service A? If so, how can I achieve this?
Why are you catching WebApplicationException in service A when service B is throwing StudyShareException? You need to catch the correct exception in service A
try {
WebClient client = tokenAuth.addAuthentication(WebClient.create(url))
.type(AccelaradMediaType.SMR_IMAGE_SHARE_V3_JSON)
.accept(AccelaradMediaType.SMR_SHARE_RESULT_JSON);
String response = client.post(body, String.class);
catch (StudyShareException e) {
//get message from exception and print
}
Now if you are trying to catch all exceptions extending WebApplicationException then you should make StudyShareException extend WebApplicationException as well.
That being said, perhaps you shouldn't catch any exception at all just let service A just bubble up the exception thrown in service B. However, that's up to you, you may want to throw a different message or a different exception in service A.

Feign Client Error Handling - Suppress the Error/Exception and convert to 200 success response

I am using feign client to connect to downstream service.
I got a requirement that when one of the downstream service endpoint returns 400 ( it's partial success scenario ) our service need this to be converted to 200 success with the response value.
I am looking for a best way of doing this.
We are using error decoder to handle the errors and the above conversion is applicable for only one endpoint not for all the downstream endpoints and noticed that decode() method should returns exception back.
You will need to create a customized Client to intercept the Response early enough to change the response status and not invoke the ErrorDecoder. The simplest approach is to create a wrapper on an existing client and create a new Response with a 200 status. Here is an example when using Feign's ApacheHttpClient:
public class ClientWrapper extends ApacheHttpClient {
private ApacheHttpClient delegate;
public ClientWrapper(ApacheHttpClient client) {
this.client = client;
}
#Override
public Response execute(Request request, Request.Options options) throws IOException {
/* execute the request on the delegate */
Response response = this.client.execute(request, options);
/* check the response code and change */
if (response.status() == 400) {
response = Response.builder(response).status(200).build();
}
return response;
}
}
This customized client can be used on any Feign client you need.
Another way of doing is by throwing custom exception at error decoder and convert this custom exception to success at spring global exception handler (using #RestControllerAdvice )
public class CustomErrorDecoder implements ErrorDecoder {
#Override
public Exception decode(String methodKey, Response response) {
if (response.status() == 400 && response.request().url().contains("/wanttocovert400to200/clientendpoints") {
ResponseData responseData;
ObjectMapper mapper = new ObjectMapper();
try {
responseData = mapper.readValue(response.body().asInputStream(), ResponseData.class);
} catch (Exception e) {
responseData = new ResponseData();
}
return new PartialSuccessException(responseData);
}
return FeignException.errorStatus(methodKey, response);
}}
And the Exception handler as below
#RestControllerAdvice
public class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.OK)
#ExceptionHandler(PartialSuccessException.class)
public ResponseData handlePartialSuccessException(
PartialSuccessException ex) {
return ex.getResponseData();
}
}
Change the microservice response:
public class CustomFeignClient extends Client.Default {
public CustomFeignClient(
final SSLSocketFactory sslContextFactory, final HostnameVerifier
hostnameVerifier) {
super(sslContextFactory, hostnameVerifier);
}
#Override
public Response execute(final Request request, final Request.Options
options) throws IOException {
Response response = super.execute(request, options);
if (HttpStatus.SC_OK != response.status()) {
response =
Response.builder()
.status(HttpStatus.SC_OK)
.body(InputStream.nullInputStream(), 0)
.headers(response.headers())
.request(response.request())
.build();
}
return response;
}
}
Add a Feign Client Config:
#Configuration
public class FeignClientConfig {
#Bean
public Client client() {
return new CustomFeignClient(null, null);
}
}

Continue after exception or Error in Rest Api call using OAuth2RestTemplate

i am making a rest call using Spring Oauth2RestTemplate. I am trying to catch any exception while trying to make a restAPI call and continue the flow of Exception.
Two ways i tried:
Way I(Using try catch).
public ResponseEntity<Object> getResponse(URI uri, HttpHeaders httpHeaders,
Object obj) {
ResponseEntity<Object> response = null;
try {
response = restTemplate.exchange(uri, HttpMethod.POST, new HttpEntity<>(obj, httpHeaders),
Object.class);
} catch (Exception serverEx) {
LOGGER.error("ERROR while calling API.Full Exception: ",serverEx);
response.getBody().setLink(object.getUrl());
}
return response;
}
Way II(Custom Handling).
public class RestTemplateResponseErrorHandler implements ResponseErrorHandler {
private static final Logger LOGGER = LogManager.getLogger(RestTemplateResponseErrorHandler.class);
#Override
public boolean hasError(ClientHttpResponse httpResponse) throws IOException {
return (httpResponse.getStatusCode().series() == Series.CLIENT_ERROR
|| httpResponse.getStatusCode().series() == Series.SERVER_ERROR);
}
#Override
public void handleError(ClientHttpResponse httpResponse) {
//Log The Error but contibue the flow
}
}
But neither way the execution gets stopped. I want to continue the flow of the execution. if the call fails i want to handle it and continue the flow. Can any one please suggest whats happening here?
Exception:
Caused by: java.io.IOException: Attempted read from closed stream.
at org.apache.http.impl.io.ContentLengthInputStream.read(ContentLengthInputStream.java:131)
at org.apache.http.conn.EofSensorInputStream.read(EofSensorInputStream.java:118)
at java.io.FilterInputStream.read(FilterInputStream.java:83)
at java.io.PushbackInputStream.read(PushbackInputStream.java:139)
at org.springframework.web.client.MessageBodyClientHttpResponseWrapper.hasEmptyMessageBody(MessageBodyClientHttpResponseWrapper.java:102)
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:82)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:932)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:916)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:663)
... 223 more
Error Response OutboundJaxrsResponse{status=500, reason=Internal Server Error, hasEntity=true, closed=false, buffered=false}
Any suggestion on this please?

Spring Boot custom receive responses with HTTP OPTIONS by throw exceptions

I am new to Spring Boot, and I am trying to test a connection using HTTP OPTIONS.
My design is that I have a Service class that contains the logics for the testing. I also have an API Controller class that implements the method from Service.
My currently understanding is that the controller can be used to respond back different HTTP statuses using exceptions.
This is the method I wrote inside the controller for this purpose:
#PostMapping(path = "/test")
public ResponseEntity<Void> testConnection(#RequestBody URL url) {
try {
ControllerService.testConnection(url);
return ResponseEntity.status(HttpStatus.NO_CONTENT).body(null);
} catch (CredentialsException e) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(null);
} catch (URLException | URISyntaxException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
} catch (UnknownException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
}
}
The way exceptions are triggered and the method testConnection() are inside the service class:
public static void testConnection(URL url)
throws URISyntaxException, CredentialsException, URLException, UnknownException {
String authHeaderValue = "Basic " + Base64.getEncoder().encodeToString(("user" + ':' + "password").getBytes());
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("Authorization", authHeaderValue);
RestTemplate rest = new RestTemplate();
final ResponseEntity<Object> optionsResponse = rest.exchange(url.toURI(), HttpMethod.OPTIONS, new HttpEntity<>(requestHeaders), Object.class);
int code = optionsResponse.getStatusCodeValue();
if (code == 403) {
throw new InvalidCredentialsException();
} else if (code == 404) {
throw new InvalidURLException();
} else if (code == 500) {
throw new UnknownErrorException();
} else if (code == 200){
String message = "Test connection successful";
LOGGER.info(message);
}
}
I have created those custom exception classes.
Is this the proper way to trigger the right HTTP response inside the controller method or does Spring Boot has some other design? If so, is my list of exceptions comprehensive enough or do I need to add more to the testConnection() method in the service class?
You can write ExceptionHandler for each of the Exception type, so you don't have to repeat the code or use try/ catch block at all. Just let your testConnection and other methods to throw the exception.
import org.springframework.web.bind.annotation.ExceptionHandler;
#ExceptionHandler(CredentialsException.class)
public void credentialsExceptionHandler(CredentialsException e, HttpServletResponse response) throws IOException {
response.sendError(HttpStatus.FORBIDDEN.value(), e.getMessage());
}
There are different ways to define and use the ExceptionHandler method. But conceptually same.

Propagate exception from CXF interceptor to exception mapper

I've a flow where on CXF client I've jaxrs-in-interceptor, provider and exception mapper. In my case I'm catching bad response from client through in-interceptor and then I would like abort the cxf bus chain and throw a fault. Unfortunately I couldn't do it, cause in every situation exception thrown from interceptor is being only logged, but the main error (wrong json format) is propagated to exception mapper. I would like to avoid Exception mapper, but I don't know how. I'm using WebClient to implement interceptors like this:
#Component
public class MyInterceptor extends AbstractPhaseInterceptor<Message> {
public MyInterceptor() {
super(POST_STREAM);
}
#Override
public void handleMessage(Message message) throws Fault {
if (message != null) {
//message.getExchange().setOneWay(true);
//message.getExchange().put(Exception.class, new MyException());
//message.getInterceptorChain().abort();
//message.setContent(Exception.class, new MyException());
//Endpoint ep = message.getExchange().get(Endpoint.class);
//message.getInterceptorChain().abort();
//if (ep.getInFaultObserver() != null) {
// ep.getInFaultObserver().onMessage(message);
//}
//throw new WebApplicationException( new MyException());
//message.setContent(Response.class, response);
throw new Fault(new MyException());
}
}
I read that I should implement jaxrs-filter cause exceptions thrown by interceptor are not propagated to exception mapper. Is it any way to do that in java thanks to WebClient implementation?
S client = create(url, clazz, list(jsonProvider(), providers));
WebClient.getConfig(client).getInInterceptors().add(new MyInterceptor());
I've also tried to use different phases on interceptor, but it also didn't work.
I have been researching and testing with your issue. The problem is that the exceptions thrown from the CXF interceptors escape the JAX-RS flow (see the answer of CXF team)
A Fault generated from interceptor can be catched implementing handleFault in the interceptor itself
public void handleFault(Message message) {
Exception e = message.getContent(Exception.class);
}
Or implementing a FaultListener and registering it at CXF Bus
WebClient.getConfig(client).getBus().getProperties().put("org.apache.cxf.logging.FaultListener",new MyFaultListener());
public class MyFaultListener implements FaultListener{
public boolean faultOccurred(final Exception exception,final String description,final Message message) {
//return false to avoid warning of default CXF logging interceptor
return false;
}
}
But you can not return custom response from interceptor or respond a Fault to client.
The workaround I have found to achieve the desired behaviour consist in replacing the Response with a custom object that could be processed by your usual method invokation, like an exceptionMapper
See CXF/ JAX-RS : Return Custom response from interceptor
Into Interceptor.handleMessage check the conditions you need and create a Response with custom status and entity. After this, stop the chain
public class MyInterceptor extends AbstractPhaseInterceptor<Message> {
public MyInterceptor() {
super(Phase.POST_STREAM);
}
#Override
public void handleMessage(Message message) throws Fault {
if (message != null) {
//check the condition to raise the error
//build the custom Response replacing service call
Response response = Response
.status(Response.Status.BAD_REQUEST)
.entity("custom error")
.build();
message.getExchange().put(Response.class, response);
//abort interceptor chain in you want to stop processing or throw a Fault (catched by handleFault)
//message.getInterceptorChain().abort();
//throw new Fault (new MyException());
}
public void handleFault(Message messageParam) {
}
}
Add the ResponseExceptionMapper as provider when creating the JAXRS client
providers.add(new ResponseExceptionMapper<WebApplicationException>() {
#Override
public WebApplicationException fromResponse(Response r) {
return new WebApplicationException(r);
}
});
YourService proxy = JAXRSClientFactory.create(url, clazz,providers);
Client client = WebClient.client(proxy);
WebClient.getConfig(client).getInInterceptors().add(new MyInterceptor());
After this, a call to proxy.yourService() will raise a WebApplicationException if acomplish the interceptor check. You can catch it or rethrow in the desired way
try{
proxy.yourService();
}catch (WebApplicationException e){
}
Hope this helps
I fully agree with previous answer. My implementation looks like:
#Component
public class ServiceFailureInterceptor extends AbstractPhaseInterceptor<Message> {
private static final Logger LOG = LoggerFactory.getLogger(ServiceFailureInterceptor.class);
public ServiceFailureInterceptor() {
super(PRE_STREAM);
}
#Override
public void handleMessage(Message message) {
if (message != null) {
int responseCode = (int) message.get(Message.RESPONSE_CODE);
LogicException logicException = ErrorMapper.HTTP_STATUS_CODE_MAPPER.get(responseCode);
InputStream is = b2stream(MapperUtils.json().toBytes(logicException));
// clear old message & exchange
Exchange exchange = message.getExchange();
for (Class<?> contentFormat : message.getContentFormats()) {
message.setContent(contentFormat, null);
}
resetOrigInterceptorChain(message);
resetFault(exchange);
message.setContent(InputStream.class, is);
Message outMessage = createOutMessage(exchange, is);
prepareMessage(outMessage);
prepareMessage(message);
}
}
private void prepareMessage(Message message) {
message.put(Message.REQUESTOR_ROLE, true);
message.put(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
}
private Message createOutMessage(Exchange exchange, InputStream logicException) {
Endpoint ep = exchange.get(Endpoint.class);
Message outMessage = ep != null ? ep.getBinding().createMessage() : new MessageImpl();
outMessage.setContent(InputStream.class, logicException);
exchange.setOutMessage(outMessage);
outMessage.setExchange(exchange);
return outMessage;
}
private void resetFault(Exchange exchange) {
exchange.put(Exception.class, null);
}
private void resetOrigInterceptorChain(Message message) {
InterceptorChain chain = message.getInterceptorChain();
if (chain != null) {
for (Interceptor<?> interceptor : chain) {
chain.remove(interceptor);
}
chain.reset();
}
}
}
After setting this exception manually I'm going to ExceptionMapper implementation where my LogicException is consumed and response with exception is building. I cannot avoid Exception mapper when is declared as a provider through WebClient, so I've decided to use it and remapped Exception later.

Categories