Spring Boot - Custom validation annotation on form not working - java

I'd like to have an annotation that validates that a MultipartFile is an image. I've created an #interface and a ConstraintValidator, and added the annotation to my field.
Other validation annotations, like #NotEmpty and #Size(min = 0, max = 2) are working fine.
Here is the code in summary. This question has the same problem, but the answer doesn't work for me.
Form.java:
#Validated
public class Form {
#MultipartImage
private MultipartFile image;
...
}
#Interface MultipartImage
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
import static java.lang.annotation.ElementType.METHOD;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
import validation.MultipartFileImageConstraintValidator;
#Documented
#Constraint(validatedBy = { MultipartFileImageConstraintValidator.class })
#Target({ LOCAL_VARIABLE, FIELD, METHOD })
#Retention(RetentionPolicy.RUNTIME)
public #interface MultipartImage {
String message() default "{MultipartImage.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
The validator, MultipartFileConstraintValidator.java
import java.io.IOException;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.springframework.web.multipart.MultipartFile;
public class MultipartFileConstraintValidator implements ConstraintValidator<MultipartImage, MultipartFile> {
#Override
public void initialize(final MultipartImage constraintAnnotation) {
}
#Override
public boolean isValid(final MultipartFile file, final ConstraintValidatorContext context) {
return false;
}
Here's the form submit method in the controller
#RequestMapping(value = "/formsubmit", method = RequestMethod.POST)
public ModelAndView handleForm(#Validated final Form form,
final BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
...
// returns the model
}
}
Validator set up in the #Configuration file, see https://stackoverflow.com/a/21965098/4161471
#Configuration
#ConfigurationProperties("static")
#AutoConfigureAfter(DispatcherServletAutoConfiguration.class)
public class StaticResourceConfig extends WebMvcConfigurerAdapter {
...
#Bean(name = "validator")
public LocalValidatorFactoryBean validator() {
LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
bean.setValidationMessageSource(messageSource());
return bean;
}
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
final MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
methodValidationPostProcessor.setValidator(validator());
return methodValidationPostProcessor;
}
#Override
public Validator getValidator() {
return validator();
}
#Bean
public ReloadableResourceBundleMessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
// Load files containing message keys.
// Order matters. The first files override later files.
messageSource.setBasenames(//
// load messages and ValidationMessages from a folder relative to the jar
"file:locale/messages", //
"file:locale/ValidationMessages", //
// load from within the jar
"classpath:locale/messages", //
"classpath:locale/ValidationMessages" //
);
messageSource.getBasenameSet();
messageSource.setCacheSeconds(10); // reload messages every 10 seconds
return messageSource;
}
}

There was information missing from my original code, specifically regarding the controller, where an additional validator is defined and bound. It uses the wrong method to include the validator FormValidator, and overrides the annotation validations.
binder.setValidator(formValidator) overrides any other validator. Instead binder.addValidators(formValidator) should be used!
#Controller
public class FormController {
#Autowired
final private FormValidator formValidator;
#InitBinder("form")
protected void initBinder(WebDataBinder binder) {
// correct
binder.addValidators(formValidator);
// wrong
//binder.setValidator(formValidator);
}
...
#RequestMapping(value = "/formsubmit", method = RequestMethod.POST)
public ModelAndView handleForm(#Validated final Form form, final BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
...
// returns the model
}
...
}
}
I have also removed the Bean MethodValidationPostProcessor in the #Configuration class.

Related

Custom declarative spring pojo validation during deserialization of request

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 ?

springboot logging with aspectj getting IllegalArgumentException: error at ::0 formal unbound in pointcut

I want to create an aspectJ component within springboot project, that prints log messages wherever #Loggable annotation is present, methods or class, or maybe both (method will be considered).
Loggable annotation:
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.TYPE, ElementType.METHOD })
public #interface Loggable {
boolean duration() default false;
}
Aspectj class:
#Aspect
#Component
public class LogInterceptorAspect {
#Pointcut("execution(public * ((#Loggable *)+).*(..)) && within(#Loggable *)")
public boolean loggableDefinition(Loggable loggable) {
return loggable.duration();
}
#Around("loggableDefinition(withDuration)")
public void log(ProceedingJoinPoint joinPoint, boolean withDuration) throws Throwable {
getLogger(joinPoint).info("start {}", joinPoint.getSignature().getName());
StopWatch sw = new StopWatch();
Object returnVal = null;
try {
sw.start();
returnVal = joinPoint.proceed();
} finally {
sw.stop();
}
getLogger(joinPoint).info("return value: {}, duration: {}", returnVal, sw.getTotalTimeMillis()));
}
private Logger getLogger(JoinPoint joinPoint) {
return LoggerFactory.getLogger(joinPoint.getSignature().getDeclaringType());
}
}
With the above code I get
java.lang.IllegalArgumentException: error at ::0 formal unbound in pointcut
What's wrong?
Basically the formal parameter is unbound on the PointCut.
Here is an alternate working example based on the approach detailed in this post: #AspectJ Class level Annotation Advice with Annotation as method argument
I modified your approach slightly to avoid the problem for a couple of reasons :
simplified the initial PointCut and gave it a single responsibility
gave it a descriptive name indicating its purpose
made it more reusable by removing the dependency on Loggable
kept it close in implementation to most of the sample documentation available
broke the Advice into two simpler methods each with a single responsibility that is easy to comprehend
simplified the expressions by removing fancy operators
injected the annotation directly into the Advice where its used rather than attempting to pass from the PointCut which feels like an unnecessary complexity
kept it close in implementation to most of the sample documentation available
added the start of a unit test to verify the expected behavior so that changes can be made responsibly to the PointCut and Advice expressions (you should complete it)
When working with PointCut/Advice Expressions, I generally try to go for the simplest, clearest solutions possible and unit test them thoroughly to ensure the behavior I expect is what I get. The next person to look at your code will appreciate it.
Hope this helps.
package com.spring.aspects;
import static org.junit.Assert.assertEquals;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.StopWatch;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = AspectInjectAnnotationTest.TestContext.class)
public class AspectInjectAnnotationTest {
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.TYPE, ElementType.METHOD })
public #interface Loggable {
boolean duration() default false;
}
#Aspect
public static class LogInterceptorAspect {
#Pointcut("execution(public * *(..))")
public void anyPublicMethod() {
}
#Around("anyPublicMethod() && #annotation(loggable)")
public Object aroundLoggableMethods(ProceedingJoinPoint joinPoint, Loggable loggable) throws Throwable {
return log(joinPoint, loggable);
}
#Around("(anyPublicMethod() && !#annotation(AspectInjectAnnotationTest.Loggable)) && #within(loggable)")
public Object aroundPublicMethodsOnLoggableClasses(ProceedingJoinPoint joinPoint, Loggable loggable)
throws Throwable {
return log(joinPoint, loggable);
}
public Object log(ProceedingJoinPoint joinPoint, Loggable loggable) throws Throwable {
getLogger(joinPoint).info("start [{}], duration [{}]", joinPoint.getSignature().getName(),
loggable.duration());
StopWatch sw = new StopWatch();
Object returnVal = null;
try {
sw.start();
returnVal = joinPoint.proceed();
} finally {
sw.stop();
}
getLogger(joinPoint).info("return value: [{}], duration: [{}]", returnVal, sw.getTotalTimeMillis());
return returnVal;
}
private Logger getLogger(JoinPoint joinPoint) {
return LoggerFactory.getLogger(joinPoint.getSignature().getDeclaringType());
}
}
// class level annotation - should only proxy public methods
#Loggable(duration = true)
public static class Service1 {
// public - should be proxied
public String testS1M1(String test) {
return testProtectedM(test);
}
// public - should be proxied
public String testS1M2(String test) {
return testProtectedM(test);
}
// protected - should not be proxied
protected String testProtectedM(String test) {
return testPrivateM(test);
}
// private - should not be proxied
private String testPrivateM(String test) {
return test;
}
}
// no annotation - class uses method level
public static class Service2 {
#Loggable
public String testS2M1(String test) {
return protectedMethod(test);
}
// no annotation - should not be proxied
public String testS2M2(String test) {
return protectedMethod(test);
}
// protected - should not be proxied
protected String protectedMethod(String test) {
return testPrivate(test);
}
// private - should not be proxied
private String testPrivate(String test) {
return test;
}
}
// annotation - class and method level - make sure only call once
#Loggable
public static class Service3 {
#Loggable
public String testS3M1(String test) {
return test;
}
}
// context configuration for the test class
#Configuration
#EnableAspectJAutoProxy
public static class TestContext {
// configure the aspect
#Bean
public LogInterceptorAspect loggingAspect() {
return new LogInterceptorAspect();
}
// configure a proxied beans
#Bean
public Service1 service1() {
return new Service1();
}
// configure a proxied bean
#Bean
public Service2 service2() {
return new Service2();
}
// configure a proxied bean
#Bean
public Service3 service3() {
return new Service3();
}
}
#Autowired
private Service1 service1;
#Autowired
private Service2 service2;
#Autowired
private Service3 service3;
#Test
public void aspectShouldLogAsExpected() {
// observe the output in the log, but craft this into specific
// unit tests to assert the behavior you are expecting.
assertEquals("service-1-method-1", service1.testS1M1("service-1-method-1")); // expect logging
assertEquals("service-1-method-2", service1.testS1M2("service-1-method-2")); // expect logging
assertEquals("service-2-method-1", service2.testS2M1("service-2-method-1")); // expect logging
assertEquals("service-2-method-2", service2.testS2M2("service-2-method-2")); // expect no logging
assertEquals("service-3-method-1", service3.testS3M1("service-3-method-1")); // expect logging once
}
}

Custom validation for RequestParam doesn't work with Spring MVC

I can't make custom validation working with Spring MVC. I implemented own annotation for parameter and custom validator for it (all is given below), but validation never happens. Any ideas would be really appreciated.
Controller
#Validated
#RestController
public class FooController {
#RequestMapping(value = "/somepath",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.OK)
public String get(#CustomParam #RequestParam(String fooParam) {
return "Hello";
}
}
Custom Request Parameter
#Documented
#Constraint(validatedBy = CustomValidator.class)
#Target({ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
public #interface CustomParam {
String message() default "Wrong!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Custom Validator
#Component
public class CustomValidator implements ConstraintValidator<CustomParam, String> {
#Override
public void initialize(CustomParam param) {}
#Override
public boolean isValid(String givenParam, ConstraintValidatorContext context) {
// some custom validation is here, never enter this method though
}
}
Seems I got what is wrong here. Because I use Spring Boot, it uses hibernate validator by default. To fix this, I followed this answer and just changed my Spring configuration with adding the beans.
#Bean
public Validator validator() {
return new LocalValidatorFactoryBean();
}
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
methodValidationPostProcessor.setValidator(validator());
return methodValidationPostProcessor;
}

Use #Validated and #Valid with spring validator

I have a java bean being used to send JSON messages to a spring #RestController and I have bean validation setup and running just fine using #Valid. But I want to move to Protobuf/Thrift and move away from REST. It is an internal API and a lot of big companies have done away with REST internally. What this really means is that I no longer have control of the message objects - they are generated externally. I can't put annotations on them anymore.
So now my validation has to be programmatic. How do I do this? I have coded up a Validator and it works just great. But it doesn't use the nice #Valid annotation. I have to do the following:
#Service
public StuffEndpoint implements StuffThriftDef.Iface {
#Autowired
private MyValidator myValidator;
public void things(MyMessage msg) throws BindException {
BindingResult errors = new BeanPropertyBindingResult(msg, msg.getClass().getName());
errors = myValidator.validate(msg);
if (errors.hasErrors()) {
throw new BindException(errors);
} else {
doRealWork();
}
}
}
This stinks. I have to do this in every single method. Now, I can put a lot of that into one method that throws BindException and that makes it one line of code to add to every method. But that's still not great.
What I want is to see it look like this:
#Service
#Validated
public StuffEndpoint implements StuffThriftDef.Iface {
public void things(#Valid MyMessage msg) {
doRealWork();
}
}
And still get the same result. Remember, my bean has no annotations. And yes, I know I can use the #InitBinder annotation on a method. But that only works for web requests.
I don't mind injecting the correct Validator into this class, but I would prefer if my ValidatorFactory could pull the correct one based on the supports() method.
Is this possible? Is there a way to configure bean validation to actually use Spring validation instead? Do I have to hijack a Aspect somewhere? Hack into the LocalValidatorFactory or the MethodValidationPostProcessor?
Thanks.
Its pretty complicated thing to combine Spring validation and JSR-303 constrains. And there is no 'ready to use' way. The main inconvenience is that Spring validation uses BindingResult, and JSR-303 uses ConstraintValidatorContext as result of validation.
You can try to make your own validation engine, using Spring AOP. Let's consider, what we need to do for it. First of all, declare AOP dependencies (if you didn't yet):
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.8</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.8</version>
</dependency>
I'm using Spring of version 4.2.4.RELEASE, but of cause you can use your own. AspectJ needed for use aspect annotation. Next step, we have to create simple validator registry:
public class CustomValidatorRegistry {
private List<Validator> validatorList = new ArrayList<>();
public void addValidator(Validator validator){
validatorList.add(validator);
}
public List<Validator> getValidatorsForObject(Object o) {
List<Validator> result = new ArrayList<>();
for(Validator validator : validatorList){
if(validator.supports(o.getClass())){
result.add(validator);
}
}
return result;
}
}
As you see it is very simple class, which allow us to find validator for object. Now lets create annotation, that will be mark methods, that need to be validated:
package com.mydomain.validation;
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface CustomValidation {
}
Because of standard BindingException class is not RuntimeException, we can't use it in overriden methods. This means we need define our own exception:
public class CustomValidatorException extends RuntimeException {
private BindingResult bindingResult;
public CustomValidatorException(BindingResult bindingResult){
this.bindingResult = bindingResult;
}
public BindingResult getBindingResult() {
return bindingResult;
}
}
Now we are ready to create an aspect that will do most of the work. Aspect will execute before methods, which marked with CustomValidation annotation:
#Aspect
#Component
public class CustomValidatingAspect {
#Autowired
private CustomValidatorRegistry registry; //aspect will use our validator registry
#Before(value = "execution(public * *(..)) && annotation(com.mydomain.validation.CustomValidation)")
public void doBefore(JoinPoint point){
Annotation[][] paramAnnotations =
((MethodSignature)point.getSignature()).getMethod().getParameterAnnotations();
for(int i=0; i<paramAnnotations.length; i++){
for(Annotation annotation : paramAnnotations[i]){
//checking for standard org.springframework.validation.annotation.Validated
if(annotation.annotationType() == Validated.class){
Object arg = point.getArgs()[i];
if(arg==null) continue;
validate(arg);
}
}
}
}
private void validate(Object arg) {
List<Validator> validatorList = registry.getValidatorsForObject(arg);
for(Validator validator : validatorList){
BindingResult errors = new BeanPropertyBindingResult(arg, arg.getClass().getSimpleName());
validator.validate(arg, errors);
if(errors.hasErrors()){
throw new CustomValidatorException(errors);
}
}
}
}
execution(public * *(..)) && #annotation(com.springapp.mvc.validators.CustomValidation) means, that this aspect will applied to any public methods of beans, which marked with #CustomValidation annotation. Also note, that to mark validated parameters we are using standard org.springframework.validation.annotation.Validated annotation. But of cause we could make our custom. I think other code of aspect is very simple and does not need any comments. Further code of example validator:
public class PersonValidator implements Validator {
#Override
public boolean supports(Class<?> aClass) {
return aClass==Person.class;
}
#Override
public void validate(Object o, Errors errors) {
Person person = (Person)o;
if(person.getAge()<=0){
errors.rejectValue("age", "Age is too small");
}
}
}
Now we have make tune the configuration and all ready to use:
#Configuration
#ComponentScan(basePackages = "com.mydomain")
#EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig{
.....
#Bean
public CustomValidatorRegistry validatorRegistry(){
CustomValidatorRegistry registry = new CustomValidatorRegistry();
registry.addValidator(new PersonValidator());
return registry;
}
}
Note, proxyTargetClass is true because we will use cglib class proxy.
Example of target method in service class:
#Service
public class PersonService{
#CustomValidation
public void savePerson(#Validated Person person){
....
}
}
Because of #CustomValidation annotation aspect will be applied, and because of #Validated annotation person will be validated. And example of usage of service in controller(or any other class):
#Controller
public class PersonConroller{
#Autowired
private PersonService service;
public String savePerson(#ModelAttribute Person person, ModelMap model){
try{
service.savePerson(person);
}catch(CustomValidatorException e){
model.addAttribute("errors", e.getBindingResult());
return "viewname";
}
return "viewname";
}
}
Keep in mind, that if you will invoke #CustomValidation from methods of PersonService class, validation will not work. Because it will invoke methods of original class, but not proxy. This means, that you can invoke this methods only from outside of class (from other classes), if you want validation to be working (eg #Transactional works same way).
Sorry for long post. My answer is not about 'simple declarative way', and possible you will do not need it. But I was curious resolve this problem.
I marked #Ken's answer as correct because it is. But I have taken it a little further and wanted to post what I have made. I hope anybody coming to this page will find it interesting. I might try to get it in front of the Spring folks to see if it might be something included in future releases.
The idea is to have a new annotation to replace #Valid. So I called it #SpringValid. Using this annotation would kick off the system put together above. Here are all the pieces:
SpringValid.java
package org.springframework.validation.annotation;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
#Target({METHOD, FIELD, CONSTRUCTOR, PARAMETER})
#Retention(RUNTIME)
public #interface SpringValid {
}
SpringValidationAspect.java
package org.springframework.validation;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
#Aspect
#Component
public class SpringValidationAspect {
private SpringValidatorRegistry springValidatorRegistry;
#Autowired
public SpringValidationAspect(final SpringValidatorRegistry springValidatorRegistry) {
this.springValidatorRegistry = springValidatorRegistry;
}
public SpringValidatorRegistry getSpringValidatorRegistry() {
return springValidatorRegistry;
}
#Before("#target(org.springframework.validation.annotation.Validated) "
+ "&& execution(public * *(#org.springframework.validation.annotation.SpringValid (*), ..)) "
+ "&& args(validationTarget)")
public void beforeMethodThatNeedsValidation(Object validationTarget) {
validate(validationTarget);
}
private void validate(Object arg) {
List<Validator> validatorList = springValidatorRegistry.getValidatorsForObject(arg);
for (Validator validator : validatorList) {
BindingResult errors = new BeanPropertyBindingResult(arg, arg.getClass().getSimpleName());
validator.validate(arg, errors);
if (errors.hasErrors()) {
throw new SpringValidationException(errors);
}
}
}
}
Spring's examples show classes annotated with #Validated so I wanted to keep that. The above aspect only targets classes with #Validated at the class-level. And, just like when you use #Valid, it looks for the #SpringValid annotation stuck to a method parameter.
SpringValidationException.java
package org.springframework.validation;
import org.springframework.validation.BindingResult;
public class SpringValidationException extends RuntimeException {
private static final long serialVersionUID = 1L;
private BindingResult bindingResult;
public SpringValidationException(final BindingResult bindingResult) {
this.bindingResult = bindingResult;
}
public BindingResult getBindingResult() {
return bindingResult;
}
}
SpringValidatorRegistry.java
package org.springframework.validation;
import org.springframework.validation.Validator;
import java.util.ArrayList;
import java.util.List;
public class SpringValidatorRegistry {
private List<Validator> validatorList = new ArrayList<>();
public void addValidator(Validator validator) {
validatorList.add(validator);
}
public List<Validator> getValidatorsForObject(Object o) {
List<Validator> result = new ArrayList<>();
for (Validator validator : validatorList) {
if (validator.supports(o.getClass())) {
result.add(validator);
}
}
return result;
}
}
Just like the first answer, a place to register all classes that implement Spring's org.springframework.validation.Validator interface.
SpringValidator.java
package org.springframework.validation.annotation;
import org.springframework.stereotype.Component;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Component
public #interface SpringValidator {
}
This is just extra sauce to make it easier to register/find Validators. You could register all your Validators by hand, or you could find them via reflection. So this part is not required, I just thought it made things easier.
MyConfig.java
package com.example.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.SpringValidationAspect;
import org.springframework.validation.SpringValidatorRegistry;
import org.springframework.validation.annotation.SpringValidator;
import java.util.Map;
import javax.validation.Validator;
#Configuration
public class MyConfig {
#Autowired
private ApplicationContext applicationContext;
#Bean
public SpringValidatorRegistry validatorRegistry() {
SpringValidatorRegistry registry = new SpringValidatorRegistry();
Map<String, Object> validators =
applicationContext.getBeansWithAnnotation(SpringValidator.class);
validators.values()
.forEach(v -> registry.addValidator((org.springframework.validation.Validator) v));
return registry;
}
#Bean
public SpringValidationAspect springValidationAspect() {
return new SpringValidationAspect(validatorRegistry());
}
}
See, scan your classpath and look for #SpringValidator classes and register them. Then register the Aspect and away you go.
Here is an example of such a Validator:
MyMessageValidator.java
package com.example.validators;
import com.example.messages.MyMessage;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import org.springframework.validation.annotation.SpringValidator;
#SpringValidator
public class MyMessageValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return MyMessage.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmpty(errors, "firstField", "{javax.validation.constraints.NotNull}",
"firstField cannot be null");
MyMessage obj = (MyMessage) target;
if (obj.getSecondField != null && obj.getSecondField > 100) {
errors.rejectField(errors, "secondField", "{javax.validation.constraints.Max}", "secondField is too big");
}
}
}
And here is the service class that uses the #SpringValid annotation:
MyService.java
package com.example.services;
import com.example.messages.MyMessage;
import org.springframework.validation.annotation.SpringValid;
import org.springframework.validation.annotation.Validated;
import javax.inject.Inject;
#Validated
public class MyService {
public String doIt(#SpringValid final MyMessage msg) {
return "we did it!";
}
}
Hope this makes sense for someone at some point. I personally think it is quite useful. A lot of companies are starting to move their internal APIs away from REST and to something like Protobuf or Thrift. You can still use Bean Validation but you have to use XML, and it isn't all that nice. So I hope this will be helpful to people who want to still do programmatic validation.
Hope it helps someone. I've got it working by adding the following configuration:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
#Configuration
public class ValidatorConfiguration {
#Bean
public MethodValidationPostProcessor getMethodValidationPostProcessor(){
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
processor.setValidator(this.validator());
return processor;
}
#Bean
public LocalValidatorFactoryBean validator(){
return new LocalValidatorFactoryBean();
}
}
The service is then annotated the same way (#Validated on the class and #Valid on the parameter) and can be injected into another bean where the method can be called directly and validation happens.

Why does #AuthenticationPrincipal return the Authentication instead of the principal object?

I want to retrieve the current user in my controller methods with the #AuthenticationPrincipal annotation. The docs state the following:
Annotation that binds a method parameter or method return value to the Authentication.getPrincipal().
But in fact I get the Authentication object instead of Authentication.getPrincipal().
This is my simple controller method:
#RequestMapping("/")
public #ResponseBody String index(#AuthenticationPrincipal final WindowsAuthenticationToken user) {
return String.format("Welcome to the home page, %s!", user.getName());
}
WindowsAuthenticationToken implements Authentication. In this implementation getPrincipal returns a WindowsPrincipal.
The controller method above works, but when I change the arguments type to WindowsPrincipal and try to access the website, I get the following error page:
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Tue Mar 03 15:13:52 CET 2015
There was an unexpected error (type=Internal Server Error, status=500).
argument type mismatch HandlerMethod details: Controller [pkg.HomeController] Method [public java.lang.String pkg.HomeController.index(waffle.servlet.WindowsPrincipal)] Resolved arguments: [0] [type=waffle.spring.WindowsAuthenticationToken] [value=waffle.spring.WindowsAuthenticationToken#121a2581]
This is my configuration file:
package pkg;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import waffle.servlet.spi.BasicSecurityFilterProvider;
import waffle.servlet.spi.NegotiateSecurityFilterProvider;
import waffle.servlet.spi.SecurityFilterProvider;
import waffle.servlet.spi.SecurityFilterProviderCollection;
import waffle.spring.NegotiateSecurityFilter;
import waffle.spring.NegotiateSecurityFilterEntryPoint;
import waffle.windows.auth.impl.WindowsAuthProviderImpl;
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private NegotiateSecurityFilterEntryPoint negotiateSecurityFilterEntryPoint;
#Autowired
private NegotiateSecurityFilter waffleNegotiateSecurityFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling().authenticationEntryPoint(negotiateSecurityFilterEntryPoint).and()
.addFilterBefore(waffleNegotiateSecurityFilter, BasicAuthenticationFilter.class).authorizeRequests()
.anyRequest().fullyAuthenticated();
}
#Bean
public WindowsAuthProviderImpl waffleAuthProvider() {
return new WindowsAuthProviderImpl();
}
#Bean
public NegotiateSecurityFilterProvider negotiateSecurityFilterProvider(
final WindowsAuthProviderImpl waffleAuthProvider) {
return new NegotiateSecurityFilterProvider(waffleAuthProvider);
}
#Bean
public BasicSecurityFilterProvider basicSecurityFilterProvider(final WindowsAuthProviderImpl waffleAuthProvider) {
return new BasicSecurityFilterProvider(waffleAuthProvider);
}
#Bean
public SecurityFilterProviderCollection waffleSecurityFilterProviderCollection(
final NegotiateSecurityFilterProvider negotiateSecurityFilterProvider,
final BasicSecurityFilterProvider basicSecurityFilterProvider) {
final SecurityFilterProvider[] providers = { negotiateSecurityFilterProvider, basicSecurityFilterProvider };
return new SecurityFilterProviderCollection(providers);
}
#Bean
public NegotiateSecurityFilterEntryPoint negotiateSecurityFilterEntryPoint(
final SecurityFilterProviderCollection waffleSecurityFilterProviderCollection) {
final NegotiateSecurityFilterEntryPoint entryPoint = new NegotiateSecurityFilterEntryPoint();
entryPoint.setProvider(waffleSecurityFilterProviderCollection);
return entryPoint;
}
#Bean
public NegotiateSecurityFilter waffleNegotiateSecurityFilter(
final SecurityFilterProviderCollection waffleSecurityFilterProviderCollection) {
final NegotiateSecurityFilter filter = new NegotiateSecurityFilter();
filter.setProvider(waffleSecurityFilterProviderCollection);
return filter;
}
}
Why is the behaviour different from how it should be?
My principal object did not implement UserDetails. Because WindowsPrincipal is a class of an external library I could not make any changes to it. In the end I created a new filter that wraps the WindowsPrincipal in a class that implements UserDetails. Now I get the correct principal object using #AuthenticationPrincipal.
It is because your WindowsPrincipal implements Principal. Remove the implements clause and it will work again. I had the same problem and this resolved it.

Categories