How throws CustomException from reactive service to controller? - java

I am receiving a request in a 0.RestController and give it to the service for processing. If service doesnt throw exception, i just return HttpStatus.200, but if an exception occurs in the service, i need catch it in controller and return the status depending on the exception.
Inside service, i need to use Mono.fromCallable for repo access. Well, if user not found, i try throw my CustomException, but i cant cach it in controller. What am I doing wrong?
Controller:
#RestController
#RequiredArgsConstructor
#Slf4j
public class CardStatusController {
private final CardStatusService cardStatusService;
#PostMapping(value = "api/{user_id}/confirm", produces = {MediaType.APPLICATION_JSON_VALUE})
public Mono<ResponseEntity<HttpStatus>> confirmStatus(
#PathVariable("user_id") String userId,
#RequestBody CardStatusRequest statusRequest) {
statusRequest.setUserId(userId);
cardStatusService.checkCardStatus(statusRequest);
return Mono.just(new ResponseEntity<>(HttpStatus.OK));
}
#ExceptionHandler({ CheckCardStatusException.class })
public void handleException() {
log.error("error!");
}
My service:
public Mono<Void> checkCardStatus(CardStatusRequest statusRequest) throws CheckCardStatusException {
if (statusRequest.getCardEmissionStatus().equals(CardEmissionStatus.ACCEPTED)) {
String reference = statusRequest.getReference();
return Mono.fromCallable(() -> userRepository.findById(statusRequest.getUserId()))
.subscribeOn(Schedulers.boundedElastic())
.switchIfEmpty(Mono.error(new CheckCardStatusException(HttpStatus.NOT_FOUND)))
.flatMap(user -> Mono.fromCallable(() -> cardRepository.findFireCardByUserId(user.getId()))
.flatMap(optionalCard -> {
if (optionalCard.isPresent()) {
if (optionalCard.get().getExtId().isEmpty()) {
Card card = optionalCard.get();
card.setExtId(reference);
try {
cardRepository.save(card);
} catch (Exception e) {
return Mono.error(new CheckCardStatusException(HttpStatus.INTERNAL_SERVER_ERROR));
}
} else {
if (!optionalCard.get().getExtId().equals(reference)) {
return Mono.error(new CheckCardStatusException(HttpStatus.CONFLICT));
}
}
} else {
Card card = new Card();
//set card params
try {
cardRepository.save(card);
} catch (Exception e) {
return Mono.error(new CheckCardStatusException(HttpStatus.INTERNAL_SERVER_ERROR));
}
}
return Mono.error(new CheckCardStatusException(HttpStatus.OK));
})).then();
}
else {
return Mono.error(new CheckCardStatusException(HttpStatus.OK));
}
}
}
My CustomException:
#AllArgsConstructor
#Getter
public class CheckCardStatusException extends RuntimeException {
private HttpStatus httpStatus;
}

The Mono returned by checkCardStatus is never subscribed, so the error signal is ignored. You have to return the whole chain to the Webflux framework as follows:
public Mono<ResponseEntity<HttpStatus>> confirmStatus(
#PathVariable("user_id") String userId,
#RequestBody CardStatusRequest statusRequest) {
statusRequest.setUserId(userId);
return cardStatusService.checkCardStatus(statusRequest)
.then(Mono.just(new ResponseEntity<>(HttpStatus.OK)));
}
In case of an error, the corresponding ExceptionHandler will be executed.

Related

Handle exceptions on Flux frominterable parallel

Is there any way to handle exceptions in a Flux parallel in case N of X rails fail? I have tried with the onErrorMap, onErrorReturn, and with this try catch, but it keeps throwing error even if all the others are ok, because it is going to the catch of the processRequest method.
protected Object processRequest(RequestHolder requestHolder) {
RequestHolderImpl requestHolderImpl = (RequestHolderImpl) requestHolder;
try {
if (requestHolderImpl.getPayload().getClass().equals(LinkedList.class)) {
payload.addAll((List<DataSourceRequest>) requestHolderImpl.getPayload());
} else {
payload.add((DataSourceRequest) requestHolderImpl.getPayload());
}
List<PurposeResponse> response = Flux.fromIterable(payload)
.parallel()
.flatMap(request -> {
try {
return dataSourceCall(request);
} catch (WebClientResponseException e) {
return Mono.just(new PurposeResponse(request.getPurpose(), buildResponseFromException(e, request.getPurpose())));
} catch (Exception e) {
LOGGER.error("No response could be obtained from DS. Exception thrown: {}", e.getMessage());
return Mono.just(new PurposeResponse(request.getPurpose(), new DataSourceException(e)));
}
})
.sequential()
.collectList()
.block();
return new ResponseHolderImpl(response, products);
} catch (Exception e) {
return new DataSourceException(e.getMessage());
}
}
private Mono<PurposeResponse> dataSourceCall(DataSourceRequest purpose) {
RequestHolder requestHolder = new RequestHolderImpl(purpose,
data,
products,
token);
String purposeName = getPurposeName(requestHolder);
RequestEntity<?> requestEntity = createRequestEntity(requestHolder);
LOGGER.info("Sending request to this url: {}", requestEntity.getUrl());
return webClient.get()
.uri(requestEntity.getUrl())
.header("Authorization", "Bearer " + token)
.retrieve()
.bodyToMono(JsonNode.class)
.elapsed()
.map(data -> {
LOGGER.info("Response took {} milliseconds", data.getT1());
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Response obtained from Data Source: {}", data.getT2());
}
return new PurposeResponse(purposeName, data.getT2());
});
}
private Object buildResponseFromException(WebClientResponseException e, String purposeName) {
//do things
}

Force function to throw and return specific Exception to client results in throwing "InvocationTargetException"

When the user creates a record with a name that's already exists in the DB , I'm returning a specific Exception.
#PostMapping("/campaigns")
public ResponseEntity<CampaignDTO> saveCampaign(#RequestBody CampaignDTO campaignDTO) throws ApiErrorResponse, Exception {
if (this.campaignService.getCampaignByName(campaignDTO.getName()) != null) {
throw new IllegalArgumentException("The value already exists!");
}
if (campaignDTO.getProducts() == null) {
ApiErrorResponse errorReponseDto = new ApiErrorResponse("No Products attached");
throw errorReponseDto;
}
campaignDTO = campaignService.saveCampaign(campaignDTO);
ResponseEntity<CampaignDTO> responseEntity = new ResponseEntity<>(campaignDTO , HttpStatus.CREATED);
return responseEntity; // return 201
}
And the Exception that I want to return to the Client is:
public class ApiErrorResponse extends Throwable {
private final String error;
//Any addtional info you might later want to add to it
public ApiErrorResponse(String error){
this.error = error;
}
public String getError(){
return this.error;
}
}
However , when I throw
IllegalArgumentException("The value already exists!")
It is caught by
catch (InvocationTargetException ex) {
// Unwrap for HandlerExceptionResolvers ...
Throwable targetException = ex.getTargetException();
if (targetException instanceof RuntimeException) {
throw (RuntimeException) targetException;
}
else if (targetException instanceof Error) {
throw (Error) targetException;
}
else if (targetException instanceof Exception) {
throw (Exception) targetException;
}
else {
throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
}
}
How can we prevent this , and return "ApiErrorResponse" when the user inserts the same name ?
I want to return my Exception , not anything else.
Here you have few decision. I will mark 2 of them.
1st is to return directly BadRequest for example with specific DTO - It is not best example with the Throwable, but you can create new ErrorResponseDTO:
#PostMapping("/campaigns")
public ResponseEntity<?> saveCampaign(#RequestBody CampaignDTO campaignDTO) throws ApiErrorResponse, Exception {
if (this.campaignService.getCampaignByName(campaignDTO.getName()) != null) {
ApiErrorResponse errorReponseDto = new ApiErrorResponse("The value already exists!");
return ResponseEntity.badRequest().body(errorReponseDto) // return 400
}
if (campaignDTO.getProducts() == null) {
ApiErrorResponse errorReponseDto = new ApiErrorResponse("No Products attached");
return new ResponseEntity<>(errorReponseDto , HttpStatus.BAD_REQUEST); // return 400
}
campaignDTO = campaignService.saveCampaign(campaignDTO);
ResponseEntity<CampaignDTO> responseEntity = new ResponseEntity<>(campaignDTO , HttpStatus.CREATED);
return responseEntity; // return 201
}
Other way is to use #ControllerAdvice which will handle exception and will return what you want. This advice will be triggered after you throw the exception:
#ControllerAdvice
public class MyAdvice {
#ExceptionHandler(value = ApiErrorResponse.class)
public ResponseEntity<MyErrorResponse> handleException(ApiErrorResponse exception) {
return return ResponseEntity.badRequest().body(MyErrorResponse)
}
}
a better way to define a global exception class and a global exception handler.
the global exception class:
#EqualsAndHashCode(callSuper = true)
#Data
public class GlobalErrorInfoException extends RuntimeException {
private String message;
private HttpStatus status;
private Long timestamp;
public GlobalErrorInfoException(HttpStatus status, String message) {
super(message);
this.status = status;
this.message = message;
this.timestamp = System.currentTimeMillis();
}
}
the global exception handler
#RestControllerAdvice
#Slf4j
public class GlobalErrorInfoHandler {
// other ExceptionHandler
// GlobalErrorInfoException handler
#ExceptionHandler(value = GlobalErrorInfoException.class)
public ResponseEntity<?> errorHandlerOverJson(GlobalErrorInfoException e) {
log.error("Global Exception ", e);
return new ResponseEntity<>(e.getMessage(), e.getStatus());
}
#ExceptionHandler(Exception.class)
public ResponseEntity<?> handleRuntimeException(Exception e) {
log.error("error ", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("internal server error");
}
}
use
// your throw
throw new GlobalErrorInfoException(HttpStatus.BAD_REQUEST, "The value already exists!");
you can also handler IllegalArgumentException in the global exception handler.

Spring Webclient - Site Failover on Error

I'm attempting to create a library that leverages Spring Webclient that has common failure logic for all of our applications. There are 2 considerations - configurable retry logic and failover logic to secondary sites.
The failover needs to handle network exceptions and request exceptions with a specific status code, default >= 500.
Would anyone be able to sanity check the below?
i.e.
public Builder webClientBuilder() throws SSLException {
return WebClient.builder()
.baseUrl(baseUrl)
.filter((request, next) -> exchangeFilterFunction(request, next))
.clientConnector(createReactorClientHttpConnector());
}
private Mono<ClientResponse> exchangeFilterFunction(ClientRequest request, ExchangeFunction next) {
if (StringUtils.hasLength(failoverBaseUrl)) {
return nextExchangeWithRetry(request, next)
.onErrorResume(e -> {
if (isRetriableException(e)) {
String uri = request.url().toString().replace(baseUrl, failoverBaseUrl);
LOGGER.info("Attempting to call Failover Site: " + uri);
ClientRequest retryRequest = ClientRequest.from(request).url(URI.create(uri)).build();
return nextExchangeWithRetry(retryRequest, next);
}
return Mono.error(e);
});
} else {
LOGGER.debug("No Failover Configured");
return nextExchangeWithRetry(request, next);
}
}
private Mono<ClientResponse> nextExchangeWithRetry(ClientRequest request, ExchangeFunction next) {
return next.exchange(request)
.flatMap(clientResponse -> {
HttpStatus httpStatus = clientResponse.statusCode();
if (httpStatus.isError()) {
return clientResponse.createException().flatMap(Mono::error);
}
return Mono.just(clientResponse);
})
.retryWhen(retrySpec(request));
}
private boolean isRetriableException(Throwable throwable) {
if (nonRetriableExceptions != null && nonRetriableExceptions.contains(throwable.getClass())) {
return false;
}
if (throwable instanceof WebClientResponseException
&& isNotRetryHttpResponseCode(((WebClientResponseException) throwable).getRawStatusCode())) {
return false;
}
return true;
}

Best practices to retrieve CompletableFuture lists of different types

I want to retrieve data of different types from a database and return to the user within an HTTP result from a Spring Boot service. Because the database retrieval takes a significant amount of time for each, I am making these DB calls asynchronously with CompletableFuture. The pattern I have works and saves time compared to doing this synchronously, but I feel that it can and should be laid out in a cleaner fashion.
I edited the code to change the types to 'PartA', 'PartB', 'PartC', but this is otherwise how it appears. Currently, the service accepts the lists of different types (PartA, PartB, PartC), creates Completable future types of each list calling its own CompletableFuture method that calls the DB, builds a generic list of CompleteableFutures with each type, "gets" the generic list, then adds all the contents of each Future list to the list passed into the service.
This is how the Service methods are coded:
Service.java:
public void metadata(final List<PartA> partAs,final List<PartB> partBs,final List<PartC> partCs,
String prefix,String base,String suffix) throws Exception {
try {
CompletableFuture<List<PartA>> futurePartAs = partACompletableFuture(prefix,base,suffix).thenApply(list -> {
logger.info("PartA here");
return list;
});
CompletableFuture<List<PartB>> futurePartBs = partBCompletableFuture(prefix,base,suffix).thenApply(list -> {
logger.info("PartBs here");
return list;
});
CompletableFuture<List<PartC>> futurePartCs = partCCompletableFuture(prefix,base,suffix).thenApply(list -> {
logger.info("PartCs here");
return list;
});
CompletableFuture<?> combinedFuture = CompletableFuture.allOf(CompletableFuture.allOf(futurePartAs, futurePartBs, futurePartCs));
combinedFuture.get();
partAs.addAll(futurePartAs.get());
partBs.addAll(futurePartBs.get());
partCs.addAll(futurePartCs.get());
} catch (Exception e) {
logger.error("Exception: ", e);
throw e;
}
}
#Async("asyncExecutor")
public CompletableFuture<List<PartA>> partACompletableFuture(String prefix,String base,String suffix) {
return CompletableFuture.supplyAsync(() -> {
try {
logger.info("start PartA");
return getPartAs(prefix,base,suffix);
} catch (Exception e) {
logger.error("Exception: ", e);
throw e;
}
});
}
#Async("asyncExecutor")
public CompletableFuture<List<PartB>> partBCompletableFuture(String prefix,String base,String suffix) {
return CompletableFuture.supplyAsync(() -> {
try {
logger.info("start B");
return getPartBs(prefix,base,suffix);
} catch (Exception e) {
logger.error("Exception: ", e);
throw e;
}
});
}
#Async("asyncExecutor")
public CompletableFuture<List<PartC>> partCCompletableFuture(String prefix,String base,String suffix) {
return CompletableFuture.supplyAsync(() -> {
try {
logger.info("start PartC");
return getPartCs(prefix,base,suffix);
} catch (Exception e) {
logger.error("Exception: ", e);
throw e;
}
});
}
In case you wish to view the Controller and Response type:
Controller.java
#GetMapping(value="/parts/metadata",produces = { MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<MetadataResponse> metadata (#ApiParam(name="prefix",value = "Prefix value for a part",required = false)
#RequestParam(required=false) String prefix,
#ApiParam(name="base",value = "Base value for a part",required= true)
#RequestParam String base,
#ApiParam(name="suffix",value = "Suffix value for a part",required=false)
#RequestParam(required=false) #NotBlank String suffix ) throws Exception {
final List<PartA> partAs = new ArrayList<>();
final List<PartB> partBs = new ArrayList<>();
final List<PartC> partCs = new ArrayList<>();
service.metadata(partAs,partBs,partCs,prefix,base,suffix);
MetadataResponse.MetadataResponseResult res = MetadataResponse.MetadataResponseResult.builder()
.partAs(partAs)
.partBs(partBs)
.partCs(partCs)
.build();
return ResponseEntity.ok(MetadataResponse.result(res, MetadataResponse.class));
}
MetadataResponse.java
#ApiModel(value = "MetadataResponse", parent = BaseBodyResponse.class, description = "Part A, B, C")
public class MetadataResponse extends BaseBodyResponse<MetadataResponse.MetadataResponseResult> {
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#ApiModel(value = "MetadataResponseResult", description = "This Model holds Part As, Bs, Cs")
public static class MetadataResponseResult {
List<PartA> partAs;
List<PartB> partBs;
List<PartC> partCs;
}
}
I don't understand exactly why you need to pass all these lists as parameters in this case: public void metadata(final List<PartA> partAs,final List<PartB> partBs,final List<PartC> partCs, String prefix,String base,String suffix) throws Exception You could modify this method to return the MetadataResponseResult class you already have and use the lists from the ComparableFutures directly
I would remove the thenApply methods since you just log a statement and you don't actually change the results.
Instead of having the three methods (partACompletableFuture, partABCompletableFuture, partCCompletableFuture) you could have one method that receives a Supplier as a parameter.
#Async("asyncExecutor")
public <T> CompletableFuture<T> partCompletableFuture(Supplier<T> supplier) {
return CompletableFuture.supplyAsync(() -> {
try {
logger.info("start Part");
return supplier.get();
} catch (Exception e) {
logger.error("Exception: ", e);
throw e;
}
});
}
Aftewards you can use it as so:
CompletableFuture<List<PartA>> futurePartAs = partCompletableFuture(() ->
getPartAs(prefix,base,suffix));
It should much cleaner. Hope this helped!

How to include a message in a BadRequestException?

Is it possible to include a message in a BadRequestException so when the user sees a response code a 400, he/she can also figure out why?
The scenario would be something like this, simplified:
public Entity getEntityWithOptions(#PathParam("id") String id, #QueryParam("option") String optValue) {
if (optValue != null) {
// Option is an enum
try {
Option option = Option.valueOf(optValue);
} catch (IllegalArgumentException e) {
throw new BadRequestException(e.getMessage());
}
return new Entity(option);
}
return new Entity();
}
I know this can be done returning a Response object instead, but I wouldn't want that.
Is this possible? Maybe with an ExceptionMapper<BadRequestException>? Or this cannot be done since BadRequestException is already a Jersey-specific exception?
There is a really simple approach to this as shown below.
Response.ResponseBuilder resp_builder=Response.status(Response.Status.BAD_REQUEST);
resp_builder.entity(e.getMessage());//message you need as the body
throw new WebApplicationException(resp_builder.build());
if you need to add headers, response media type or some other functionality, ResponseBuilder provides them all.
You can throw a CustomException and map it to a CustomExceptionMapper to provide customized response.
public class CustomException extends RuntimeException {
public CustomException(Throwable throwable) {
super(throwable);
}
public CustomException(String string, Throwable throwable) {
super(string, throwable);
}
public CustomException(String string) {
super(string);
}
public CustomException() {
super();
}
}
#Provider
public class CustomExceptionMapper implements ExceptionMapper<CustomException> {
private static Logger logger = Logger.getLogger(CustomExceptionMapper.class.getName());
/**
* This constructor is invoked when exception is thrown, after
* resource-method has been invoked. Using #provider.
*/
public CustomExceptionMapper() {
super();
}
/**
* When exception is thrown by the jersey container.This method is invoked
*/
public Response toResponse(CustomException ex) {
logger.log(Level.SEVERE, ex.getMessage(), ex);
Response.ResponseBuilder resp = Response.status(Response.Status.BAD_REQUEST)
.entity(ex.getMessage());
return resp.build();
}
}
Use the CustomException in your code like this.
public Entity getEntityWithOptions(#PathParam("id") String id,
#QueryParam("option") String optValue)
throws CustomException {
if (optValue != null) {
// Option is an enum
try {
Option option = Option.valueOf(optValue);
} catch (IllegalArgumentException e) {
throw new CustomException(e.getMessage(),e);
}
return new Entity(option);
}
return new Entity();
}
Instead of message, you can also construct an object and pass it to mapper through CustomException.
You should create a custom exception like this
public class CustomBadReq extends WebApplicationException {
public CustomBadReq(String message) {
super(Response.status(Response.Status.BAD_REQUEST)
.entity(message).type(MediaType.TEXT_PLAIN).build());
}
}
See also this
You can do it using the BadRequestException(Response response)constructor.
For example:
String msg = e.getMessage();
throw new BadRequestException(Response.status(BAD_REQUEST)
.entity(msg).build());

Categories

Resources