I have two modules, one calls the other from a rest template.
( admin calls notifServer)
the notifServer has a method annotated with #Async . I want to throw an exception in that method, but the admin gets the response too quickly and the exception method cannot be caught at admin.
I an new to spring and the #Async process. I've tried mapping the response body from the NotifServer to a CCompletableFuture.class .
But Still I get no error response.
This code is from admin
ResponseEntity response = fcmRestTemplate.exchange(nsUrl + "/fcm/admin/" + bulkFcmId, HttpMethod.POST,
HttpEntityUtils.getHttpEntity(moduleCode), CompletableFuture.class);
if (response.getStatusCode() != HttpStatus.CREATED && response.getStatusCode() != HttpStatus.ACCEPTED) {
String errorMessage = ErrorResourceUtil.getErrorMessage((HashMap) response.getBody(),"Unable to send fcm");
setStatusToFailedByBulkFcmId(bulkFcmId);
throw new ClientException(errorMessage);
}
now this is from NotifServer
JobExecution jobExecution = jobLauncher
.run(importJob, new JobParametersBuilder()
.addString("fullPathFileName", TMP_DIR)
.addString("batch_fcm_id", String.valueOf(id))
.addLong("time",System.currentTimeMillis())
.toJobParameters());
if(jobExecution.getStepExecutions().stream().map(StepExecution::getStatus).findFirst().get().equals(BatchStatus.ABANDONED)){
throw new ClientException("INVALID CSV");
This is annotated with #Async.
So is there a way for me to catch the client exception in the response body in the Admin?
EDIT
This is the API from notifServer
#ResponseStatus(HttpStatus.CREATED)
#PostMapping(value = "/admin/{bulkFcmId}")
public void pushFCMByAdmin(#PathVariable Long bulkFcmId) {
fcmService.sendFcmByAdmin(bulkFcmId, AuthUtil.getCurrentUser());
}
Then the sendFcmByAdmin has #Async annotation.
In below code can you provide the return type to be a business object rather than CompletableFuture.class.Since you are passing CompletableFuture.class as a parameter to the exchange it expects a response return value of the type CompletableFuture.class.
ResponseEntity response = fcmRestTemplate.exchange(nsUrl + "/fcm/admin/" + bulkFcmId, HttpMethod.POST,
HttpEntityUtils.getHttpEntity(moduleCode), CompletableFuture.class);
if (response.getStatusCode() != HttpStatus.CREATED && response.getStatusCode() != HttpStatus.ACCEPTED) {
String errorMessage = ErrorResourceUtil.getErrorMessage((HashMap) response.getBody(),"Unable to send fcm");
setStatusToFailedByBulkFcmId(bulkFcmId);
throw new ClientException(errorMessage);
}
Instead of passing Completable Future ,can you try creating it as follows:
Use an asynchronous method to make the rest template call in admin:
#Async
public CompletableFuture<List<BusinessObject>> getResponseAsynchronously(String value) {
String url = "https://restendpoint.eu/rest/v2/lang/" + value + "?fields=name";
BusinessObject[] response = restTemplate.getForObject(url, Country[].class);
return CompletableFuture.completedFuture(Arrays.asList(response));
}
then in the controller read the CompletableFuture like:
#GetMapping("")
public List<String> getAllDataFromRestCall() throws Throwable {
CompletableFuture<List<BusinessObject>> businessObjectsFuture = countryClient.getResponseAsynchronously("fr");
List<String> europeanFrenchSpeakingCountries;
try {
europeanFrenchSpeakingCountries = new ArrayList<>(businessObjectsFuture
.get()
.stream()
.map(Country::getName)
.collect(Collectors.toList()));
} catch (Throwable e) {
throw e.getCause();
}
return europeanFrenchSpeakingCountries;
}
Related
I am using RestTemplate to make Http connection to get data from external APIs. For this I have implemented a custom error handler and set it on the restTemplate object. Below is my custom error handler
public class CustomResponseErrorHandler implements ResponseErrorHandler {
public boolean hasError(ClientHttpResponse response) throws IOException {
int rawStatusCode = response.getRawStatusCode();
if (rawStatusCode / 200 != 1) {
LOG.debug("HTTPS hasError - " + rawStatusCode + "; " + response.getStatusText() + "; " + response.getStatusCode());
return true;
}
return false;
}
public void handleError(ClientHttpResponse response) throws IOException {
int rawStatusCode = response.getRawStatusCode();
LOG.debug("HTTPS handleError - " + rawStatusCode + "; " + response.getStatusText() + "; " + response.getStatusCode());
}
}
and my RestTemplateUtils class looks like below
public class RestTemplateUtils {
RestTemplate restTemplate;
public ResponseEntity<String> restGet(String url) {
restTemplate.setErrorHandler(new CustomResponseErrorHandler());
ResponseEntity<String> response= restTemplate.getForEntity(url, String.class);
return response;
}
}
I expect that any error that gets thrown during the restTemplate.getForEntity() call should be caught and logged by the CustomResponseErrorHandler but that is not the case. When I pass in a non-existent url ResponseEntity<String> response= restTemplate.getForEntity(url, String.class); throws ResourceAccessException. What should I do if I want my custom error handler to catch a 404 in such a case? Am I missing something here or misunderstanding how custom error handler should work here?
If you completely give a non existing url then I don't think the code is going to the point where error handler is executed;
Looking at RestTemplate#doExecute
doExecute(URI url, #Nullable HttpMethod method, #Nullable RequestCallback requestCallback,
#Nullable ResponseExtractor<T> responseExtractor)
code
try {
ClientHttpRequest request = createRequest(url, method);
if (requestCallback != null) {
requestCallback.doWithRequest(request);
}
response = request.execute();
handleResponse(url, method, response);
return (responseExtractor != null ? responseExtractor.extractData(response) : null);
}
handleResponse is where the error handler is looked for but I think yours is erroring out at request.execute();
Provide some non existing url on the server api path, then you would recieve a 404 from the server and your custom error handler gets executed.
I have a requirement to design spring-boot based webservice with #Async tags.
if the data return from server takes huge time how can i divide my webservice in multiple end points so that client does not have to wait for response from server.
i hv tried using three end points.
1. localhost:8080/start -> client will send request for data. (returs a uuid of requestnumber).
2. localhost:8080/checkprogress -> check for progress if data is ready at server.
3. localhost:8080/done/requestId -> return the data list
#ResponseBody
#GetMapping(value = "/start")
public ResponseEntity<String> start() {
String requestId = UUID.randomUUID().toString();
LOG.info("jobName" + requestId);
Job job = new Job("jobName" + requestId);
requestQueue.put(requestId.toString(), job);
service.submitWork(job);
return new ResponseEntity<String>(requestId, HttpStatus.ACCEPTED);
}
#ResponseBody
#RequestMapping(value = "/progress/{requestId}")
public ResponseEntity<String> fetchStatus(#PathVariable("requestId") String requestId) {
Job job = requestQueue.get(requestId);
if (job == null) {
return new ResponseEntity<String>("RequestId is either invalid or already served. requestId:" + requestId,
HttpStatus.BAD_REQUEST);
}
if (job.getState() == State.RUNNING || job.getState() == State.NEW)
return new ResponseEntity<String>(HttpStatus.NO_CONTENT);
if (job.getState() == State.DONE)
return new ResponseEntity<String>(HttpStatus.OK);
return null;
}
#ResponseBody
#RequestMapping(value = "/done/{requestId}")
public ResponseEntity<Object> done(#PathVariable("requestId") String requestId) {
LOG.info("removing requestId:" + requestId);
Job job = requestQueue.get(requestId);
if (job == null) {
return new ResponseEntity<Object>("RequestId is either invalid or already served. requestId:" + requestId,
HttpStatus.BAD_REQUEST);
}
// ResponseEntity<Object> response = new ResponseEntity<Object>(job.getList(),
// createHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE), HttpStatus.OK);
ResponseEntity<Object> response = new ResponseEntity<Object>(job.getListOfSecurities(),
createHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE), HttpStatus.OK);
requestQueue.remove(requestId);
return response;
}
is there a better way of doing above thing in a Standard way in spring-boot java8 application please?
My requirement is, I have to call multiple API in my REST application . I want each API will work independently irrespective of waiting for response from the other. i.e I need if one API request take 5sec then all the api request should take 5sec also
You can make the API call inside a seperate thread as shown below:
new Thread(new Runnable() {
public void run() {
try {
// Do your api call here
}
catch(Exception e)
{// Log or do watever you need}
}
Thus the API call will work asynchronously !
You can use the org.springframework.web.client.AsyncRestTemplate class which return a ListenableFuture to get the value asynchronously. So your method will have take time equal to the most slowest api call.
public static void main(String[] args) {
AsyncRestTemplate asycTemp = new AsyncRestTemplate();
HttpMethod method = HttpMethod.GET;
// create request entity using HttpHeaders
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_PLAIN);
HttpEntity<String> requestEntity = new HttpEntity<String>("params", headers);
ListenableFuture<ResponseEntity<String>> future = asycTemp.exchange("https://www.google.com", method, requestEntity, String.class);
ListenableFuture<ResponseEntity<String>> future1 = asycTemp.exchange("https://www.yahoo.com", method, requestEntity, String.class);
ListenableFuture<ResponseEntity<String>> future2 = asycTemp.exchange("https://www.bing.com", method, requestEntity, String.class);
try {
// waits for the result
ResponseEntity<String> entity = future.get();
// prints body source code for the given URL
System.out.println(entity.getBody());
entity = future1.get();
System.out.println(entity.getBody());
entity = future2.get();
System.out.println(entity.getBody());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
I am trying to create a web application using spring 5 . It's a micro-service which hit few other micro-services. Response from one service is dependent the other.I am using global exception handing in my application.
Here is my code:
#Override
public Mono<Response> checkAvailablity(Request request) {
Mono<Response> authResponse = userService.authenticateToken(request);
return authResponse.doOnSuccess(t -> {
// if success is returned.
// Want to return this innerResponse
Mono<Response> innerResponse =
httpService.sendRequest(Constant.SER_BOOKING_SERVICE_CHECK_AVAILABILTY,
request.toString(), Response.class);
}).doOnError(t -> {
logger.info("Subscribing mono in Booking service - On Error");
Mono.error(new CustomException(Constant.EX_MODULE_CONNECTION_TIMED_OUT));
});
In case of error I want to throw CustomException and catch it in global exception handler:
#ControllerAdvice
public class ExceptionInterceptor {
public static Logger logger = Logger.getLogger(ExceptionInterceptor.class);
#ExceptionHandler(value = CustomException.class)
#ResponseBody
public Response authenticationFailure(ServerHttpRequest httpRequest, ServerHttpResponse response,
CustomException ex) {
logger.info("CustomException Occured with code => " + ex.getMessage());
return buildErrorResponse(ex.getMessage());
}
Based on the above code I have two problems:
The exception which is thrown in Mono.error() is not captured in global exception handler.
In case of success, response from the inner service should be returned.
Used two methods in mono: flatmap() and onErrorMap()
and updated my checkAvailablity() code:
public Mono<Response> checkAvailablity(Request request) {
Mono<Response> authResponse = userService.authenticateToken(request);
return authResponse.flatmap(t -> {
// Added transform() for success case
Mono<Response> response = httpService.sendRequest(Constant.SER_BOOKING_SERVICE_CHECK_AVAILABILTY,
request.toString(), Response.class);
logger.info("Response from SER_BOOKING_SERVICE_CHECK_AVAILABILTY");
return response;
}).onErrorMap(t -> {
// Added onErrorMap() for failure case & now exception is caught in global exception handler.
throw new CustomException(Constant.EX_MODULE_CONNECTION_TIMED_OUT);
});
}
#Before("within(control..*)")
public void before(JoinPoint joinPoint) {
// Set Process Start Time
startTime = System.currentTimeMillis();
Object[] arguments = joinPoint.getArgs();
if (arguments != null && arguments.length > 0) {
System.out.println("data:" + arguments[0]);
}
}
I tried to use #aspect to output tht request body, but if the parameter is wrong when using #valid, spring boot will throw MethodArgumentNotValidException, and #aspect logs will not be output.
How can I output the json request body at the time when server receives a http request even there is an exceptinon happened?
I fixed the problem by using #AfterThrowing.
#AfterThrowing(pointcut = "controllerInvocation()", throwing = "exception")
public void afterThrowing(JoinPoint joinPoint, Exception exception) {
this.outputResponseLog(joinPoint, exception);
}