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:
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
}
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?
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:
@Validators
;Validateable
;Validators
but doesn't implement Validatable
interface')validate
if applicable type corresponds to listener; 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.