Spring 5 PathVariable Validation Causing Http 404 - java

When I add #Validated to a Spring RestConstroller I am receiving a HTTP 404 response for that Controllers endpoints. Before adding the annotation the endpoint is found without any issues.
The issue is with #PathVariable specifically and I have validation working correctly (using #Valid) with non-primitive RequestBody parameters.
There are quite a few articles on this and I've spent a significant amount of time trying several variations without success. The most simple is .
According to this simple example https://sdqali.in/blog/2015/12/05/validating-requestparams-and-pathvariables-in-spring-mvc/ I believe it should be as simple as adding #Validated to the Controller, leaving off #Valid for the PathVariable parameter and adding a MethodValidationPostProcessor bean. Preferably I would use #Valid instead of #Validated.
I am using Spring 5 (not boot) and have hibernate-validator 6.0.16.Final on the classpath
Problematic RestController
#Validated
#RestController
#RequestMapping(value = "practices")
public class RestPracticeResource implements PracticeResource {
#Override
#GetMapping("/{id}")
public ResponseEntity<Practice> getPractice(#Id #PathVariable("id") final String id) throws ResourceNotFoundException {
final UUID uuidId = UUID.fromString(id);
}
}
Working RestController for RequestBody
#RestController
#RequestMapping(value = "accounts")
public class RestAccountResource implements AccountResource {
#Override
#PostMapping
public ResponseEntity<AccountDto> create(#NotNull #Valid #RequestBody final CreateAccountDto createAccountDto)
throws ResourceAlreadyExistsException {
...
}
}
Id Annotation
#Target({ElementType.FIELD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = Id.IdValidator.class)
public #interface Id {
// ... Stock standard code here
class IdValidator implements ConstraintValidator<Id, String> {
#Override
public void initialize(final Id id) {}
#Override
public boolean isValid(final String id, final ConstraintValidatorContext constraintValidatorContext) {
// Basic REGEX match for UUID format
return ValidationUtil.isValidId(id);
}
}
}
ExceptionHander
#ExceptionHandler(ConstraintViolationException.class)
public final ResponseEntity<ApiError> handleConstraintViolationException(final ConstraintViolationException ex,
final WebRequest webRequest) {
// .. extract violations into standard format
return new ResponseEntity<>(apiError, BAD_REQUEST);
}
MethodValidationPostProcessor bean
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
Actual Results
As soon as I add #Validated I receive Http 404 responses for the /practices/{id} endpoint not matter what format id I provider. If I remove #Validated I can pass a valid UUID and all is ok or pass an invalid UUID and get an Exception thrown from UUID.fromString(id).
#Id is ignored if I attempt to use #Valid as per the request body validation.
Expected Results
Adding #Validated will enabled the #Id annotation validation and a ConstraintViolationException is thrown when a non-UUID formatted {id} is provided.

Here's an old question that is similar to this and i found the solution i was looking for. Link: Spring rest controller #RequestParam validation
To summarize Szymon Stepniak's answer. This is a spring-mvc bug. I'm using hibernate version 5.2.4.Final and spring-webmvc 4.3.8.RELEASE version.
If the rest controller implements an interface and we also add the #Validated annotation the RequestMappingHandlerMapping will not register the endpoint. I am not aware of the root cause. But removing the "implement interface" solved the problem for me.

Use the Proxy Target Class and it will work with
#Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)

I added the usual stuff you have done (libraries in pom, #Valid on RequestBody etc) and got a 404 too!
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.1.3.Final</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.1-b11</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
What the Spring docs (and many blogs) leave it as subtle is that Spring looks for an exit point to throw the error but if that exit doesn't exist, it will reply with 404. After reading a lot especially this blog, adding this class got Spring to recognize #Valid and find an exit point to throw the error
#RestControllerAdvice
#Order(1)
public class RestControllerExceptionHandler {
#RequestMapping(value = { "error/404" }, method = RequestMethod.GET)
#ExceptionHandler(Exception.class)
public String handleUnexpectedException(Exception e) {
return "views/base/rest-error";
}
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(MethodArgumentNotValidException.class)
public String handlemethodArgumentNotValid(MethodArgumentNotValidException exception) { //
// TODO you can choose to return your custom object here, which will then get transformed to json/xml etc.
return "Sorry, that was not quite right: " + exception.getMessage();
}
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(ConstraintViolationException.class)
public String handleConstraintViolation(ConstraintViolationException exception) { //
// TODO you can choose to return your custom object here, which will then get transformed to json/xml etc.
return "Sorry, that was not quite right: " + exception.getMessage();
}
}

Related

Override standart Spring Data REST API

I have one Entity User in my app. Spring Data REST provides me standard endpoints:
`GET` /user
`GET` /user/<id>
`POST` /user
`PUT` /user
`PATCH` /user
`DELETE` /user/<id>
I need to override default behaviour of DELETE endpoint not changing endpoint url /user. If I add following to my controller:
#Controller
#RequestMapping("/user")
public class User {
#DeleteMapping("/{id}")
#CrossOrigin
public ResponseEntity<?> delete(#PathVariable("id") final String id) {
userService.delete(id); // in service I remove user with other
return ResponseEntity.ok().build();
}
// other custom endpoints
}
I found that other standard REST endpoints do not work - I always receive 405 error. So, my question is - how to customize this endpoint and not affect other endpoints? (I know how to do this in #RepositoryEventHandler - but I should avoid this in my case)
Did you read this: Overriding Spring Data REST Response Handlers?
#RepositoryRestController
#RequestMapping("/users") // or 'user'? - check this...
public class UserController {
#Autoware
private UserRepo userRepo;
#Transactional
#DeleteMapping("/{id}")
public ResponseEntity<?> delete(#PathVariable("id") String id) { // or Long id?..
// custom logic
return ResponseEntity.noContent().build();
}
}
But if you want to add extra business logic to delete process you even don't need to implement a custom controller, you can use a custom event handler:
#Component
#RepositoryEventHandler(User.class)
public class UserEventHandler {
#Autoware
private UserRepo userRepo;
#BeforeDeleteEvent
public void beforeDelete(User u) {
//...
if (/* smth. wrong */) throw new MyException(...);
}
}

JSR - 349 bean validation for Spring #RestController with Spring Boot

I am using Spring Boot 1.5.2.RELEASE and not able to incorporate JSR - 349 ( bean validation 1.1 ) for #RequestParam & #PathVariable at method itself.
For POST requests, if method parameter is a Java POJO then annotating that parameter with #Valid is working fine but annotating #RequestParam & #PathVariable with something like #NotEmpty, #Email not working.
I have annotated controller class with Spring's #Validated
There are lots of questions on SO and I have commented on this answer that its not working for me.
Spring Boot includes - validation-api-1.1.0.Final.jar and hibernate-validator-5.3.4.Final.jar .
Am I missing anything?
Example code ,
#RequestMapping(method = RequestMethod.GET, value = "/testValidated", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseBean<String> testValidated(#Email #NotEmpty #RequestParam("email") String email) {
ResponseBean<String> response = new ResponseBean<>();
response.setResponse(Constants.SUCCESS);
response.setMessage("testValidated");
logger.error("Validator Not called");
return response;
}
Below handler is never called when I send empty values or not well formed email address for email & control always goes to with in testValidated method.
#ExceptionHandler(ConstraintViolationException.class)
#ResponseBody
#ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseBean handle(ConstraintViolationException exception) {
StringBuilder messages = new StringBuilder();
ResponseBean response = new ResponseBean();
exception.getConstraintViolations().forEach(entry -> messages.append(entry.getMessage() + "\n"));
response.setResponse(Constants.FAILURE);
response.setErrorcode(Constants.ERROR_CODE_BAD_REQUEST);
response.setMessage(messages.toString());
return response;
}
ResponseBean<T> is my application specific class.
I had asked the question after more than two days of unsuccessful hit & trial. Lots of confusing answers are out there because of confusions around Spring Validations and JSR validations, how Spring invokes JSR validators, changes in JSR standards & types of validations supported.
Finally, this article helped a lot.
I solved problem in two steps,
1.Added following beans to my Configuration - without these beans , nothing works.
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor mvProcessor = new MethodValidationPostProcessor();
mvProcessor.setValidator(validator());
return mvProcessor;
}
#Bean
public LocalValidatorFactoryBean validator() {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setProviderClass(HibernateValidator.class);
validator.afterPropertiesSet();
return validator;
}
2.Placed Spring's #Validated annotation on my controller like below,
#RestController
#RequestMapping("/...")
#Validated
public class MyRestController {
}
Validated is - org.springframework.validation.annotation.Validated
This set up doesn't affected #Valid annotations for #RequestBody validations in same controller and those continued to work as those were.
So now, I can trigger validations like below for methods in MyRestController class,
#RequestMapping(method = RequestMethod.GET, value = "/testValidated" , consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseBean<String> testValidated(
#Email(message="email RequestParam is not a valid email address")
#NotEmpty(message="email RequestParam is empty")
#RequestParam("email") String email) {
ResponseBean<String> response = new ResponseBean<>();
....
return response;
}
I had to add another handler in exception handler for exception - ConstraintViolationException though since #Validated throws this exception while #Valid throws MethodArgumentNotValidException
Spring #Validated #Controller did not mapped when adding #Validated. Removal of any inheritance from controller itself did help. Otherwise Sabir Khan's answer worked and did help.

Spring REStful web service #initbinder not allowing other validation

I have the following Rest controller:
#RestController
public class DocumentSearchController_global
{
#InitBinder//("TestCustomAnotation")
protected void initBinder(WebDataBinder binder) {
binder.setValidator(new ChekAtleastOneValueValidator());
}
#RequestMapping(value = "/validator", method = RequestMethod.POST, produces = { MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE })
protected DocumentSearchResponse validatortest(#Valid #RequestBody TestCustomAnotation objDMSRequest, Errors e, BindingResult br) throws AppException
{
if(br.hasErrors())
System.out.println("ERRor");
if (e.hasErrors())
{
System.out.println("Got Error: "+ e.getFieldError());
}
DocumentSearchResponse objDocSearchResponse = null;
return objDocSearchResponse;
}
#ExceptionHandler
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
#ResponseBody
public String handleMethodArgumentNotValidException(
MethodArgumentNotValidException error) {
System.out.println("ERROR-->>>>>>>>>>>>>>>>>>>>>>>>" +error.getMessage());
return "Bad request: " + error.getMessage();
}
}
And this is the bean where the request will be cast:
public class TestCustomAnotation
{
#ValidDocumentModifiedDate({"7Days", "30Days","60Days"})
String docModifiedDate;
#NotNull
String objectId;
#NotNull
String jobId;
Setter and GEtter
}
In the controller if I specify binder.setValidator(new
ChekAtleastOneValueValidator()); the contol will only go to
ChekAtleastOneValueValidator it will not check for #notnull
#ValidDocumentModifiedDate`
If I don't have binder.setValidator(new
ChekAtleastOneValueValidator()); then the control will check for
#notnull#ValidDocumentModifiedDate validation but not
ChekAtleastOneValueValidator.
My question is: is there a way in Spring to use Spring validation, custom annotation and #notnull annotation and get all the error of all the validation or spring allows to use only Spring validators?
Actually the question itself was wrong. I got the answer I use a Spring Validator class to validate all the request comming in and then use #validated in stead of #valid. I don't use annotation at the request anymore and let the class be a POJO. thats it problem solved

Is it possible to validate #RequestParam in Spring MVC REST endpoint?

In Jersey 2 it is possible to do this:
#GET
#PATH("user/{email}")
public IDto getUser(#NotNull #Email #PathParam("email") String validEmail) {
return userManagementService.findUserByEmail(validEmail);
}
But I cannot make something similar to work in Spring MVC, it seems that the validation is only done when providing an object in #RequestBody or using an SpringMVC Form, for example the following won't work:
#RequestMapping(value="/user/{email}", method = RequestMethod.GET)
public #ResponseBody IDto getUser(#NotNull #Email #PathVariable String validEmail) {
return userManagementService.findUserByEmail(validEmail);
}
There are other similar questions, but those seem to be oriented to Spring MVC UI applications, in my case it is only a REST API which returns JSON response so I don't have any View to map/bind to the controller.
Seems it is possible, using #Validated.
Here's an example.
Based on OP's question, this should work:
#RestController
#Validated
public class MyController {
#GetMapping(value="/user/{email}")
public #ResponseBody IDto getUser(#NotNull #Email #PathVariable String validEmail) {
return userManagementService.findUserByEmail(validEmail);
}
}
In plain Spring implementations, it may be required to manually register the validator bean:
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
1- Simply add #Validated annotation at the top of your class.
2- Put whatever annotations for validations (#NotBlank, Min(1), etc.) before the #RequestParam annotation in your method signature.
The validated annotation from the org.springframework.validation.annotation.Validated package to validate a #PathVariable. Make sure the class annotated with #Validated.
#GetMapping("/name-for-day/{dayOfWeek}")
public String getNameOfDay(#PathVariable("dayOfWeek") #Min(1) #Max(7) Integer dayOfWeek) {
return dayOfWeek + "";
}
As far as I can tell, you cannot do this out-of-the-box with Spring.
Options:
Use a regular expression:
#RequestMapping(value="/user/{email:SOME_REXEXP}", method = RequestMethod.GET)
public #ResponseBody IDto getUser(#PathVariable String validEmail) {
return userManagementService.findUserByEmail(validEmail);
}
Use Hibernate Validator to validate the method. Either call the validator manually, or make Spring call it for you using AOP. See https://github.com/gunnarmorling/methodvalidation-integration
Controller should be annotated with spring's #Validated
So update your code with
#Validated
#RequestMapping(value="/user/{email}", method = RequestMethod.GET)
public #ResponseBody IDto getUser(
#NotNull
#Email
#PathVariable String validEmail) {
return userManagementService.findUserByEmail(validEmail);
}

How to create a default method in SpringMVC using annotations?

I can't find a solution to this, and it's driving me crazy. I have #Controller mapped that responds to several methods using #RequestMapping. I'd like to tag one of those methods as default when nothing more specific is specified. For example:
#Controller
#RequestMapping("/user/*")
public class UserController {
#RequestMapping("login")
public String login( MapModel model ) {}
#RequestMapping("logout")
public String logout( MapModel model ) {}
#RequestMapping("authenticate")
public String authenticate( MapModel model ) {}
}
So /user/login -> login method, /user/logout -> logout, etc. I'd like to make it so that if someone goes to /user then it routes to one of these methods. However, I don't see anything on #RequestMapping that would allow me to specify one of these methods as a default handler. I also don't see any other annotations that might be used on the class either to do this. I'm beginning to suspect it doesn't exist.
I'm using Spring 2.5.6. Is this solved in 3.0.0? I might just hack Spring to make it work because it's tremendously annoying this isn't more straightforward.
Thanks in Advance.
Take a look at this answer:
Spring MVC and annotated controllers issue
What if you annotate a method with:
#RequestMapping(method = RequestMethod.GET)
You can see an example here:
Spring 3.0 MVC + Hibernate : Simplified with Annotations – Tutorial
The same behavior can be seen here:
Spring Framework 3.0 MVC by Aaron Schram (look at page 21)
Short answer: I do not know how to simply specify one method as default with a simple tag.
But there is this ...
I do not know in which version of Spring this was implemented, but you can provide multiple values to #RequestMapping in 3.1.2. So I do this:
#Controller
#RequestMapping("/user")
public class UserController {
#RequestMapping(value = {"", "/", "/list"}, method = RequestMethod.GET)
public String listUsers(ModelMap model) { }
#RequestMapping(value = "/add", method = RequestMethod.POST)
public ModelAndView add(HttpServletRequest request, ModelMap model) { }
}
The following URLs then map to listUsers():
http://example.com/user
http://example.com/user/
http://example.com/user/list
I would create one default method without RequestMapping's value in there. Please see method defaultCall() below. You can then simply call them with URL: [yourhostname]/user
#Controller
#RequestMapping("/user")
public class UserController {
#RequestMapping(method = RequestMethod.GET)
public String defaultCall( MapModel model ) {
//Call the default method directly, or use the 'forward' String. Example:
return authenticate( model );
}
#RequestMapping("login")
public String login( MapModel model ) {}
#RequestMapping("logout")
public String logout( MapModel model ) {}
#RequestMapping("authenticate")
public String authenticate( MapModel model ) {}
}
Ref: Spring Framework Request Mapping
Simply using #RequestMapping("**") on your default method should work. Any more specific mappings should still pick up their requests. I use this method for setting up default methods sometimes. Currently using Spring v4.3.8.RELEASE.

Categories

Resources