środa, 7 listopada 2012

Iterables i reduce

Ostatnio uczestnicząc w kursie "JavaScript dla Javowców" doznałem pewnego olśnienia. Otóż JavaScript, którego nadal nie lubię (choć już trochę bardziej szanuje), ma kilka dość dobrych pomysłów, które warto przenieść do świata Javy.

Konkretnie mówię tu o funkcyjnym podejściu do operacji na tablicach/hashmapach w JS. Ktoś na szczęście już dawno wpadł na ten pomysł i mamy bibliotekę Google Guava, która jest dość dobrze poważana w świecie Javy.

Klika analogii JS => Java:

array.map(...) => Iterables.transform(...)
array.filter(...) => Iterables.filter(...)

W Guavie brakuje mi tylko 1 funkcji do pełni szczęścia, tj. odpowiednika funkcji array.reduce(...). Teoretycznie jest w planach jej prowadzenie, ale póki co jej nie ma, więc można szybko stworzyć własną implementacje.

import com.google.common.base.Preconditions;

public class Iterables {

 /**
  * redukuje przekazany {@link Iterable} do obiektu A
  * @param iterable co redukujemy
  * @param initialValue wartosc inicjalna, najlepiej neutralna dla danej                 operacji 0 dla dodawania, 1 dla mnożenia, itd 
  * @param reducer funkcja redukująca
  * @return wartosc zredukowana
  */
 public static <A, B> A reduce(Iterable<B> iterable, A initialValue, Reducer<A, B> reducer) {

  Preconditions.checkNotNull(initialValue);
  A result = initialValue;
  for (B b : iterable) {
   result = reducer.reduce(result, b);
  }
  return result;
 }
}

/**
 * funkcja redukująca
 * @param <A> klasa obiektu docelowego
 * @param <B> klasa obiektu wejsciowego
 */
public interface Reducer<A, B> {

 /**
  * redukuje parametr wejsciowy do docelowego
  *
  * @param to aktualny obiekt
  * @param from obiekt wejsciowy
  * @return zredukowany obiekt docelowy
  */
 A reduce(A current, B from);
}

I samo wykorzystanie:
List<Integer> someNumbers = Lists.newArrayList(1, 2, 3);
Reducer<Integer, Integer> sum = new Reducer<Integer, Integer>() {

 @Override
 public Integer reduce(Integer current, Integer from) {
  return current + from;
 }
};
Integer sumValue = Iterables.reduce(someNumbers, 0, sum);
assertThat(sumValue).isEqualTo(6);

Niby nic wielce odkrywczego, ale taka jest cała Guava, która proste rzeczy zmienia w jeszcze prostsze lub też "standaryzuje" pewne operacje.

Jeśli komuś takie (funkcyjne) podejście nie bardzo pasuje, to radziłbym się zacząć przyzwyczajać. W kolejnej wersji Javy prawdopodobnie wejdą wyrażenia lambda, więc funkcyjność wkroczy do "czystej" Javy pełną parą.

czwartek, 11 października 2012

Infinitest i applet allert

Jeśli masz problem z infinitestem w postaci alertu z komunikatem:
"The applet is attempting to access the "exists" state attributes of ..."
jak poniżej:


Wystarczy do parametrów uruchomieniowych eclipsa dodać: "-Djava.awt.headless=true".
Pomysł zaczerpnięty ze strony: http://www.eclipse.org/forums/index.php/t/166452/

piątek, 29 czerwca 2012

JavaEE materiały

Mam chciwy plan ogarnąć (cały) internet z materiałami dla programistów, głównie JavyEE, ale wszystkie wspólne mianowniki w programowaniu, będą równie mile widziane. Po co to robić skoro jest google? Ano chociażby po to, że googla trzeba wiedzieć o co spytać, a ja czasami mam po prostu ochotę przeczytać/obejrzeć coś nowego... Po drugie często coś ciekawego znajdę na vimeo, a potem totalnie nie mam pojęcia jak to odnaleźć.

Proponuje następujący układ, za każdy link dzięki, któremu dowiedziałeś/aś się czegoś nowego/przydatnego, proszę o jakiś nowy link, żeby powstał samo napędzający się zbiór materiałów. Myślę, że nie muszę dodawać, że zależy mi na linkach, które jednak coś za sobą niosą. Docelowo chciałbym, aby powstał przesiany kanon materiałów dla programisty, do którego można w każdej chwili wrócić, czy też zweryfikować, że już się dane źródło przetrawiło.

Blogi: 

Video:

Craftsmanship:
DDD:
TDD:

Agile:

Spring:

Java:

Inne:

Książki:

niedziela, 24 czerwca 2012

Mapowanie opakowywacza kolekcji

Opakowywacze kolekcji, które zostały opisane przez Koziołka, uważam za bardzo dobre i przydatne podejście/wzorzec. Jednak w pewnym momencie natrafiłem na problem podczas mapowania takich tworów. Mamy w systemie encje BankAccount, wielu różnych aktorów (Client, Manager, itd) może mieć zbiór BankAccount. Warto opakować taki zbiór komponentem BankAccounts, który miałby wspólne metody dla wszystkich aktorów, takie jak np. znajdź konto z zadanego banku, itd.

Wyglądałyby to mniej więcej tak:
@Embeddable
public class BankAccounts {

    @OneToMany(cascade = { CascadeType.ALL }, orphanRemoval = true)
    private Set<BankAccount> bankAccounts = new HashSet<>();
    //...
}
I w jednym z aktorów:
@Entity
public class Client{
    //...

    @Embedded
    @AssociationOverride(name = "bankAccounts", 
        joinColumns = @JoinColumn(name = "client_id"))
    private BankAccounts bankAccounts = new BankAccounts();
    //...
}
I tu powstaje problem, w postaci wyjątku:
javax.persistence.PersistenceException: [PersistenceUnit: testPU] Unable to configure EntityManagerFactory
    at org.hibernate.ejb.Ejb3Configuration.configure(Ejb3Configuration.java:378)
    at org.hibernate.ejb.HibernatePersistence.createEntityManagerFactory(HibernatePersistence.java:56)
    at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:63)
    at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:47)
    at pl.cqbroker.utils.DaoTest.setUp(DaoTest.java:39)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:27)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:30)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
    at ...
Caused by: org.hibernate.AnnotationException: Illegal attempt to define a @JoinColumn with a mappedBy association: bankAccounts.bankAccounts
    at org.hibernate.cfg.Ejb3JoinColumn.buildJoinColumn(Ejb3JoinColumn.java:253)
    at org.hibernate.cfg.Ejb3JoinColumn.buildJoinColumnsWithDefaultColumnSuffix(Ejb3JoinColumn.java:227)
    at org.hibernate.cfg.Ejb3JoinColumn.buildJoinColumns(Ejb3JoinColumn.java:194)
    at org.hibernate.cfg.ColumnsBuilder.extractMetadata(ColumnsBuilder.java:124)
    at org.hibernate.cfg.AnnotationBinder.processElementAnnotations(AnnotationBinder.java:1495)
    at org.hibernate.cfg.AnnotationBinder.fillComponent(AnnotationBinder.java:2433)
    at org.hibernate.cfg.AnnotationBinder.fillComponent(AnnotationBinder.java:2336)
    at org.hibernate.cfg.AnnotationBinder.bindComponent(AnnotationBinder.java:2285)
    at org.hibernate.cfg.AnnotationBinder.processElementAnnotations(AnnotationBinder.java:2021)
    at org.hibernate.cfg.AnnotationBinder.processIdPropertiesIfNotAlready(AnnotationBinder.java:796)
    at org.hibernate.cfg.AnnotationBinder.bindClass(AnnotationBinder.java:707)
    at org.hibernate.cfg.Configuration$MetadataSourceQueue.processAnnotatedClassesQueue(Configuration.java:4035)
    at org.hibernate.cfg.Configuration$MetadataSourceQueue.processMetadata(Configuration.java:3989)
    at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1398)
    at org.hibernate.cfg.Configuration.buildMappings(Configuration.java:1375)
    at org.hibernate.ejb.Ejb3Configuration.buildMappings(Ejb3Configuration.java:1519)
    at org.hibernate.ejb.EventListenerConfigurator.configure(EventListenerConfigurator.java:193)
    at org.hibernate.ejb.Ejb3Configuration.configure(Ejb3Configuration.java:1100)
    at org.hibernate.ejb.Ejb3Configuration.configure(Ejb3Configuration.java:282)
    at org.hibernate.ejb.Ejb3Configuration.configure(Ejb3Configuration.java:366)
    ... 28 more
Rozwiązanie jest w generalnie proste, ale trochę czasu mi zajęło zanim zrozumiałem co jest nie tak, więc gdyby ktoś miał podobny problem, wystarczy dodać adnotację @JoinColumn(). Może być wypełniona jakimiś parametrami, bądź nie - nie ma to znaczenia bo i tak zostanie nadpisana przez Clienta lub inną encję.
@Embeddable
public class BankAccounts {

    @OneToMany(cascade = { CascadeType.ALL }, orphanRemoval = true)
    @JoinColumn
    private Set<BankAccount> bankAccounts = new HashSet<>();
    //...
}

wtorek, 19 czerwca 2012

Value Object a prawidłowa implementacja


Z racji wszechobecnej mody na DDD, obiekty typu Value Object (VO) powstają jak grzyby po deszczu. Zaimplementowanie takiego obiektu wydaje się być trywialne. Jednak bazując na własnych doświadczeniach widziałem/zrobiłem wiele różnej jakości VO i dopiero teraz chyba mam pełny pogląd w tej sprawie. Przykładem niech będzie VO Email, służący do opakowania stringowej reprezentacji adresu email. Implementacja będzie bazowała na Hibernate, ale zaprezentowane rozwiazania są wspólne do większości frameworków od persystencji.
@ValueObject
@Access(AccessType.FIELD)
public class Email implements Serializable{
 private static final long serialVersionUID = -6632187560263057672L;
 @Basic
 private String value;


 /**
  * na potrzeby hibenrate prywatny konstruktor
  */
 @SuppressWarnings("unused")
 private Email() {}

 /**
  * @param email - poprawy adres email
  * @throws IllegalArgumentException jeśli adres email jest niepoprawny
  */
 public Email(String email) {
  if (!EmailValidator.getInstance().isValid(email)) {
   throw new IllegalArgumentException("podałeś nieprawidłowy format adresu email");
  }
  this.value = email;
 }

 @Override
 public boolean equals(Object obj) {
  if (obj == this)
   return true;
  if (!(obj instanceof Email))
   return true;
  Email email = (Email) obj;
  return new EqualsBuilder().append(value, email.value).isEquals();
 }

 @Override
 public int hashCode() {
  return new HashCodeBuilder().append(value).toHashCode();
 }

 @Override
 public String toString() {
  return value;
 }

 public String getValue() {
  return value;
 }
}
Na pierwszy rzut oka nic wielce okrywczego w tym kodzie nie ma. Mimo wszystko warto opisać kilka kluczowych aspektów. Po pierwsze warto zrobić sobie adnotację „marker” do oznaczania wszystkich VO. Łatwiej je wtedy chociażby wyszukiwać w projekcie.
/**
 * stereotyp do oznacznia klass o charakterze ValueObject, 
 *  - ich identyczność bazuje na nie na Id jak w przypadku encji, tylko na ich stanie
 *  - najczęśniej powinny być niemodyfikowalne
 *  - enkapsulują pola o podobnej semantyce,
 * @author aludwiko
 */
public @interface ValueObject {}
Kolejna adnotacja @Access służy do nadpisania po czym mapowane są pola. Niezależnie od tego czy klasa bazowa np. User, mapowana jest z wykorzystaniem getterów czy bezpośrednio pól, Email zawsze będzie mapowany bezpośrednio do pola. Dzięki temu nie musimy tworzyć settera i gettera, co poprawia nam enkapsulację. W moim przypadku getter został, ale jest on po prostu przydatny.

Z racji tego, że encje przeważnie implementują interfejs Serializable, od razu dodajemy go do VO, na pewno nic złego z tego nie wyniknie.

Konstruktor domyślny (wymagany przez Hibernata) został zaimplementowany jako prywatny, aby nikogo nie kusiło tworzenie „pustych” Emaili. Publiczny konstruktor gwarantuje nam, że nigdy utworzymy „błędnego” obiektu Email, co jest jedną z cech dobrego VO, tzn jego stan nie powinien być nieprawidłowy.

Taki konstruktor i wspomniany wcześniej brak settera powodują że Email spełnia kolejną ważna cechę VO, jest niemodyfikowalny. Jest to bardzo ważna cecha w kontekście VO. Equals i hashCode dopełniają minimalną implementację.

Ok, mamy VO, pytanie co dalej z nim zrobić. Jeśli chodzi o warstwę widoku, to należy stworzyć odpowiedni Converter w JSF, PropertyEditor w starym Springu, lub jakikolwiek inny mechanizm, który z wartości inputa utworzy nam obiekt Email. Z racji tego że nie mamy settera nie bindujemy na widoku wartości bezpośrednio do value
<h:inputtext id="email" value="#{user.email.value}">
tylko do samego email'a
<h:inputtext id="email" value="#{user.email}">
Przykładowa implementacji w springu:
public class EmailPropertyEditor extends PropertyEditorSupport {

 @Override
 public void setAsText(String text) throws IllegalArgumentException {
  if (StringUtils.isNotBlank(text)) {
   Email email = new Email(text);
   setValue(email);
  }
  else {
   setValue(null);
  }
 }

 @Override
 public String getAsText() {
  Email email = (Email) getValue();
  if (email != null) {
   return email.getValue();
  }
  else {
   return "";
  }
 }
}
W tym momencie mamy już obiekt z wypełnionym Email'em, teraz warto powiedzieć Hibernatowi jak ma zapisywać taki obiekt z Email'em do bazy. Są dwa główne podejścia, albo idziemy w kierunku @Embeddable i @Embedded albo Hibernate UserType.

Pierwsze podejście jest już proste i prawdopodobnie każdy miał z nim styczność. Chociaż np. dyskusja czy każdy obiekt @Embeddable jest automatycznie VO, nadal trwa i właściwie jestem po obu stronach w tym konflikcie.

Drugie podejście, choć trochę bardziej skomplikowane, daje ciekawy efekt w postaci możliwości tworzenia bardziej obiektowych zapytań, takich jak np:
Email email = new Email("aaaa@op.pl");
Criteria criteria = session.createCriteria(User.class).add(eq("email", email));
lub jako HQL
"from User WHERE email = :someEmail"
W tym przypadku musimy napisać własną implementację typu pola w klasie, tak aby hibernate wiedział jak zapisywać Emaila. W większości wypadków wystarczy zaimplementowanie interfejsu UserType. Jednak w tym przypadku chciałbym mieć możliwość tworzenia zapytań z wykorzystaniem LIKE:
Criteria criteria = session.createCriteria(User.class).add(ilike("email.value", "op.pl"));
Dostęp do pól VO w zapytaniach jest możliwy, ale musimy zaimplementować CompositeUserType, przykład implementacji:
/**
 * implementacja UserType'a dla VO {@link Email}
 * @author aludwiko
 */
public class EmailCompositeType implements CompositeUserType {

 /**
  * ORDER IS IMPORTANT! it must match the order the columns are defined in the property mapping
  */
 @Override
 public String[] getPropertyNames() {
  return new String[] { "value" };
 }
 @Override
 public Type[] getPropertyTypes() {
  return new Type[] { Hibernate.STRING };
 }
 @Override
 public Object getPropertyValue(Object component, int property) throws HibernateException {
  if (component == null) {
   return null;
  }
  Email email = (Email) component;
  switch (property) {
   case 0: {
    return email.getValue();
   }
   default: {
    throw new HibernateException("Invalid property index [" + property + "] for Email");
   }
  }
 }
 @Override
 public void setPropertyValue(Object component, int property, Object value) throws HibernateException {
  throw new UnsupportedOperationException();
 }
 @Override
 public Class returnedClass() {
  return Email.class;
 }
 @Override
 public boolean equals(Object x, Object y) throws HibernateException {
  if (x == y) {
   return true;
  }
  if (x == null || y == null) {
   return false;
  }
  Email emailX = (Email) x;
  Email emailY = (Email) y;
  return emailX.equals(emailY);
 }
 @Override
 public int hashCode(Object x) throws HibernateException {
  return x.hashCode();
 }
 @Override
 public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws HibernateException, SQLException {
  String name = names[0];
  Object emailString = Hibernate.STRING.nullSafeGet(rs, name);
  if (emailString == null) {
   return null;
  }
  return new Email((String) emailString);
 }
 @Override
 public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException {
  if (value == null) {
   Hibernate.STRING.nullSafeSet(st, null, index);
  }
  else {
   Hibernate.STRING.nullSafeSet(st, ((Email) value).toString(), index);
  }
 }
 @Override
 public Object deepCopy(Object value) throws HibernateException {
  return value;
 }
 @Override
 public boolean isMutable() {
  return true;
 }
 @Override
 public Serializable disassemble(Object value, SessionImplementor session) throws HibernateException {
  return (Serializable) value;
 }
 @Override
 public Object assemble(Serializable cached, SessionImplementor session, Object owner) throws HibernateException {
  return cached;
 }
 @Override
 public Object replace(Object original, Object target, SessionImplementor session, Object owner) throws HibernateException {
  return original;
 }
}
I samo wykorzystanie:
@Entity
public class User {
        //...
 @Column
 @Type(type = "package.EmailCompositeType")
 public Email getEmail() {
  return email;
 }
 public void setEmail(Email email) {
  this.email = email;
 }
        //...
}
Mam nadzieję, że temat został opisany kompleksowo i da się to przeczytać w skończonym czasie. Gdyby ktoś miał coś ciekawego do dodania, to zachęcam do komentowania.

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.

Hibernate Validator (JSR 303) + mechanizm refleksji = walidacja absolutna

Przygarnięcie przez JavaEE projektu Hibernate Validator pod numerem JSR 303 było wg. mnie kolejnym dobrym krokiem w standaryzacji dobry rozwiązań z projektów opensourcowych.

Jednak po jakimś czasie używania (jakże przyjemnego) standardowych walidatorów, doszedłem do wniosku, że to za mało. Napisanie kilku własnych, które swoją drogą również tworzy się bardzo prosto, tylko na chwilę zaspokoiło moje potrzeby. Dopiero połączenie JSR 303 i mechanizmu refleksji w javie, daje maksymalne możliwości wykorzystania tego standardu.

Załóżmy, że mamy klasę która posiada dwie daty: od i do.
public class Entity {

    private Date from;
    private Date to;
}

Chcielibyśmy sprawdzić czy podane daty są po kolei, tj 'from' <= 'to'. Możemy napisać własny walidator dla klasy Entity, ale w projekcie takich klas z datami możemy mieć mnóstwo i dla każdej z nich należałoby stworzyć oddzielny walidator. Dzięki refleksją w javie możemy zrobić jeden uniwersalny, który jako parametry przyjmowałby 2 wartości, nazwa pola 'from', nazwa pola 'to'.
@Target({ TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = CheckDatesValidator.class)
public @interface CheckDates {

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

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

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

    String dateFrom();

    String dateTo();

}

I sam walidator:
public class CheckDatesValidator implements ConstraintValidator<CheckDates, Object> {

    private String dateFromFieldName;
    private String dateToFieldName;
    private String message;

    @Override
    public void initialize(CheckDates checkDates) {
        
        dateFromFieldName = checkDates.dateFrom();
        dateToFieldName = checkDates.dateTo();
        message = checkDates.message();
    }

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

        boolean result = validateDates(object);

        if (!result) {
            
            //jeśli walidacja nie powiodła sie to tworzymy własny constraint - wskazujemy dla którego pola wystąpił błąd
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate(message)
                    .addNode(dateToFieldName)
                    .addConstraintViolation();
        }

        return result;

    }

    private boolean validateDates(Object object) {

        try {
            
            Date dateFrom = field(dateFromFieldName).ofType(Date.class).in(object).get();
            Date dateTo = field(dateToFieldName).ofType(Date.class).in(object).get();

            // sprawdzenie dat
            return checkDatesInterval(dateFrom, dateTo);
        }
        catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
}

Poprzez refleksję można odwołać sie do pola używając właściwie "czystej" javy, ale wygląda to średnio w kodzie i lepiej użyć jakiegoś gotowego rozwiązania. Ja osobiście polecam biliotekę: FEST

Zastowanie adnotacji jest następujące:
@CheckDates(dateFrom="from", dateTo="to")
public class Entity {

    private Date from;
    private Date to;
}