Custom Validator on Controller methods - java

I followed this tutorial to create custom validators:
http://codetutr.com/2013/05/29/custom-spring-mvc-validation-annotations/
And according to this one, it's possible to validate request aguments with JSR-303 validation annotation:
https://raymondhlee.wordpress.com/2015/08/29/validating-spring-mvc-request-mapping-method-parameters/
My custom ConstraintValidator is never invoked. Here is my code:
Controller:
#RestController
#RequestMapping(value = "/json")
#Validated
public class JsonResource {
#RequestMapping(method = POST, consumes=APPLICATION_JSON_VALUE"))
public void postJson(#SafeHtml #RequestBody JsonNode jsonQuery){
// post a foo
}
}
SafeHtml annotation:
#Documented
#Constraint(validatedBy = {SafeHtmlJsonValidator.class})
#Target( {ElementType.PARAMETER, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface SafeHtml {
String message() default "{SafeHtml}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Custom ConstraintValidator:
#Component
public class SafeHtmlJsonValidator implements ConstraintValidator<SafeHtml, JsonNode> {
#Override
public void initialize(SafeHtml constraintAnnotation) {}
#Override
public boolean isValid(JsonNode value, ConstraintValidatorContext context) {
// validate my JSON
return true;
}
}
The problem is that SafeHtmlJsonValidator.isValid() is never invoked.
Tested with Spring 4.2.6.RELEASE

If you don't want to convert everything to a DTO you can add MethodValidationPostProcessor
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
that will allow you to add validation annotation to method field directly
like
#RestController
#RequestMapping(value = "/json")
#Validated
public class JsonResource {
#RequestMapping(method = POST, consumes=APPLICATION_JSON_VALUE"))
public void postJson(#SafeHtml #RequestBody JsonNode jsonQuery){
// post a foo
}
}

I am not sure if annotating with custom validation (#SafeHtml) onto a #RequestBody parameter will work in a controller method. It will surely work for #RequestParam and #PathVariable in the controller method, when its a get request.
For post requests, I believe there should be a POJO bean representing the request body, and the custom validation annotations should be placed on the attributes of this request object.
In your example you should ideally have a RequestObj.class with an attribute JsonNode, and this should be annotated with #SafeHtml.
Since you have already annotated #Validated at the class level of the controller, the controller method will then be
#RequestMapping(method = POST, consumes=APPLICATION_JSON_VALUE"))
public void postJson(#RequestBody RequestObj requestBody){
// post a foo
}
and RequestObj.class will have
#SafeHtml
private JsonNode jsonQuery;
//getters,setters...
This should trigger the custom validator. Worth a try. Note that your request json structure will change accordingly.

Related

how can I do to make it return error when restful url has invalid parameter

#RestController()
#RequestMapping(path = "/users")
public class UserController {
#GetMapping()
public #ResponseBody Page<User> getAllUsers(#RequestParam Integer pageSize, UserRequest userRequest) {
//TODO: some implementation
}}
public class UserRequest{
public String name;
public String age;
}
send the request with invalid parameter, like localhost:8800/users?name1=1234, I want to return error. but in fact it ignore the invalid parameter name1.
I tried to add the user defined annotation on the method parameter and on the class , codes like below
#RestController()
#RequestMapping(path = "/users")
#Validated
public class UserController {
#GetMapping()
public #ResponseBody Page<User> getAllUsers(#RequestParam #Validated Integer pageSize, #Validated UserRequest userRequest} {
//TODO: some implementation
}
}
But it does not working.
I think it is happened because framework has ignore the invalid parameter before the method was called.
where did framework handle the url and how can I do to make it return error instead of ignore?
You can reject parameters that are not valid. You can do so in a HandlerInterceptor class.
Reference: Rejecting GET requests with additional query params
In addition to what is done in the above reference, in your addInterceptors, you can specify the path that is intercepted.
Like this:
#Configuration
public class WebConfig implements WebMvcConfigurer {
private String USERS_PATH = "/users";
// If you want to cover all endpoints in your controller
// private String USERS_PATH = List.of("/users", "/users/**");
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new FooHandlerInterceptor()).addPathPatterns(USERS_PATH);
}
}

how to add specific logic to the method by custom annotation in Spring boot?

My question is this.
I would like to add custom annotation to spring boot and designate it as declared without declaring a specific logic.
Suppose you have the following code:
#MyCustomAnnotation
#Controller
public class ExController {
#RequestMapping(value = "/index", method = RequestMethod.GET)
public String index(Model model){
return "index";
}
}
I want the above code to perform the following logic.
#MyCustomAnnotation
#Controller
public class ExController {
#RequestMapping(value = "/index", method = RequestMethod.GET)
public String index(Model model){
//Code added because it has an annotation
model.addAttribute("key","value");
//Code added because it has an annotation
return "index";
}
}
I've been thinking about Reflection and other methods, but I can't think of the right way
Can someone grateful give a solution or keyword for this problem?
#RequestParam is a Spring annotation that is used to bind a parameter to the value of a method parameter.
If you want to add logic to your method, you can do so without any annotations.
public ResponseEntity<String> myMethod(#RequestParam(required = false) String param) {
//additional logic
return new ResponseEntity<>(HttpStatus.OK);
}
The above code will add additional logic to the method.
i hope this can be helpful i am new to this .
You can add specific logic to the method by custom annotation in Spring boot by using the #Component annotation.
You can use #Configuration annotation to add your custom logic.
<code>#Configuration
public class MyCustomAnnotationConfig {
#Bean
public MyCustomAnnotationBean myCustomAnnotationBean() {
return new MyCustomAnnotationBean();
}
}
And then you can use #Autowired to inject your bean into your controller.
```
#MyCustomAnnotation
#Controller
public class ExController {
#Autowired
private MyCustomAnnotationBean myCustomAnnotationBean;
#RequestMapping(value = "/index", method = RequestMethod.GET)
public String index(Model model){
myCustomAnnotationBean.doSomething();
return "index";
}
}

How to validate #PathVariable with custom validator annotation containing repository bean

I know how to validate #PathVariable from https://stackoverflow.com/a/35404423/4800811
and it worked as expected with standard annotations but not with the customized one using a Repository bean. Maybe the bean is not initialized and I end up with NullPointerException when accessing the end point has #PathVariable validated. So how to get that work?
My Controller:
#RestController
#Validated
public class CustomerGroupController {
#PutMapping(value = "/deactive/{id}")
public HttpEntity<UpdateResult> deactive(#PathVariable #CustomerGroupEmpty String id) {
}
}
My custom validator:
public class CustomerGroupEmptyValidator implements ConstraintValidator<CustomerGroupEmpty, String>{
#Autowired
private CustomerRepository repository;
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
// NullPointerException here (repository == null)
if (value!=null && !repository.existsByCustomerGroup(value)) {
return false;
}
return true;
}
}
My Custom Annotation:
#Documented
#Constraint(validatedBy = CustomerGroupEmptyValidator.class)
#Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
public #interface CustomerGroupEmpty {
String message() default "The customer group is not empty.";
Class<?>[] groups() default {};
Class<? extends Payload> [] payload() default {};
}
code in this post is correct, only mistake is that validator need to override initialize method as well. Probably user123 incorrect configure repository bean, the simply way to check this is define it manually in configuration class

How to retrieve attribute from ControllerAdvice selector in ControllerAdvice class

I can define a Spring ControllerAdvice that is selectively used by a subset of controllers using a custom annotation:
#RestController
#UseAdviceA
#RequestMapping("/myapi")
class ApiController {
...
}
#ControllerAdvice(annotations = UseAdviceA.class)
class AdviceA {
...
}
But is it possible to pass in an attribute via the custom annotation where the advice class can pick up from the annotation? For e.g.:
#RestController
#UseAdviceA("my.value")
#RequestMapping("/myapi")
class ApiController {
...
}
#ControllerAdvice(annotations = UseAdviceA.class)
class AdviceA {
// Some way to get the string "myvalue" from the instance of UseAdviceA
...
}
Any other way to achieve the same outcome, which is to be able to define a custom configuration at the Controller method which can be passed to the ControllerAdvice would be much appreciated too.
Here is a solution.
Given
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface UseAdviceA {
public String myValue();
}
Controller
#RestController
#UseAdviceA(myValue = "ApiController")
#RequestMapping("/myapi")
class ApiController {
...
}
Your Controller Advice should be like
#ControllerAdvice(annotations = {UseAdviceA.class})
class AdviceA {
#ExceptionHandler({SomeException.class})
public ResponseEntity<String> handleSomeException(SomeException pe, HandlerMethod handlerMethod) {
String value = handlerMethod.getMethod().getDeclaringClass().getAnnotation(UseAdviceA.class).myValue();
//value will be ApiController
return new ResponseEntity<>("SomeString", HttpStatus.BAD_REQUEST);
}

Will I have any collisions coming from the #ControllerAdvice if I use it to populate a #ModelAttribute

This is the main controller for the web entrypoint
#Controller
#RequestMapping("/webapp")
public class WebAppController {
#RequestMapping(value = "/home/{authKey}",method = RequestMethod.GET)
String index(#ModelAttribute MyMeta myMeta, Model model){
System.out.println("Token: "+myMeta.getAccessToken());
return "index";
}
#RequestMapping(value = "/config/{authKey}",method = RequestMethod.GET)
String config(#ModelAttribute MyMeta myMeta, Model model){
return "configure";
}
}
Now if you look at the interceptor you can see how I am creating the #ModelAttribute, and see the implementation
#Component
#ControllerAdvice
public class SessionInterceptor implements AsyncHandlerInterceptor {
MyMeta myMeta;
...
#ModelAttribute
public MyMeta getTest() {
return this.myMeta;
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
...
// parse the key from the request
...
MetaMagicKey metaMagicKey = metaMagicKeyRepo.findKeyByMagicKey(key);
// do work here query my DB and build stuff
...
// assign the queried data built into object
this.myMeta = metaMagicKey.getId().getMyMeta();
return true;
}
My question is, I do not know the true inter-workings of Springboot so I am worried if too many people execute this I might have some object swapping, or some kind of collision? There really isn't a clean way to do this and all of the research I've done is torn between using HttpServletRequest#setAttribute() and using #ModelAttribute, I like the route I chose above as it's VERY easy to implement in my methods.
Springboot 1.4.2 - Java 8
EDIT:
What I ended up trying is this, based on several pages I've read.
I created a new component:
#Component
#RequestScope
public class HWRequest implements Serializable {
private MyMeta myMeta;
public MyMeta getMyMeta() {
return myMeta;
}
public void setMyMeta(MyMeta myMeta) {
this.myMeta = myMeta;
}
}
And then My Config class
#Configuration
public class AppConfig extends WebMvcConfigurerAdapter {
UserSessionInterceptor userSessionInterceptor;
#Autowired
public AppConfig(UserSessionInterceptor userSessionInterceptor) {
this.userSessionInterceptor = userSessionInterceptor;
}
#Bean
#RequestScope
public HWRequest hwRequest() {
return new HWRequest();
}
#Bean
public UserSessionInterceptor createUserSessionInterceptor() {
return userSessionInterceptor;
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(createUserSessionInterceptor()).addPathPatterns("/user/**");
}
}
And here is the interceptor I modified
#Component
#ControllerAdvice
public class SessionInterceptor implements AsyncHandlerInterceptor {
#Resource
HWRequest hwRequest;
...
#ModelAttribute
public HWRequest getTest() {
return this.hwRequest;
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
...
// parse the key from the request
...
MetaMagicKey metaMagicKey = metaMagicKeyRepo.findKeyByMagicKey(key);
// do work here query my DB and build stuff
...
// assign the queried data built into object
this.hwRequest.setMyMeta(metaMagicKey.getId().getMyMeta());
return true;
}
And of course the modified controller to fit my needs
#Controller
#RequestMapping("/user")
public class WebAppUserController {
#RequestMapping(value = "/home/{authKey}",method = RequestMethod.GET)
String index(#ModelAttribute HWRequest request, Model model){
return "index";
}
#RequestMapping(value = "/config/{authKey}",method = RequestMethod.GET)
String config(#ModelAttribute HWRequest request, Model model){
return "configure";
}
}
Based on all of the documentation I've read this should work, but maybe I am missing something as the interceptor is STILL a singleton. Maybe I am missing something?
myMeta variable represents state in singleton bean. Of course it is not thread-safe and various users will get collisions. Do not ever store any of your application state in singleton beans.
If you want to store some state per request, use Spring's request scope. That means creating separate bean just for storing state annotated with #RequestScope annotation
Reaction on EDIT:
This bean registration can be deleted as it is already registered into Spring IoC container with #Component annotation:
#Bean
#RequestScope
public HWRequest hwRequest() {
return new HWRequest();
}
Another piece that is not needed in your AppConfig is autowiring UserSessionInterceptor bean and registering it as bean again. Delete that. As that bean is being autowired it obviously already is in IoC container, so no need to register it again.
Another confusing piece is workd session in naming. As you are dealing with #RequestScope instead of #SessionScope I would advise to change naming of your class to request (e.g. RequestInterceptor). Session vs Request are very different beasts.
Otherwise it looks like it can work and should be thread safe.

Categories

Resources