piątek, 3 lutego 2012

Przekazywanie adnotacji do adnotacji.

Adnotacje mają pewne swoje ograniczenia. Pierwsze z nich to fakt, że danej adnotacji do danego targetu możemy użyć tylko raz.

Bazując na przykładzie z poprzedniego posta, jeśli w klasie Entity, mamy 2 pary pól do identycznej walidacji, np:
public class Entity {

    private Date from;
    private Date to;
   
    private Date activeFrom;
    private Date activeTo;
}

To użycie tej samej adnotacji walidującej dwa razy jest niedozwolone.
@CheckDates(dateFrom="from", dateTo="to")
@CheckDates(dateFrom="activeFrom", dateTo="activeTo")
public class Entity {

    private Date from;
    private Date to;
    
    private Date activeFrom;
    private Date activeTo;
}

Rozwiązaniem tego problemu jest wykorzystanie kolejnego ograniczenia adnotacji, mówięcego, że parametrem adnotacji może być tylko typ prymitywny, String, Class, enum, inna adnotacja, lub tablica 1-wymiarowa tablica wymienionych wcześniej klas.

Implementacja czegoś takiego mogłba by być następująca:
@Target({ TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = MultiCheckDatesValidator.class)
public @interface MultiCheckDates {

    String message() default "{pl.costam.MultiCheckDates}";

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

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

    CheckDates[] value();
}

I sam walidator, który tak de facto w pętli wywołuje walidator z poprzedniego posta (musimy jedynie dopisać konstruktor):
public class MultiCheckDatesValidator implements ConstraintValidator<MultiCheckDates, Object> {

    private CheckDates[] checkDates;


    @Override
    public void initialize(MultiCheckDates constraintAnnotation) {

        checkDates = constraintAnnotation.value();
    }

    @Override
    public boolean isValid(Object object, ConstraintValidatorContext context) {

        boolean isValid = true;

        for (CheckDates checkDate : checkDates) {

            /**
             * tworzymy i wywołujemy walidator dla pojedyńczej pary from-to
             */
            if (!new CheckDatesValidator(checkDate).isValid(object, context)) {

                isValid = false;
            }
        }

        return isValid;
    }
}

I samo wywołanie:
@MultiCheckDates({
        @CheckDates(dateFrom = "from", dateTo = "to"),
        @CheckDates(dateFrom = "activeFrom", dateTo = "activeTo") })
public class Entity {

Za piękne może to i nie jest, ale warto mieć świadomość limitów wykorzystania adnotacji. Prawdopodobnie lepszym rozwiązaniem byłoby stworzenie osobnej klasy DateRange, jak zasugerował to Michał Gruca w swoim komentarzu.
Choć czasami zastajemy kod taki a nie inny i refaktor bywa bardzo bolesny.

Brak komentarzy:

Publikowanie komentarza