Ignoring Jackson InvalidFormatException to use #ExceptionHandler for #Valid #RequestBody - spring

Basically, I'm trying to validate inputs from the #RequestBody of a REST controller using an #ExceptionHandler to catch MethodArgumentNotValidException constraints.
#PostMapping(value = "/createbaseline")
public ResponseEntity<?> createBaseline(#Valid #RequestPart Baseline baseline){
//...
return ResponseEntity.ok("...");
}
Before I can do that Jackson is throwing an InvalidFormatException when it fails to parse a string to a Date, thus preventing my #ExceptionHandler from validating the remaining inputs. Here is my #ExceptionHandler method that I want to use for validating inputs.
#ExceptionHandler(MethodArgumentNotValidException.class)
protected final ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex){
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach(error -> {
String[] mapping = error.getDefaultMessage().split(":");
errors.put(mapping[0], mapping[1]);
});
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}
I've considered just changing the Date property of the object to a String, but I know that's against best practices. The reason why I want to use the handleMethodArgumentNotValid is because I have custom constraint annotations on fields in my objects that specify a key that I can access via MethodArgumentNotValidException.getBindingResult() like so:
#RequiredFieldConstraint(key = "label")
private String label;
The client can then use those keys to determine which input field will show an error. If I used the method below to catch the InvalidFormatException then I don't have access to those keys specified by the field annotations.
#ExceptionHandler(value = {InvalidFormatException.class})
protected ResponseEntity<Object> handleException(Exception e){
//can't access annotation keys in this method body
return ResponseEntity.badRequest().body("bad");
}
I need to be able to validate ALL inputs sent in the #ResponseBody in order to send the appropriate error message back to the client but I'd still like to utilize deserializing the #RequestBody directly into an object. Does anyone have ideas on how to approach or work around this?

I found a work around for this by implementing a custom deserializer for Dates so that it catches the InvalidFormatException but still returns a new Date() with .setTime(-1) that I can check for in my handleMethodArgumentNotValid method along with all the other inputs.

Related

How can I throw mapping to object exception with gson?

I'm trying to force gson to throw an exception when an string does not map to an object which I'm passing to it.
#ResponseStatus(HttpStatus.CREATED)
#PostMapping("offer")
public String postOffer(#RequestBody String jsonBody) {
Offer offer = gson.fromJson(jsonBody, Offer.class);
offerRepository.save(offer);
return offer.getId();
}
Currently, it will just save what ever it can to the db and ignore any elements that don't map to the class. This is bad for me because I get bad data making it's way to the db.
Any help would be appreciated.
ps. using springboot-data-mongodb and gson for mapping.
Thanks
In GSON you cannot make some fields required.
You can handle this i your code, if the variable is not present in json then in Offer object that variable will simple be assigned as null.
You can add null check to your code for the required fields and throw your own exception.
Since gson dont have this facility, you can also try the answer from below link-
Gson optional and required fields
To achieve this you need to follow two steps:-
1) Mark all required field in Offer class as #NotNull(message="your custom message")
2) Add below class to tell Mongo to validate document before persisting it to the database.
#Configuration
public class MongoEventValidationListener {
#Bean
public ValidatingMongoEventListener validatingMongoEventListener() {
return new ValidatingMongoEventListener(validator());
}
#Bean
public LocalValidatorFactoryBean validator() {
return new LocalValidatorFactoryBean();
}
}

ValidationException vs ConstraintViolationException for validation

I am trying to figure out which Exception (ConstraintViolationException or ValidationException) i have to used on #ControllerAdvice,
.
My controller is
#RequestMapping(value="/myUrl",method = RequestMethod.POST)
public String getMyData( #Valid #RequestBody MyObject myObject,BindingResult bindingResult ) {
And this is my ControllerAdvice class and i am not sure which exception to use.
#ControllerAdvice
public class ValidationAdviceClass{
#ExceptionHandler(ValidationException.class or ConstraintViolationException.class )
public ResponseEntity handleValidtionErrors(ValidationException ex or ConstraintViolationException) {
// mycode
}
Searching i found someone saying use MethodArgumentNotValidException
I am confused with which type of exception to use.
No exception is automatically thrown unless an error happened during the process of validation, for example (then a ValidationException would be thrown).
Any validation errors in your RequestBody would be passed to the BindingResult that you also have as a parameter of your function. Then it's up to you to decide if you want to throw an exception.
I suggest you read this Spring Tutorial about Validating Form Input to better understand how you can achieve what you want.

#RequestBody not restricting to POJO type and BindingResult hasErrors always false

I was not experiencing this problem early in development but just noticed that this was happening when debugging another problem. This happens on all REST endpoints, but below is an example:
#RestController
#RequestMapping("/editlisting")
public class EditParkingSpaceListingController {
#Autowired
ParkingSpaceRepository parkingSpaceRepository;
#Autowired
ParkingSpaceListingRepository parkingSpaceListingRepository;
#RequestMapping(method = RequestMethod.PUT)
public ResponseEntity<String> editParking(#RequestBody ParkingSpaceListingClient pslc, BindingResult result) {
if (result.hasErrors()) {
return new ResponseEntity<String>("", HttpStatus.BAD_REQUEST);
}
// Code to save pslc data to database.
Now, if I send an HTTP request with the body as
{ }
I get a 200 response and when I check MongoDB, there is a new empty document in the collection. If I send an empty body with no brackets, as expected it will return 400. If I send a body with random garbage data that does not exist in the POJO, BindingResult does not seem to pick up the error and a new blank document is still created.
You need to follow the below steps for the input document validations:
(1) Add the javax.validation package constraints (like #NotNull, #Size, etc..) to your ParkingSpaceListingClient bean class.
(2) Add #Validated annotation to your controller method, to capture the validation errors into BindingResult object.
You can look here for more details on Input Validations.

Getting value of invalid field after MethodArgumentNotValidException

My entity class is annotated with #NotNull of hibernate for one of the field.I am catching the exception in #ExceptionHelper. My problem is that i want to access the value of the invalid field so that i can add it into the logger.
Here is the ExceptionHandler:
#ExceptionHandler({ MethodArgumentNotValidException .class })
#ResponseBody
public ResponseEntity<?> handleInvalidMethodArgException(MethodArgumentNotValidException de) {
List<FieldError> bindingResult = de.getBindingResult().getFieldErrors();
LOGGER.debug(String.format("Start : %s : handleDataexception()", this
.getClass().getSimpleName()));
for(FieldError fieldError: bindingResult){
LOGGER.debug("Invalid {} value submitted for {}",
fieldError.getRejectedValue(), fieldError.getField());
}
ErrorMsg = new ErrorMsg(errorCode, errorMessage);
return new ResponseEntity<>(errMsg, HttpStatus.NOT_FOUND);
}
I have added #Validated with the #RequestBody in the controller.
MethodArgumentNotValidException has a getBindingResult method that returns the BindingResult if the failure is validation-related, or null if none. So, you can call the getFieldErrors method which returns List of FieldErrors. Each FieldError has a getRejectedValue which gives you the invalid value of the corresponding field:
ex.getBindingResult().getFieldErrors().forEach(fieldError -> {
LOGGER.debug("Invalid {} value submitted for {}",
fieldError.getRejectedValue(), fieldError.getField());
});
ex is an instance of MethodArgumentNotValidException.
Your entity won't store incorrect values furthermore it is bad practise to use entity object on the front. You should create a DTO object, than send it to your application. Check it (here you could add incorrect staff to logger) and then process it to your entity object and try to save or whatever you want to do.

Spring validation keeps validating the wrong argument

I have a controller with a web method that looks like this:
public Response registerDevice(
#Valid final Device device,
#RequestBody final Tokens tokens
) {...}
And a validator that looks like this:
public class DeviceValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return Device.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
// Do magic
}
}
}
I'm trying to get Spring to validate the Device argument which is being generated by an interceptor. But every time I try, it validates the tokens argument instead.
I've tried using #InitBinder to specify the validator, #Validated instead of #Validand registering MethodValidationPostProcessor classes. So far with no luck.
Either the validator is not called at all, or tokens argument is validated when I was the Device argument validated.
I'm using Spring 4.1.6 and Hibernate validator 5.1.3.
Can anyone offer any clues as to what I'm doing wrong? I've searched the web all afternoon trying to sort this out. Can't believe that the validation area of spring is still as messed up as it was 5 years ago :-(
Ok. Have now solved it after two days of messing about with all sorts of variations. If there is one thing Spring's validation lets you do - it's come up with an incredible array of things that don't work! But back to my solution.
Basically what I needed was a way to manually create request mapping arguments, validate them and then ensure that no matter whether it was a success or failure, that the caller always received a custom JSON response. Doing this proved a lot harder than I thought because despite the number of blog posts and stackoverflow answers, I never found a complete solution. So I've endeavoured to outline each piece of the puzzle needed to achieve what I wanted.
Note: in the following code samples, I've generalised the names of things to help clarify whats custom and whats not.
Configuration
Although several blog posts I read talked about various classes such as the MethodValidationPostProcessor, in the end I found I didn't need anything setup beyond the #EnableWebMvc annotation. The default resolvers etc proved to be what I needed.
Request Mapping
My final request mapping signatures looked like this:
#RequestMapping(...)
public MyMsgObject handleRequest (
#Valid final MyHeaderObj myHeaderObj,
#RequestBody final MyRequestPayload myRequestPayload
) {...}
You will note here that unlike just about every blog post and sample I found, I have two objects being passed to the method. The first is an object that I want to dynamically generate from the headers. The second is a deserialised object from the JSON payload. Other objects could just as easily be included such as path arguments etc. Try something like this without the code below and you will get a wide variety of weird and wonderful errors.
The tricky part that caused me all the pain was that I wanted to validate the myHeaderObj instance, and NOT validate the myRequestPayload instance. This caused quite a headache to resolve.
Also note the MyMsgObject result object. Here I want to return an object that will be serialised out to JSON. Including when exceptions occur as this class contains error fields that need to be populated in addition to the HttpStatus code.
Controller Advice
Next I created an ControllerAdvice class which contained the binding for validation and a general error trap.
#ControllerAdvice
public class MyControllerAdvice {
#Autowired
private MyCustomValidator customValidator;
#InitBinder
protected void initBinder(WebDataBinder binder) {
if (binder.getTarget() == null) {
// Plain arguments have a null target.
return;
}
if (MyHeaderObj.class.isAssignableFrom(binder.getTarget().getClass())) {
binder.addValidators(this.customValidator);
}
}
#ExceptionHandler(Exception.class)
#ResponseStatus(value=HttpStatus.INTERNAL_SERVER_ERROR)
#ResponseBody
public MyMsgObject handleException(Exception e) {
MyMsgObject myMsgObject = new MyMsgObject();
myMsgObject.setStatus(MyStatus.Failure);
myMsgObject.setMessage(e.getMessage());
return myMsgObject;
}
}
Two things going on here. The first is registering the validator. Note that we have to check the type of the argument. This is because #InitBinder is called for each argument to the #RequestMapping and we only want the validator on the MyHeaderObj argument. If we don't do this, exceptions will be thrown when Spring attempts to apply the validator to arguments it's not valid for.
The second thing is the exception handler. We have to use #ResponseBody to ensure that Spring treats the returned object as something to be serialised out. Otherwise we will just get the standard HTML exception report.
Validator
Here we use a pretty standard validator implementation.
#Component
public class MyCustomValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return MyHeaderObj.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
...
errors.rejectValue("fieldName", "ErrorCode", "Invalid ...");
}
}
One thing that I still don't really get with this is the supports(Class<?> clazz) method. I would have thought that Spring uses this method to test arguments to decide if this validator should apply. But it doesn't. Hence all the code in the #InitBinder to decide when to apply this validator.
The Argument Handler
This is the biggest piece of code. Here we need to generate the MyHeaderObj object to be passed to the #RequestMapping. Spring will auto detect this class.
public class MyHeaderObjArgumentHandler implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return MyHeaderObj.class.isAssignableFrom(parameter.getParameterType());
}
#Override
public Object resolveArgument(
MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
// Code to generate the instance of MyHeaderObj!
MyHeaderObj myHeaderObj = ...;
// Call validators if the argument has validation annotations.
WebDataBinder binder = binderFactory.createBinder(webRequest, myHeaderObj, parameter.getParameterName());
this.validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors()) {
throw new MyCustomException(myHeaderObj);
}
return myHeaderObj;
}
protected void validateIfApplicable(WebDataBinder binder, MethodParameter methodParam) {
Annotation[] annotations = methodParam.getParameterAnnotations();
for (Annotation ann : annotations) {
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] { hints });
binder.validate(validationHints);
break;
}
}
}
}
The main job of this class is to use whatever means it requires to build the argument (myHeaderObj). Once built it then proceeds to call the Spring validators to check this instance. If there is a problem (as detected by checking the returned errors), it then throws an exception that the #ExceptionHandler's can detect and process.
Note the validateIfApplicable(WebDataBinder binder, MethodParameter methodParam) method. This is code I found in a number of Spring's classes. It's job is to detect if any argument has a #Validated or #Valid annotation and if so, call the associated validators. By default, Spring does not do this for custom argument handlers like this one, so it's up to us to add this functionality. Seriously Spring ???? No AbstractSomething ????
The last piece, explicit Exception catches
Lastly I also needed to catch more explicit exceptions. For example the MyCustomException thrown above. So here I created a second #ControllerAdvise.
#ControllerAdvice
#Order(Ordered.HIGHEST_PRECEDENCE) // Make sure we get the highest priority.
public class MyCustomExceptionHandler {
#ExceptionHandler
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
#ResponseBody
public Response handleException(MyCustomException e) {
MyMsgObject myMsgObject = new MyMsgObject();
myMsgObject.setStatus(MyStatus.Failure);
myMsgObject.setMessage(e.getMessage());
return myMsgObject;
}
}
Although superficially the similar to the general exception handler. There is one different. We need to specify the #Order(Ordered.HIGHEST_PRECEDENCE) annotation. Without this, Spring will just execute the first exception handler that matches the thrown exception. Regardless of whether there is a better matching handler or not. So we use this annotation to ensure that this exception handler is given precedence over the general one.
Summary
This solution works well for me. I'm not sure that I've got the best solution and there may be Spring classes which I've not found which can help. I hope this helps anyone with the same or similar problems.

Resources