views:

232

answers:

2

I have a class hierarchy of JPA entities that all inherit from a BaseEntity class:

@MappedSuperclass
@EntityListeners( { ValidatorListener.class })
public abstract class BaseEntity implements Serializable {
    // other stuff
}

I want all entities that implement a given interface to be validated automatically on persist and/or update. Here's what I've got.

My ValidatorListener:

public class ValidatorListener {

    private enum Type {
        PERSIST, UPDATE
    }

    @PrePersist
    public void checkPersist(final Object entity) {
        if (entity instanceof Validateable) {
            this.check((Validateable) entity, Type.PERSIST);
        }
    }

    @PreUpdate
    public void checkUpdate(final Object entity) {
        if (entity instanceof Validateable) {
            this.check((Validateable) entity, Type.UPDATE);
        }
    }

    private void check(final Validateable entity, final Type persist) {
        switch (persist) {
        case PERSIST:
            if (entity instanceof Persist) {
                ((Persist) entity).persist();
            }
            if (entity instanceof PersistOrUpdate) {
                ((PersistOrUpdate) entity).persistOrUpdate();
            }
            break;
        case UPDATE:
            if (entity instanceof Update) {
                ((Update) entity).update();
            }
            if (entity instanceof PersistOrUpdate) {
                ((PersistOrUpdate) entity).persistOrUpdate();
            }
            break;

        default:
            break;
        }
    }

}

and here's my Validateable interface that it checks against (the outer interface is just a marker, the inner contain the methods):

public interface Validateable {

    interface Persist extends Validateable {
        void persist();
    }

    interface PersistOrUpdate extends Validateable {
        void persistOrUpdate();
    }

    interface Update extends Validateable {
        void update();
    }

}

All of this works, however I would like to extend this behavior to Embeddable classes. I know two solutions:

  1. call the validation method of the embeddable object manually from the entity validation method:

    public void persistOrUpdate(){
        // validate my own properties first
        // then manually validate the embeddable property:
        myEmbeddable.persistOrUpdate();
        // this works but I'd like something that I don't have to call manually
    }
    
  2. use reflection, checking all properties to see if their type is of one of their interface types. This would work, but it's not pretty. Is there a more elegant solution?

A: 

OK, here's my own solution:

http://pastebin.com/YPmt7ifm

I'm using spring BeanUtils to iterate over properties. Still: has someone got a more elegant solution?

seanizer
+1  A: 

Consider annotation based approach. It will produce less code (it seems) and is almost always easier to understand.

Introduce new annotation:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Validators {
  String[] values();
}

Apply this annotation to each entity and embeddable object that needs validation, e.g.:

@MappedSuperclass
@EntityListeners( { ValidatorListener.class })
@Validators({Type.PERSIST, Type.UPDATE})
public abstract class MyEntity extends BaseEntity implements Serializable, Validateable {
    // other stuff
    @Validators(Type.PERSIST)
    @Embedded
    public Address getAddress() {
        return address;
    }
}

Of course, every entity and embeddable object should still implement Validateable interface that becomes simpler:

public interface Validateable {
  void validate(Type type);
}

Then validating logic becomes simpler:

  1. check if entity is annotated with @Validators;
  2. if not then go to iterating over embedded elements;
  3. check if entity implements Validateable;
  4. if not then to go iterating over embedded elements (possibly issuing warning for entity: 'Entity marked with Validators but doesn't implement Validatable interface')
  5. if both yes, then run validate if applicable type corresponds to listener;
  6. iterate over embedded elements with the same logic as above.

This approach allows you to separate declaring validation on entities and its embeddable elements (annotations) from validation logic (Java classes - entities and embeddable classes). For example, sometimes embeddable object may implement Validateable but no validation is required. It seems that listener also becomes simpler.

But if you are not after separating validation declarations from validation logic then your solution is quite satisfactory and possibly simpler.

grigory
while I like your solution to a certain degree, I am not going to use it. a) I want the validation methods to be inside the entity classes. b) I use a type hierarchy and want to use super.validate() in my validate() methods. I don't want to have to maintain external validator classes (in my validation logic, I am mainly checking that required fields are set and things like 'if type is a then amount must be 1')
seanizer
@seanizer - validation methods are inside entity classes - I am sorry that somehow I made this point not clear. The same interface @Validateable still applies in the same fashion.
grigory
ok, but then I don't really see why I don't just stick with @PrePersist and @PreUpdate annotations on methods in the entities (an approach that I originally used, but I prefer the interface-based approach)
seanizer
just edited lat paragraph because I believe it answers this question. I think your solution is completely fine as is.
grigory
thanks. I'll accept your answer as it's good, although I won't use it :-)
seanizer
it is quite possible that my solution is not better, but I hope that by bringing up the point of separating validation declaration (you use @EntityListeners) and implementation (Validateable interface) I have touched on something useful for you. I like your ideas and implementation too :-).
grigory