I'm trying to create a custom annotation for local variables that pretty much do the same thing as #NotNull #NotEmpty and #NotBlank. I can't use those annotations since they don't apply for local variables so I decided to make my own annotation. Here's the annotation:
import controllers.validation.validator.NotNullOrEmptyValidator;
import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.ReportAsSingleViolation;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
#Target(LOCAL_VARIABLE)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Constraint(validatedBy = NotNullOrEmptyValidator.class)
#ReportAsSingleViolation
public #interface NotNullOrEmpty {
String message() default "something is wrong!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
#Target(LOCAL_VARIABLE)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#interface List {
NotNullOrEmpty[] value();
}
}
And here's the validator:
import controllers.validation.constraints.NotNullOrEmpty;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class NotNullOrEmptyValidator implements ConstraintValidator<NotNullOrEmpty, Object> {
#Override
public void initialize(NotNullOrEmpty constraintAnnotation) {
}
#Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
return value!=null && !value.toString().isEmpty();
}
}
But for some reason when I try to use it like this:
public class Foo {
public void doSomething(HttpServletRequest request) {
#NotNullOrEmpty(message = "headerpiece cannot be empty or null.")
String headerPiece = request.get("something");
}
}
It just doesn't fire. I've tried passing in that something value in the header with an empty string and tried not passing it in at all but the annotation just doesn't fire and neither does the validator. What am I doing wrong here?
Make sure it is deployed as a webapp. In my case it is not firing for test cases but gets fired in actual web application.
Related
I'm trying to make my own getter annotation, as it is done in lombok:
package testAnns.anns;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target({ElementType.FIELD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface Get {
Access value() default Access.PUBLIC;
Value[] method() default {};
#Retention(RetentionPolicy.RUNTIME)
#Target({})
#interface Value {}
}
Tried to specify:
package testAnns;
import testAnns.anns.Get;
public class TestAnns {
#Get public int l = 10;
}
I try to call:
public static void main(String[] args) {
TestAnns ann = new TestAnns();
System.out.println(ann.getL()); // error
}
Nothing works.
What to do, how to be, why does not work?
Connoisseurs, please help me figure it out, I don’t understand why it doesn’t work ...
I am looking for annotation to annotate pojo class which I need to validate during request deserialization. I am searching for annotation to pass as parameter class which will validate my pojo.
Implementation can look like that:
#ValidateAnnotation(class = ExampleClassValidator.class)
public class ExampleClass {
private String name;
}
Has anyone know any of spring annotation for that approach or some dependency which offer that declarative validation ? I am asking because I cannot find any similar solution in documentation.
You can use #InitBinder to configure a validator based on the target of the method. Here's a simple example:
Annotation class:
package test.xyz;
import org.springframework.validation.Validator;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
#Retention(RetentionPolicy.RUNTIME)
public #interface ValidateAnnotation {
Class<? extends Validator> value();
}
The example class to be validated:
package test.xyz;
#ValidateAnnotation(ExampleClassValidator.class)
public class ExampleClass {
}
The validator class:
package test.xyz;
import org.springframework.validation.Errors;
public class ExampleClassValidator implements org.springframework.validation.Validator {
#Override
public boolean supports(Class<?> aClass) {
return ExampleClass.class.isAssignableFrom(aClass);
}
#Override
public void validate(Object o, Errors errors) {
}
}
And finally the controller class with the #InitBinder definition:
import org.springframework.stereotype.Controller;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import test.xyz.ExampleClass;
import test.xyz.ValidateAnnotation;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.Collections;
#Controller
public class ExampleController {
#RequestMapping(value="test-endpoint", method= RequestMethod.GET)
public #ResponseBody
Object testMethod(#Valid ExampleClass exampleClass, Errors errors) {
return Collections.singletonMap("success", true);
}
#InitBinder
public void initBinder(WebDataBinder binder, HttpServletRequest request) throws IllegalAccessException, InstantiationException {
Class<?> targetClass = binder.getTarget().getClass();
if(targetClass.isAnnotationPresent(ValidateAnnotation.class)) {
ValidateAnnotation annotation = targetClass.getAnnotation(ValidateAnnotation.class);
Class<? extends Validator> value = annotation.value();
Validator validator = value.newInstance();
binder.setValidator(validator);
}
}
}
Explanation:
You can use the WebDataBinder's getTarget method to access the target to be validated. From there it is straightforward to check the annotation on the class, get the validator class, and set it on the binder. I believe you can also use the #ControllerAdvice annotation to configure a global InitBinder. As a disclaimer, I don't know if it is recommended to access the binder target within the InitBinder, but I haven't had any issues the few times I've done so.
For normal validation you can annotate your class with the annotations from the javax.validation.constraints package, like javax.validation.constraints.NotEmpty. For custom validation, you can make your own annotation that will call a custom validator that you write.
For example, if you wanted to create a validator that makes sure a field is nine characters long you could do the following:
First, create your custom validation annotation.
#Documented
#Constraint(validatedBy = NineCharactersValidator.class)
#Target( { ElementType.METHOD, ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface NineCharactersOnly {
String message() default "This field must contain exactly nine characters";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Next, create your custom validator:
public class NineCharactersValidator implements ConstraintValidator<NineCharactersOnly, String> {
#Override
public void initialize(NineCharactersOnly contactNumber) {
}
#Override
public boolean isValid(String contactField, ConstraintValidatorContext cxt) {
return contactField != null && contactField.length() == 9;
}
}
Next, use the annotation on fields that need to be constrained on your pojo.
public class ExampleClass {
#NineCharactersOnly
private String fieldThatMustBeNineCharacters;
}
Next, mark your method parameters in the controller with #Valid so they will be validated by Spring:
#RestController
public class CustomValidationController {
#PostMapping("/customValidationPost")
public ResponseEntity<String> customValidation(#Valid ExampleClass exampleClass, BindingResult result, Model m) {
// we know the data is valid if we get this far because Spring automatically validates the input and
// throws a MethodArgumentNotValidException if it's invalid and returns an HTTP response of 400 (Bad Request).
return ResponseEntity.ok("Data is valid");
}
}
Finally, if you want custom logic for handling validation errors instead of just sending a 400, you can create a custom validation handler method.
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(MethodArgumentNotValidException.class)
public Map<String, String> handleValidationException(MethodArgumentNotValidException e) {
Map<String, String> errors = new HashMap<>();
d.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return errors;
}
Maybe writing your custom annotation and using Spring AOP will help you. Spring AOP is quite simple.
I found pretty good solution but in one place i used reflection :(
Please feel free to comment and rate this solution, is it good enough or something could be done better.
I had create own annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface Validator {
Class<? extends org.springframework.validation.Validator> validator();
}
I next step I extend LocalValidatorFactoryBean to override validate method and here I was forced to use reflection to get class from annotation.
#Component
#RequiredArgsConstructor
class CustomLocalValidatorFactoryBean extends LocalValidatorFactoryBean {
private final Map<Class<? extends Validator>, Validator> validators;
#Override
public void validate(Object target, Errors errors, Object... validationHints) {
Class<? extends Validator> validatorKey = target.getClass().getAnnotation(com.validation.validator.Validator.class).validator();
Optional.ofNullable(validators.get(validatorKey)).ifPresentOrElse(
validator ->
validator.validate(target, errors),
() -> super.validate(target, errors, validationHints)
);
}
}
I annotate pojo with my annotation to specify validator.
#Data
#Validator(validator = PersonValidator.class)
public class PersonDto {
private final String name;
private final String surname;
private final Integer age;
}
As you can see in my CustomLocalValidatorFactoryBean I injected a map of validators, in this map i store validators assigned to key which is the class of this validator. This class i specify in annotation in pojo to fetch suitable validator for currect validate target. And this is my configuration of validatos map.
#Configuration
class ValidatorConfig {
#Bean
Map<Class<? extends Validator>, Validator> validators() {
var validators = new HashMap<Class<? extends Validator>, Validator>();
validators.put(PersonValidator.class, new PersonValidator());
return validators;
}
}
I specify custom #RestControllerAdvice and override method handleMethodArgumentNotValid.
#RestControllerAdvice
class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = error.getCode();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}
}
And this is my validator, it could be bean with dao acces but it could also be simple pojo.
public class PersonValidator implements Validator {
#Override
public boolean supports(Class<?> aClass) {
return PersonDto.class.isAssignableFrom(aClass);
}
#Override
public void validate(Object object, Errors errors) {
Optional.of(object).map(obj -> (PersonDto) obj).ifPresent(person -> {
Optional.ofNullable(person.getName())
.filter(name -> Strings.isNotBlank(name) && name.length() >= 3)
.ifPresentOrElse(name -> doNothing(), () -> errors.reject("person.name", "name of person is invalid!"));
});
}
}
What do you think about that configuration, is it cannon on sparrow or you just like that solution ?
I have a validation in my application that uses cookies (request.getCookies) to validate a captcha code.
I want to create a ConstraintValidator for this captcha validation, so it is validated alongside the other bean's attributes - as specified by JSR-303 Bean Validation.
Is there a way to retrieve the HttpServletRequest in a ConstraintValidator?
Assuming (due to present tag) that you are having Spring, with the recent version of one (>=2.5.1), this should be as simple as
package org.yourapp.controller.validation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class YourValidator implements ConstraintValidator<YourValidatorAnnotaion, String> {
// here should be autowired a proxy to currently undergoing request
#Autowired
private HttpServletRequest request;
#Override
public void initialize(YourValidatorAnnotaion constraintAnnotation) {
// this should autowire all dependencies of this class using
// current application context
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
// here goes your custom logic for validation, like matching
// captcha challenge to captcha response, but i am not doing
// this for you, as i don't know what it supposed to be, so
// i simply test presence of cookies.
return request.getCookies().length > 0;
}
}
For the completness, here is a sample #YourValidatorAnnotaion implementation:
package org.yourapp.controller.validation;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
#Constraint(validatedBy = YourValidator.class)
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Retention(RUNTIME)
public #interface YourValidatorAnnotaion {
String message() default "No cookies - no validation";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Thats it. Now if you annotate your DTO field with #YourValidatorAnnotaion, you should get an error whenever controller with such #Valid #RequestBody argument will be called without cookies in request header.
I'm struggling a bit with understanding how rest interceptor annotations can add different values that are later visible in the filter. Given the code below I would expect that once in the filter the permissions values would have foo and bar in them, however they are empty. Any help would be greatly appreciated.
Annotation
package edu.psu.swe.fortress.poc.interceptor;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.enterprise.util.Nonbinding;
import javax.ws.rs.NameBinding;
#NameBinding
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(value=RetentionPolicy.RUNTIME)
public #interface FortressProtected
{
#Nonbinding String[] permissions() default {};
}
Filter
package edu.psu.swe.fortress.poc.interceptor;
import java.io.IOException;
import java.lang.annotation.Annotation;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.ext.Provider;
#Provider
#FortressProtected
public class FortressAuthorizer implements ContainerRequestFilter
{
#Override
public void filter(ContainerRequestContext requestContext) throws IOException
{
System.out.println("In the interceptor");
Class<?> clazz = this.getClass();
FortressProtected annotation = clazz.getAnnotation(edu.psu.swe.fortress.poc.interceptor.FortressProtected.class);
System.out.println("Annotation? " + clazz.isAnnotation());
for (Annotation a : clazz.getAnnotations())
{
System.out.println(a);
}
for (String s : annotation.permissions())
{
System.out.println(s);
}
}
}
App config
package edu.psu.swe.fortress.poc.rest;
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
import edu.psu.swe.fortress.poc.interceptor.FortressAuthorizer;
import edu.psu.swe.fortress.poc.interceptor.FortressProtected;
#ApplicationPath("")
public class FortressTestApp extends Application
{
private Set<Class<?>> clazzez_ = new HashSet<>();
{
clazzez_.add(ResourceImpl.class);
clazzez_.add(FortressProtected.class);
clazzez_.add(FortressAuthorizer.class);
}
public Set<Class<?>> getClasses()
{
return clazzez_;
}
}
Resource class
package edu.psu.swe.fortress.poc.rest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import edu.psu.swe.fortress.poc.interceptor.FortressProtected;
#FortressProtected(permissions={"foo", "bar"})
#Path("tests")
public class ResourceImpl
{
#GET
#Produces("application/text")
public String getHello()
{
FortressProtected annotation = this.getClass().getAnnotation(edu.psu.swe.fortress.poc.interceptor.FortressProtected.class);
System.out.println(annotation.toString());
return "hello";
}
}
Log output looks like:
15:59:55,223 INFO [stdout] (default task-9) #edu.psu.swe.fortress.poc.interceptor.FortressProtected(permissions=[])
15:59:55,229 INFO [stdout] (default task-9) #edu.psu.swe.fortress.poc.interceptor.FortressProtected(permissions=[foo, bar])
Thanks in advance.
Look at this in your filter
Class<?> clazz = this.getClass();
FortressProtected annotation = clazz.getAnnotation(FortressProtected.class);
this.getClass() corresponds to the filter class (whose annotation has no values). You instead need to get the annotation on the ResourceImpl
A couple options. You could explicitly use ResourceImpl.class.getAnnotation(...). But the problem with this is that once you bind more than one class, how do you match which class corresponds to which request. For that reason, the next option is more viable.
What you do is inject ResourceInfo. With this, you can call it's getResourceMethod or getResourceClass methods. These methods return the matched method and class, respectively. You could then check for the annotation at the class level as well as the method level (as we are also allowed to bind at the method level). So you might have something more like:
#Provider
#FortressProtected
public class FortressAuthorizer implements ContainerRequestFilter {
#Context
ResourceInfo resourceInfo;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
Class<?> resourceClass = resourceInfo.getResourceClass();
FortressProtected classAnnot = resourceClass.getAnnotation(FortressProtected.class);
if (classAnnot != null) {
// do something with annotation
}
Method resourceMethod = resourceInfo.getResourceMethod();
FortressProtected methodAnnot = resourceMethod.getAnnotation(FortressProtected.class);
if (methodAnnot != null) {
// do something with annotation
}
}
}
I'm working with AOP in spring:
I have written an annotation
#Retention(RetentionPolicy.RUNTIME)
public #interface TestAnnotation {
}
And I use it on a controller method:
#ResponseBody
#TestAnnotation
#RequestMapping(method = RequestMethod.PUT, value = "/path/{variable}")
public return_type controller_call(#PathVariable String variable) {
return service.methodName(variable);
}
In the advice I have written the following code:
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String methodName = signature.getMethod().getName();
Class<?>[] parameterTypes = signature.getMethod().getParameterTypes();
Annotation[] annotations = joinPoint.getTarget().getClass().getMethod(methodName, parameterTypes).getAnnotations();
This lists the RequestMapping and the ResponseBody annotation but it doesn't list my TestAnnotation.
Any idea why??
For me this works, maybe you are doing something wrong. Probably your sample code does not really reflect your situation. I have replicated this situation in a plain Java + AspectJ setup, merely putting the Spring libs on the classpath, but not running with Spring AOP. It should be the same result with Spring AOP, though, because pointcut matching is just like in native AspectJ.
Sample annotation:
package de.scrum_master.app;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
#Retention(RetentionPolicy.RUNTIME)
public #interface TestAnnotation {}
Sample class with entry point:
package de.scrum_master.app;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
public class Application {
#ResponseBody
#TestAnnotation
#RequestMapping(method = RequestMethod.PUT, value = "/path/{variable}")
public String controller_call(#PathVariable String variable) {
return "dummy value";
}
public static void main(String[] args) {
new Application().controller_call("my/path");
}
}
Aspect with sample pointcut/advice:
package de.scrum_master.aspect;
import java.lang.annotation.Annotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
#Aspect
public class MyAspect {
#Before("execution(!static * *..Application.*(..))")
public void myAdvice(JoinPoint joinPoint) throws Throwable {
System.out.println(joinPoint);
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String methodName = signature.getMethod().getName();
Class<?>[] parameterTypes = signature.getMethod().getParameterTypes();
Annotation[] annotations = joinPoint.getTarget().getClass().getMethod(methodName, parameterTypes).getAnnotations();
for (Annotation annotation : annotations)
System.out.println(annotation);
}
}
Console output:
execution(String de.scrum_master.app.Application.controller_call(String))
#org.springframework.web.bind.annotation.ResponseBody()
#de.scrum_master.app.TestAnnotation()
#org.springframework.web.bind.annotation.RequestMapping(headers=[], name=, value=[/path/{variable}], produces=[], method=[PUT], params=[], consumes=[])
I had the same problem, and the solution was to set ensure that the runtime retention policy is set and the target type is a method.
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface MyAnnotation { }