How to set validation groups dynamically - java

I have a Spring Boot application where I want to validate a request body object with the #valid annotation.
#PostMapping("update")
public void upateSnippet(#Valid #RequestBody MyDto dto)
This works as expected. But I need to do the validation based on session specific informations. So I created validation groups which should be set dynamically. I don´t want to call the validator manually in the controller. The validation logic should be completely outside of the controller.
So what I´m trying to do is to register a custom MethodValidationInterceptor where I can specify the groups in determineValidationGroups().
public class SessionAwareValidationInterceptor extends MethodValidationInterceptor
{
#Autowired
public SessionAwareValidationInterceptor(Validator validator)
{
super(validator);
}
#Override
protected Class<?>[] determineValidationGroups(MethodInvocation invocation)
{
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpSession session = requestAttributes.getRequest().getSession();
//return groups from session
}
}
In my configuration I register the interceptor as any other bean:
#Bean
public MethodValidationInterceptor methodValidationInterceptor(Validator validator)
{
return new SessionAwareValidationInterceptor(validator);
}
But I have serveral problems with this:
The #Valid annotation works on its own, but the MethodValidationInterceptor is only called when I add a #Validated annotation on the controller class.
But even when I use both annotations combined, Spring does not use my custom interceptor.
How do I register my custom MethodValidationInterceptor? Or is there maybe an other way to register the validation groups dynamically? Plan B would be some custom constraint validators which get the necessary informations out of the session, but I would prefer the group approach.

I figured it out how to do it. I had to create a custom MethodValidationPostProcessor that returns my SessionAwareValidationInterceptor.
public class CustomMethodValidationPostProcessor extends MethodValidationPostProcessor {
#Override
#NotNull
protected Advice createMethodValidationAdvice(Validator validator) {
return validator != null ? new SessionAwareValidationInterceptor(validator) : new SessionAwareValidationInterceptor();
}
}
Then I simply registered it as bean. This way, I was able to use my solution from above. To make it work, I have to use the #Validated annotation on class level and the #Valid annotation on the method parameter.

Related

Can I explicitly call custom validator from service in Spring Boot?

I have a custom validator class that implements Validator, like this:
public class MyCustomValidator implements Validator
I want to be able to call its validate() method from a Service.
This is how this method looks:
#Override
public void validate(Object target, Errors errors) {
// validation goes here
MyClass request = (MyClass) target;
if (request.getId() == null) {
errors.reject("content.id", "Id is missing";
}
}
I don't want to have this validator in my endpoint, because I need to fetch the object to be validated from the database and then call the validation on it, so I need to do it from my service.
Can you please guide me on how to achieve this?
Use validation annotations in class but don't use #Valid on request body, then spring won't validate your class.
public class MyClass{
#NotNull
private Integer id;
#NotBlank
private String data;
}
Autowired Validator first
#Autowired
private final Validator validator;
Then for class validate using the validator conditionally when needed.
if(isValidate) {
Set<ConstraintViolation<MyClass>> violations = validator.validate(myClassObj);
if (!violations.isEmpty()) {
throw new ConstraintViolationException(new HashSet<ConstraintViolation<?>>(violations));
}
}
The Validator interface is, as far as i understand it, called as soon as a matching object (determined by the public boolean Validator.supports(Class clazz) method).
However, your goal seems to be to validate an object of MyClass only at a specific time, coming from your persistence layer to your service layer.
There are multiple ways to achieve this.
The first and most obvious one is to not extend any classes, but to use a custom component with some notion of a validation function:
#Component
public class CustomValidator{
public void validate(MyClass target) throws ValidationException {
// validation goes here
if (target.getId() == null) {
throw new ValidationException("Id is missing");
}
}
}
And inject/autowire it into your service object:
#Component
public class MyClassService{
// will be injected in first instance of this component
#Autowired
private CustomValidator validator
public MyClass get(MyClass target) {
try {
validator.validate(target);
return dao.retrieve(target);
} catch (ValidationException) {
// handle validation error
} catch (DataAccessException) {
// handle dao exception
}
}
}
This has the benefit that you yourself can control the validation, and error handling.
The negative side is the relatively high boilerplate.
However, if you want different Validators for different CRUD-Operations (or Service Methods), you may be interested in the Spring Validation Groups Feature.
First, you create a simple marker interface for each Operation you want to differ:
interface OnCreate {};
interface OnUpdate {};
Then, all you need to do is use the marker interfaces in the fields of your entity class,
using the Bean Validation Annotations:
public class MyClass{
#Null(groups = OnCreate.class)
#NotNull(groups = OnUpdate.class)
String id;
}
In order to use those groups in your Service Class, you will have to use the #Validated annotation.
#Validated
#Service
public class MyService {
#Validated(OnCreate.class)
void validateForCreate(#Valid InputWithGroups input){
// do something
}
#Validated(OnUpdate.class)
void validateForUpdate(#Valid InputWithGroups input){
// do something
}
}
Note that #Validated is applied to the service class as well as the methods. You can also set the group for the whole service, if you plan on using multiple services.
I for once mostly use the built-in Jakarta Bean Validation annotations in combination with marker interfaces, because of their ease of use and almost no boilerplate, while staying somewhat flexible and adjustable.
You could inject Validator and call validate
#Autowired
Validator validator;
And then call validate:
Set<ConstraintViolation<Driver>> violations = validator.validate(yourObjectToValidate);

How to decouple validators from controllers in Spring MVC

I've been building Spring MVC (3.2/4.0) controllers with validators for my web application along the lines of what I found in the Spring Petclinic sample application. However, in the example application, validators are created within the relevant controlllers using the new keyword, creating a tight coupling. Now that I'm writing tests to cover this code it is proving difficult to isolate these classes due to this coupling.
Is there a recommended way to decouple validators from controllers? Is there some other solution to this problem?
Here's an example from the Petclinic application of the tight coupling I mean:
#RequestMapping(value = "/owners/{ownerId}/pets/new", method = RequestMethod.POST)
public String processCreationForm(#ModelAttribute("pet") Pet pet, BindingResult result, SessionStatus status) {
new PetValidator().validate(pet, result);
if (result.hasErrors()) {
return "pets/createOrUpdatePetForm";
} else {
this.clinicService.savePet(pet);
status.setComplete();
return "redirect:/owners/{ownerId}";
}
}
Define Petvalidator as a bean in your application-context and make the following changes to your controller
#RequestMapping(value = "/owners/{ownerId}/pets/new", method = RequestMethod.POST)
public String processCreationForm(#ModelAttribute("pet") Pet pet, BindingResult result, SessionStatus status) {
PetValidator petValidator; //change this line
petValidator.validate(pet,result); //change this line
if (result.hasErrors()) {
return "pets/createOrUpdatePetForm";
} else {
this.clinicService.savePet(pet);
status.setComplete();
return "redirect:/owners/{ownerId}";
}
}
And you can use property injection to inject the appropriate Petvalidator to your controller. If ur using component-scanning add the following to autowire the registered bean into your controller.
#Autowired
PetValidator petValidator;
Inside your controller istead of PetValidator petvalidator;
That's what #Validis for:
public String processCreationForm(#ModelAttribute("pet") #Valid Pet pet, BindingResult result, SessionStatus status) {
if (result.hasErrors()) {
There's no need to do the validation yourself. Let Spring handle it automatically.
If your PetValidator is of type org.springframework.validation.Validator you could bind it using WebDataBinder.setValidator().
In your controller or #ControllerAdvice add a method annotated with #InitBinder.
#InitBinder
public void initBinder(WebDataBinder binder) {
// Add the validator. Could be an auto wired instance as well.
binder.setValidator(new PetValidator());
}
Now all objects of a type supported by your validator will be validated automatically.

Spring #Validated in service layer

Hej,
I want to use the #Validated(group=Foo.class) annotation to validate an argument before executing a method like following:
public void doFoo(Foo #Validated(groups=Foo.class) foo){}
When i put this method in the Controller of my Spring application, the #Validated is executed and throws an error when the Foo object is not valid. However if I put the same thing in a method in the Service layer of my application, the validation is not executed and the method just runs even when the Foo object isn't valid.
Can't you use the #Validated annotation in the service layer ? Or do I have to do configure something extra to make it work ?
Update:
I have added the following two beans to my service.xml:
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>
and replaced the #Validate with #Null like so:
public void doFoo(Foo #Null(groups=Foo.class) foo){}
I know it is a pretty silly annotation to do but I wanted to check that if I call the method now and passing null it would throw an violation exception which it does. So why does it execute the #Null annotation and not the #Validate annotation ? I know one is from javax.validation and the other is from Spring but I do not think that has anything to do with it ?
In the eyes of a Spring MVC stack, there is no such thing as a service layer. The reason it works for #Controller class handler methods is that Spring uses a special HandlerMethodArgumentResolver called ModelAttributeMethodProcessor which performs validation before resolving the argument to use in your handler method.
The service layer, as we call it, is just a plain bean with no additional behavior added to it from the MVC (DispatcherServlet) stack. As such you cannot expect any validation from Spring. You need to roll your own, probably with AOP.
With MethodValidationPostProcessor, take a look at the javadoc
Applicable methods have JSR-303 constraint annotations on their
parameters and/or on their return value (in the latter case specified
at the method level, typically as inline annotation).
Validation groups can be specified through Spring's Validated
annotation at the type level of the containing target class, applying
to all public service methods of that class. By default, JSR-303 will
validate against its default group only.
The #Validated annotation is only used to specify a validation group, it doesn't itself force any validation. You need to use one of the javax.validation annotations like #Null or #Valid. Remember that you can use as many annotations as you would like on a method parameter.
As a side note on Spring Validation for methods:
Since Spring uses interceptors in its approach, the validation itself is only performed when you're talking to a Bean's method:
When talking to an instance of this bean through the Spring or JSR-303 Validator interfaces, you'll be talking to the default Validator of the underlying ValidatorFactory. This is very convenient in that you don't have to perform yet another call on the factory, assuming that you will almost always use the default Validator anyway.
This is important because if you're trying to implement a validation in such a way for method calls within the class, it won't work. E.g.:
#Autowired
WannaValidate service;
//...
service.callMeOutside(new Form);
#Service
public class WannaValidate {
/* Spring Validation will work fine when executed from outside, as above */
#Validated
public void callMeOutside(#Valid Form form) {
AnotherForm anotherForm = new AnotherForm(form);
callMeInside(anotherForm);
}
/* Spring Validation won't work for AnotherForm if executed from inner method */
#Validated
public void callMeInside(#Valid AnotherForm form) {
// stuff
}
}
Hope someone finds this helpful. Tested with Spring 4.3, so things might be different for other versions.
#pgiecek You don't need to create a new Annotation. You can use:
#Validated
public class MyClass {
#Validated({Group1.class})
public myMethod1(#Valid Foo foo) { ... }
#Validated({Group2.class})
public myMethod2(#Valid Foo foo) { ... }
...
}
Be careful with rubensa's approach.
This only works when you declare #Valid as the only annotation. When you combine it with other annotations like #NotNull everything except the #Valid will be ignored.
The following will not work and the #NotNull will be ignored:
#Validated
public class MyClass {
#Validated(Group1.class)
public void myMethod1(#NotNull #Valid Foo foo) { ... }
#Validated(Group2.class)
public void myMethod2(#NotNull #Valid Foo foo) { ... }
}
In combination with other annotations you need to declare the javax.validation.groups.Default Group as well, like this:
#Validated
public class MyClass {
#Validated({ Default.class, Group1.class })
public void myMethod1(#NotNull #Valid Foo foo) { ... }
#Validated({ Default.class, Group2.class })
public void myMethod2(#NotNull #Valid Foo foo) { ... }
}
As stated above to specify validation groups is possible only through #Validated annotation at class level. However, it is not very convenient since sometimes you have a class containing several methods with the same entity as a parameter but each of which requiring different subset of properties to validate. It was also my case and below you can find several steps to take to solve it.
1) Implement custom annotation that enables to specify validation groups at method level in addition to groups specified through #Validated at class level.
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface ValidatedGroups {
Class<?>[] value() default {};
}
2) Extend MethodValidationInterceptor and override determineValidationGroups method as follows.
#Override
protected Class<?>[] determineValidationGroups(MethodInvocation invocation) {
final Class<?>[] classLevelGroups = super.determineValidationGroups(invocation);
final ValidatedGroups validatedGroups = AnnotationUtils.findAnnotation(
invocation.getMethod(), ValidatedGroups.class);
final Class<?>[] methodLevelGroups = validatedGroups != null ? validatedGroups.value() : new Class<?>[0];
if (methodLevelGroups.length == 0) {
return classLevelGroups;
}
final int newLength = classLevelGroups.length + methodLevelGroups.length;
final Class<?>[] mergedGroups = Arrays.copyOf(classLevelGroups, newLength);
System.arraycopy(methodLevelGroups, 0, mergedGroups, classLevelGroups.length, methodLevelGroups.length);
return mergedGroups;
}
3) Implement your own MethodValidationPostProcessor (just copy the Spring one) and in the method afterPropertiesSet use validation interceptor implemented in step 2.
#Override
public void afterPropertiesSet() throws Exception {
Pointcut pointcut = new AnnotationMatchingPointcut(Validated.class, true);
Advice advice = (this.validator != null ? new ValidatedGroupsAwareMethodValidationInterceptor(this.validator) :
new ValidatedGroupsAwareMethodValidationInterceptor());
this.advisor = new DefaultPointcutAdvisor(pointcut, advice);
}
4) Register your validation post processor instead of Spring one.
<bean class="my.package.ValidatedGroupsAwareMethodValidationPostProcessor"/>
That's it. Now you can use it as follows.
#Validated(groups = Group1.class)
public class MyClass {
#ValidatedGroups(Group2.class)
public myMethod1(Foo foo) { ... }
public myMethod2(Foo foo) { ... }
...
}

Can I use spring DI to inject a session attribute into my POJO of webapplication

I am using the Struts2 framework and have the following method in a POJO class.
public String execute() {
setUserPrincipal();
//do something
someMethod(getUserPrincipal().getLoggedInUserId());
return SUCCESS;
}
the setUserPrincipal() method looks like this
public void setUserPrincipal() {
this.principal = (UserPrincipal) getServletRequest().getSession().getAttribute("principal");
}
Basically it is simply taking a session attribute named "principal" and setting it so that I can find out who the logged in user is. The call to setUserPrincipal() to do this is quite common in most of my POJOs and it also becomes a hassle when testing the method because I have to set a session attribute.
Is there a way to automatically inject the session attribute into the POJO either using Spring or something else?
I've only used Struts2 a bit, but they have an interceptor stack that you can tie to particular actions. You can create your own interceptor that injects the session variable.
public interface UserAware
{
void setUserPrincipal(String principal);
}
// Make your actions implement UserAware
public class MyInterceptor implements Interceptor
{
public String intercept(ActionInvocation inv) throws Exception
{
UserAware action = (UserAware) inv.getAction();
String principal = inv.getInvocationContext().getSession().get("principal");
action.setUserPrincipal(principal);
return inv.invoke();
}
}
Like I said, not much Struts2 experience so this is untested but I think the idea is there.
Don't know about injecting the session, but maybe having a piece of AOP code that sets principal before execute.
Here's some documentation:
http://static.springsource.org/spring/docs/2.5.x/reference/aop.html

Spring MVC Bean Validation

I have to implement validations for a web app that uses Spring MVC 3. The problem is that the bean class has methods like getProperty("name") and setProperty("name",valueObj). The validations have to be done on the data that is returned by passing different values to getProperty("name") , for eg: getProperty("age") should be greater than 16 and getProperty("state") should be required.
I would like to know if there is any support for validation this kind of Bean and if not, what can be the work around.
Thanks,
Atif
I don't think so. Bean validation is performed on javabeans, i.e. class fields with getters and setters. Even if you can register a custom validator, and make validation work, binding won't work. You would need to also register a custom binder that populates your object. It becomes rather complicated. So stick to the javabeans convention.
It sounds like you want to a custom validation class which implements org.springframework.validation.Validator.
#Component
public class MyValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return MyBean.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
MyBean myBean = (MyBean) target;
if (StringUtils.isBlank(myBean.getProperty("state"))) {
errors.rejectValue("state", "blank");
}
}
}
In your controller you would do manual validaton like follows:
#Autowired
private MyValidator myValidator;
#RequestMapping(value = "save", method = RequestMethod.POST)
public String save(#ModelAttribute("myBean") MyBean myBean, BindingResult result) {
myValidator.validate(myBean, result);
if (result.hasErrors()) {
...
}
...
}

Categories

Resources