-2

How to write (type level annotation) custom annotation to allow a choice validation (exactly one of a number of properties has to be not null)?

vin vin
  • 1
  • 1
  • 2

1 Answers1

4

Even if this "question" is very broad I will give an answer as I have exactly the needed piece of code here:

@Target(ElementType.TYPE)
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = ChoiceValidator.class)
public @interface Choice {
    String[] value();

    boolean multiple() default false;

    String message() default "{com.stackoverflow.validation.Choice.message}";

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

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

Here a validator using a bean property access:

public class ChoiceValidator implements ConstraintValidator<Choice, Object> {
    private String[] properties;
    private boolean allowMultiple;

    @Override
    public void initialize(Choice constraintAnnotation) {
        if (constraintAnnotation.value().length < 2) {
            throw new IllegalArgumentException("at least two properties needed to make a choice");
        }
        properties = constraintAnnotation.value();
        allowMultiple = constraintAnnotation.multiple();
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        try {
            BeanInfo info = getBeanInfo(value.getClass());
            long notNull = Stream.of(properties)
                    .map(property -> Stream.of(info.getPropertyDescriptors())
                            .filter(desr -> desr.getName().equals(property))
                            .findAny()
                            .orElse(null)
                    )
                    .map(prop -> getProperty(prop, value))
                    .filter(Objects::nonNull)
                    .count();
            return allowMultiple ? notNull != 0 : notNull == 1;
        } catch (IntrospectionException noBean) {
            return false;
        }
    }

    private Object getProperty(PropertyDescriptor prop, Object bean) {
        try {
            return prop.getReadMethod() == null ? null : prop.getReadMethod().invoke(bean);
        } catch (ReflectiveOperationException noAccess) {
            return null;
        }
    }
}

A typical usage can look like this (lombok annotation to generate getters and setters):

@Data
@Choice({"one", "two"})
class OneOf {
    private String one;
    private String two;
    private String whatever;
}

@Data
@Choice(value = {"one", "two"}, multiple = true)
class AnyOf {
    private String one;
    private String two;
}

But to clarify: Stackoverflow is a QA community for developers to exchange knowledge. It is not a place to ask "Can you please code this for me for free?". All valuable contributors at least try to solve problems first and come with detailed questions afterwards. People answering questions spend their spare time and are not payed for. Please show respect by asking for specific problems and showing own efforts in the future.

Arne Burmeister
  • 20,046
  • 8
  • 53
  • 94
  • @vinvin if this is helpful to you, think about upvoting or accepting the answer if you want any more answers in the future to show respect to the effort of the answering ones – Arne Burmeister Apr 06 '17 at 22:23