spring boot timing of output #Requestbody and #Responsebody - spring-boot

#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);
}

Related

How to handle exceptions thrown in the service layer?

I'm working on a spring-boot application. I tried handling exceptions .But i guess there is something wrong about how I'm doing it because it always throws internal server error 500.
I tried setting up custom exception classes and also used response status codes with #ResponseStatus. But regardless of what the exception is it throws an internal server error only.
I'm using intellij and the message i've given in the exception is printed there but the response body is empty.This i guess must be because it is throwing an internal server error.
Controller class
#RequestMapping(value = "/attendance",method = RequestMethod.POST)
public ResponseEntity<?> enterAttendance(#RequestBody ViewDTO viewDTO) throws CustomException{
return new ResponseEntity<>(tempResultServices.handleAttendance(viewDTO),HttpStatus.OK);
}
}
Service layer
#Override
public TempResult handleAttendance(ViewDTO viewDTO) throws CustomException {
TempIdentity tempIdentity=new TempIdentity();
tempIdentity.setRegistrationNo(viewDTO.getRegistrationNo());
tempIdentity.setCourseId(viewDTO.getCourseId());
tempIdentity.setYear(viewDTO.getYear());
tempIdentity.setSemester(viewDTO.getSemester());
User user=userService.findByUserId(viewDTO.getUserId());
tempIdentity.setUser(user);
if(!viewDTO.isAttendance()){
TempResult tempResultUser =new TempResult(tempIdentity,viewDTO.isAttendance(),0);
ResultIdentity resultIdentity=new ResultIdentity(tempIdentity.getRegistrationNo(),tempIdentity.getCourseId(),tempIdentity.getYear(),tempIdentity.getSemester());
Result result=new Result(resultIdentity,0,"E*");
AttendanceDraft attendanceDraft=atteDraftService.findDraft(viewDTO.getRegistrationNo(),viewDTO.getCourseId(),viewDTO.getYear(),viewDTO.getSemester(),viewDTO.getUserId());
if(attendanceDraft!=null){
attendanceDraft.setStatus(true);
atteDraftService.save(attendanceDraft);
//atteDraftService.delete(attendanceDraft);
tempResultRepository.save(tempResultUser);
resultRepository.save(result);
return tempResultUser;
}
else{
throw new CustomException("No draft available");
}
}
else{
TempResult tempResultUser =new TempResult(tempIdentity,viewDTO.isAttendance());
AttendanceDraft attendanceDraft=atteDraftService.findDraft(viewDTO.getRegistrationNo(),viewDTO.getCourseId(),viewDTO.getYear(),viewDTO.getSemester(),viewDTO.getUserId());
if(attendanceDraft!=null){
attendanceDraft.setStatus(true);
atteDraftService.save(attendanceDraft);
//atteDraftService.delete(attendanceDraft);
tempResultRepository.save(tempResultUser);
return tempResultUser;
}
else{
throw new CustomException("No draft available");
}
}
}
The exception class
#ResponseStatus(code= HttpStatus.NOT_FOUND)
public class CustomException extends RuntimeException {
public CustomException(String message){
super(message);
}
}
The terminal in the intellij prints "No draft available ". But i want it not as an internal server error.
Can some one tell me how i should be handling these errors please?
I tried using the #RestControllerAdvice
#RestControllerAdvice
public class WebRestControllerAdvice {
#ExceptionHandler(CustomException.class)
public ResponseMsg handleNotFoundException(CustomException ex) {
ResponseMsg responseMsg = new ResponseMsg(ex.getMessage());
return responseMsg;
}
}
And this is my response message class
public class ResponseMsg {
private String message;
//getters and setters
}
This is another simple request in the application
#RequestMapping(value = "/user/view",method = RequestMethod.POST)
public ResponseEntity<?> getUser(#RequestBody UserDTO userDTO) throws CustomException{
User user=userService.findByUsername(userDTO.getUsername());
if(user!=null){
return ResponseEntity.ok(user);
}
//
throw new CustomException("User not found");
}
But still the custom exception is not thrown. The response body is empty. but intellij says "user not found" and postman returns the status code 500.
Spring boot has a very convenient way to handle exceptions in any layer of your application which is defining a #ControllerAdvice bean. Then you can throw any type of exception in your code and it will be "captured" on this class.
After this you can handle and return whatever your app needs to return.
By the way, you can return your custom object and it will be parsed to json automatically.
Documentation: https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/
Sample code:
#ControllerAdvice
public class ErrorHandler {
#ExceptionHandler(BadRequestException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public Object processValidationError(BadRequestException ex) {
//return whatever you need to return in your API
}
}

how to Throw an exception from an #Async annotated method

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;
}

Spring Boot Callback after client receives resource?

I'm creating an endpoint using Spring Boot which executes a combination of system commands (java.lang.Runtime API) to generate a zip file to return to the client upon request, here's the code.
#GetMapping(value = "generateZipFile")
public ResponseEntity<Resource> generateZipFile(#RequestParam("id") Integer id) throws IOException {
org.springframework.core.io.Resource resource = null;
//generate zip file using commandline
resource = service.generateTmpResource(id);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_TYPE, "application/zip")
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"randomFile.zip\"")
.body(resource);
//somehow delete generated file here after client receives it
}
I cannot keep stacking up the files on the server for obvious disk limit reasons, so I'm looking for a way to delete the files as soon as the client downloads them. Is there a solution in Spring Boot for this? I basically need to hook a callback that would do the cleanup after the user receives the resource.
I'm using Spring Boot 2.0.6
You can create a new thread but a best solution would be create a ThreadPoolExecutor in order to manage threads or also Scheduled annotation helps us.
new Thread() {
#Override
public void run() {
service.cleanup(id);
}
}.start();
UPDATED
A best answer, it would be using a Stack combine with Thread.
Here is the solution that I've done.
https://github.com/jjohxx/example-thread
I ended up using a HandlerInterceptorAdapter, afterCompletion was the callback I needed. The only challenge I had to deal with was passing through the id of the resource to cleanup, which I handled by adding a header in my controller method:
#GetMapping(value = "generateZipFile")
public ResponseEntity<Resource> genereateZipFile(#RequestParam("id") Integer id,
RedirectAttributes redirectAttributes) throws IOException {
org.springframework.core.io.Resource resource = myService.generateTmpResource(id);;
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_TYPE, "application/zip")
.header(MyInterceptor.TMP_RESOURCE_ID_HEADER, id.toString())
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"someFile.zip\"")
.body(resource);
}
The interceptor code:
#Component
public class MyInterceptor extends HandlerInterceptorAdapter {
public static final String TMP_RESOURCE_ID_HEADER = "Tmp-ID";
#Autowired
private MyService myService;
#Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
if(response == null || !response.containsHeader(TMP_RESOURCE_ID_HEADER)) return;
String tmpFileId = response.getHeader(TMP_RESOURCE_ID_HEADER);
myService.cleanup(tmpFileId);
}
}
For more information about interceptors see here.

How can I make sure exceptions during parsing lead to the same kind of response as the (custom) response returned for validation failures?

I'm using Spring to create an API, but I'm having some trouble introducing custom error reporting on (a part of) the validation of the request body.
When parsing/validation errors occur, I want to give a custom response back to the user.
This works well for fields annotated with #Valid along with validators like #javax.validation.constraints.NotNull by using a custom ResponseEntityExceptionHandler annotated with #ControllerAdvice.
It does not work however if an Exception is thrown while parsing the request body (before the validations even run). In that case I get an html error page with status 500 (Server Error)
How can I make sure the exceptions during parsing lead to the same kind of response as the (custom) one I return for validation failures?
My endpoint's code looks like this:
#RequestMapping(value= "/endpoint"
produces = { "application/json" },
consumes = { "application/json" },
method = RequestMethod.POST)
default ResponseEntity<Object> postSomething(#Valid #RequestBody MyRequestBody requestData){
// ...
}
MyRequestBody class looks like this:
#Validated
public class MyRequestData {
#JsonProperty("stringValue")
private String stringValue = null;
#NotNull
#Valid
public String getStringValue() {
return stringValue;
}
// ...
public enum EnumValueEnum {
VALUE_1("value 1"),
VALUE_1("value 2");
private String value;
EnumValueEnum(String value) {
this.value = value;
}
#Override
#JsonValue
public String toString() {
return String.valueOf(value);
}
#JsonCreator
public static EnumValueEnum fromValue(String text) {
if(text == null){
return null;
}
for (EnumValueEnum b : EnumValueEnum.values()){
if (String.valueOf(b.value).equals(text)) {
return b;
}
}
throw new HttpMessageNotReadableException("EnumValueEnum \"" + text + "\" does not exist");
}
}
#JsonProperty("enumValue")
private EnumValueEnum enumValue = null;
}
The custom validation error handling (and reporting) looks like this:
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class MyValidationHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
// return status(BAD_REQUEST).body(new ValidationResponse(ex.getBindingResult().getFieldErrors()));
}
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
// return status(BAD_REQUEST).body(new ValidationResponse((JsonMappingException) ex.getCause()));
}
}
In this code, if a user sends a request with an enum value that doesn't exist, an HttpMessageNotReadableException is thrown. I would like to catch that somewhere and replace it with a custom response that is consistent with the other exception handling I do. Where/How can I do that?
I found a solution to my own problem.
You can actually use Spring MVC's normal exception handling:
Annotating a method with #ExceptionHandler will make Spring try to use it for exception handling for the exception type specified (in the annotation's value field or the method's argument). This method can be placed in the controller or even in the ResponseEntityExceptionHandler I use for the other validation response handling.
#ExceptionHandler
public ResponseEntity handle(HttpMessageConversionException e){
// return status(BAD_REQUEST).body(new ValidationResponse((JsonMappingException) e.getCause()));
}
Mind which type of exception you handle:
The catch here was that the exception thrown while parsing is wrapped in (some subtype of) a JsonMappingException which in turn is wrapped again in a HttpMessageConversionException.
e instanceof HttpMessageConversionException
e.getCause() instanceof JsonMappingException
e.getCause().getCause() // == your original exception
The #ExceptionHandler should therefor accept HttpMessageConversionException instead of the originally thrown exception (which in my case was HttpMessageNotReadableException)
It will not work if you write an #ExceptionHandler that only accepts your original Exception!

Empty Exception Body in Spring MVC Test

I am having trouble while trying to make MockMvc to include the exception message in the response body. I have a controller as follows:
#RequestMapping("/user/new")
public AbstractResponse create(#Valid NewUserParameters params, BindingResult bindingResult) {
if (bindingResult.hasErrors()) throw BadRequestException.of(bindingResult);
// ...
}
where BadRequestException looks sth like this:
#ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "bad request")
public class BadRequestException extends IllegalArgumentException {
public BadRequestException(String cause) { super(cause); }
public static BadRequestException of(BindingResult bindingResult) { /* ... */ }
}
And I run the following test against /user/new controller:
#Test
public void testUserNew() throws Exception {
getMockMvc().perform(post("/user/new")
.param("username", username)
.param("password", password))
.andDo(print())
.andExpect(status().isOk());
}
which prints the following output:
Resolved Exception:
Type = controller.exception.BadRequestException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
MockHttpServletResponse:
Status = 400
Error message = bad request
Headers = {X-Content-Type-Options=[nosniff], X-XSS-Protection=[1; mode=block], Cache-Control=[no-cache, no-store, max-age=0, must-revalidate], Pragma=[no-cache], Expires=[0], X-Frame-Options=[DENY]}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
Does anybody have an idea on why is Body missing in the print() output?
Edit: I am not using any custom exception handlers and the code works as expected when I run the server. That is, running the application and making the same request to the server returns back
{"timestamp":1423076185822,
"status":400,
"error":"Bad Request",
"exception":"controller.exception.BadRequestException",
"message":"binding failed for field(s): password, username, username",
"path":"/user/new"}
as expected. Hence, there is a problem with the MockMvc I suppose. It somehow misses to capture the message field of the exception, whereas the default exception handler of the regular application server works as expected.
After opening a ticket for the issue, I was told that the error message in the body is taken care of by Spring Boot which configures error mappings at the Servlet container level and since Spring MVC Test runs with a mock Servlet request/response, there is no such error mapping. Further, they recommended me to create at least one #WebIntegrationTest and stick to Spring MVC Test for my controller logic.
Eventually, I decided to go with my own custom exception handler and stick to MockMvc for the rest as before.
#ControllerAdvice
public class CustomExceptionHandler {
#ExceptionHandler(Throwable.class)
public #ResponseBody
ExceptionResponse handle(HttpServletResponse response, Throwable throwable) {
HttpStatus status = Optional
.ofNullable(AnnotationUtils.getAnnotation(throwable.getClass(), ResponseStatus.class))
.map(ResponseStatus::value)
.orElse(HttpStatus.INTERNAL_SERVER_ERROR);
response.setStatus(status.value());
return new ExceptionResponse(throwable.getMessage());
}
}
#Data
public class ExceptionResponse extends AbstractResponse {
private final long timestamp = System.currentTimeMillis();
private final String message;
#JsonCreator
public ExceptionResponse(String message) {
checkNotNull(message, "message == NULL");
this.message = message;
}
}
This likely means that you either didn't handle the exception or you've really left the body empty. To handle the exception either add an error handler in the controller
#ExceptionHandler
public #ResponseBody String handle(BadRequestException e) {
return "I'm the body";
}
or user the global error handler if you're on 3.2 or above
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler
public #ResponseBody String handleBadRequestException(BadRequestException ex) {
return "I'm the body";
}
}
with this the body will be populate, you should populate it with your error message
Updated solution:
If you don't want to do a full integration test but still want to make sure the message is as expected, you can still do the following:
String errorMessage = getMockMvc()
.perform(post("/user/new"))
...
.andReturn().getResolvedException().getMessage();
assertThat(errorMessage, is("This is the error message!");

Resources