validator-spring icon indicating copy to clipboard operation
validator-spring copied to clipboard

Missing @Repeatable annotation on SpELAssert

Open Martin-BG opened this issue 6 years ago • 1 comments

Hello,

As the title says, there's no @Repeatable meta annotation on SpELAssert, preventing use of multiple annotations on the same element.

Other than that, SpELAssert seems to be configured to be used in such scenarios:

@Documented
@Retention(RUNTIME)
@Target({METHOD, FIELD, TYPE})
@interface List {
    SpELAssert[] value();
} 

Edit: Adding @Repeatable(SpELAssert.List.class) to SpELAssert seems to work properly in my tests.

Martin-BG avatar Apr 19 '19 17:04 Martin-BG

Hello,

I saw the problem too ,but I wrote my own ,which fit my needs.

Here is a example:

I.First create your validation annotation:

@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {SpELValidationValidator.class})
@Repeatable(value = SpELValidation.List.class)
public @interface SpELValidation {

    String message() default "Message wasn't set!";
    String value();

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE})
    @interface List {

        SpELValidation[] value();

    }

    Class<?>[] groups() default { };
    Class<? extends Payload>[] payload() default { };

}

II.Now write the SpELValidator:

Note that you MUST specify a BeanResolver ,because it will not find your Beans ,if you want to use them in the SpEL expression

@Component
public class SpELValidationValidator implements ConstraintValidator<SpELValidation, Object> {

    private SpELValidation annotation;
    private ExpressionParser parser;

    private final ApplicationContext applicationContext;

    @Autowired
    public SpELValidationValidator(ApplicationContext applicationContext) {
        this.parser = new SpelExpressionParser();
        this.applicationContext = applicationContext;
    }

    public void initialize(SpELValidation constraintAnnotation) {
        this.annotation = constraintAnnotation;
    }

    public boolean isValid(Object value, ConstraintValidatorContext context) {
        StandardEvaluationContext spelContext = this.getSpELContext(value);
        boolean result = (boolean) this.parser.parseExpression(this.annotation.value()).getValue(spelContext);

        return result;
    }

    private StandardEvaluationContext getSpELContext(Object value) {
        AutowireCapableBeanFactory beanFactory = this.applicationContext.getAutowireCapableBeanFactory();
        BeanFactoryResolver beanResolver = new BeanFactoryResolver(beanFactory);

        StandardEvaluationContext spELContext = new StandardEvaluationContext(value);
        spELContext.setBeanResolver(beanResolver);

        return spELContext;
    }

}

III.Then add a configuration for the Validator ,which you will use to validate objects:

@Configuration
public class ValidatorConfiguration {
    
    @Bean
    public LocalValidatorFactoryBean validator() {
        return new LocalValidatorFactoryBean();
    }

}

IV.Voilà now you can use it,repeat it and will not be dependent on 3rd party dependencies:

@Getter
@Setter
@SpELValidation(value = "@commentReportServicesImp.hasReported(#this.userId,#this.commentId,#this.type) == false",message = "You have already reported that comment!")
@SpELValidation(value = "#this.commentId == 'joreto2'",message = "Comment ID is not equal!")
public class CommentReportCreateRequest extends ReportCreateRequest {

    @NotNull
    private String commentId;

    @NotNull
    private Type type;

}

ItsGosho avatar Oct 23 '19 15:10 ItsGosho