2

I'm using spring-boot based rest services and looking to apply a default validation on all strings.

  • I want to apply a default validation on any string field that is part of a @RequestBody or @Param. Note: I need a default validation, i.e. I want to be able to do it without annotating all string fields.
  • I want to be able to annotate a field to skip validation.
  • I plan to implement this using a Controller advice so that all controllers get this validation.

I have looked into using property source editors but that doesn't look like it's designed to throw validation errors from there. I'm not sure if I'll have access to applied annotations on the field either.

Using a Custom spring Validator implementation, I'm not sure how I can handle nested fields in a complex object.

I'm pretty sure I'm not the only one looking for an answer for something like this. Is there a recommendation?

ab m
  • 422
  • 3
  • 17

2 Answers2

1

You can use @Valid in combination with @RequestBody to check all fields annotated with javax.validation.constraints annotations (@NotBlank, @NotNull, etc). more info here

For @RequestParam and @PathVariable there is a similar approach here

My recommendation is to use this approaches before going for a Controller advice.

Edit: after your clarifications.

Not sure about custom Spring Validator but using AOP and reflection it's possible. I implemented an example below, I hope it helps.

@Component
@Aspect
public class ControllerAdvice {

private static final Logger logger = 
LoggerFactory.getLogger(ControllerAdvice.class);

@Pointcut("within(@org.springframework.web.bind.annotation.RestController *)")
public void controller() {
}

@Before("controller()")
public void beforeRestAPI(JoinPoint jp) {
    Object[] args = jp.getArgs();
    MethodSignature signature = (MethodSignature) jp.getSignature();
    Annotation[][] annotations = signature.getMethod().getParameterAnnotations();
    for (int i = 0; i < args.length; i++) {
        // validate @RequestParam arguments
        if (args[i] instanceof String && annotations[i].length > 0
                && annotations[i][0].annotationType().equals(RequestParam.class)) {
            String s = (String) args[i];
            logger.info("validating RequestParam " + s);
        }
        // validate String fields of @RequestBody
        if (annotations[i].length > 0 
                && annotations[i][0].annotationType().equals(RequestBody.class)) {
            Field[] allFields = args[i].getClass().getDeclaredFields();
            for (Field f : allFields) {
                // skip fields with specific annotation 
                if(f.isAnnotationPresent(SkipValidation.class)) {
                    continue;
                }
                if (f.getType().equals(String.class)) {
                    logger.info("validating string field: " + f.getName());
                }

            }
        }
    }

}
}
Marc
  • 2,738
  • 1
  • 17
  • 21
  • 1
    I clarified my questions a bit more. I know how to do it with annotations. Need a way to apply a default Validator on *all* string fields without needing to annotate them across the whole codebase. – ab m Mar 03 '20 at 03:49
  • Thanks for the nice example code. Something like this will work as well. I ended up using Spring's `RequestBodyAdviceAdapter` as it is more convenient. Upvoted. – ab m Mar 07 '20 at 03:14
0

In case someone needed this in future.

I found out that Spring has an advice RequestBodyAdvice specifically for request bodies.

I extended Spring's RequestBodyAdviceAdapter which implements the RequestBodyAdvice and gives abstract methods beforeBodyRead and afterBodyRead.

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/RequestBodyAdviceAdapter.html

ab m
  • 422
  • 3
  • 17