diff --git a/annotations/pom.xml b/annotations/pom.xml
index 33624706c7..d6c9976ecf 100644
--- a/annotations/pom.xml
+++ b/annotations/pom.xml
@@ -71,7 +71,16 @@
cglib
test
-
+
+ javax.validation
+ validation-api
+ true
+
+
+ org.hibernate
+ hibernate-validator
+ test
+
@@ -97,10 +106,20 @@
3.4.GA
- cglib
- cglib
- 2.2
-
+ cglib
+ cglib
+ 2.2
+
+
+ org.hibernate
+ hibernate-validator
+ 4.0.0.Beta1
+
+
+ javax.validation
+ validation-api
+ 1.0.CR2
+
diff --git a/annotations/src/main/java/org/hibernate/cfg/AnnotationConfiguration.java b/annotations/src/main/java/org/hibernate/cfg/AnnotationConfiguration.java
index 485a97c985..e8723f5531 100644
--- a/annotations/src/main/java/org/hibernate/cfg/AnnotationConfiguration.java
+++ b/annotations/src/main/java/org/hibernate/cfg/AnnotationConfiguration.java
@@ -69,6 +69,7 @@ import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.java.JavaReflectionManager;
import org.hibernate.cfg.annotations.Version;
import org.hibernate.cfg.annotations.reflection.JPAMetadataProvider;
+import org.hibernate.cfg.beanvalidation.BeanValidationActivator;
import org.hibernate.engine.NamedQueryDefinition;
import org.hibernate.engine.NamedSQLQueryDefinition;
import org.hibernate.engine.ResultSetMappingDefinition;
@@ -800,6 +801,13 @@ public class AnnotationConfiguration extends Configuration {
}
public SessionFactory buildSessionFactory() throws HibernateException {
+ enableLegacyHibernateValidator();
+ enableBeanValidation();
+ enableHibernateSearch();
+ return super.buildSessionFactory();
+ }
+
+ private void enableLegacyHibernateValidator() {
//add validator events if the jar is available
boolean enableValidatorListeners = !"false".equalsIgnoreCase( getProperty( "hibernate.validator.autoregister_listeners" ) );
Class validateEventListenerClass = null;
@@ -868,10 +876,10 @@ public class AnnotationConfiguration extends Configuration {
}
}
}
-
- enableHibernateSearch();
-
- return super.buildSessionFactory();
+ }
+
+ private void enableBeanValidation() {
+ BeanValidationActivator.activateBeanValidation( getEventListeners(), getProperties() );
}
/**
diff --git a/annotations/src/main/java/org/hibernate/cfg/beanvalidation/BeanValidationActivator.java b/annotations/src/main/java/org/hibernate/cfg/beanvalidation/BeanValidationActivator.java
new file mode 100644
index 0000000000..df37cb2469
--- /dev/null
+++ b/annotations/src/main/java/org/hibernate/cfg/beanvalidation/BeanValidationActivator.java
@@ -0,0 +1,87 @@
+package org.hibernate.cfg.beanvalidation;
+
+import java.util.Map;
+import java.util.Properties;
+import java.lang.reflect.Method;
+import java.lang.reflect.InvocationTargetException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.hibernate.util.ReflectHelper;
+import org.hibernate.HibernateException;
+import org.hibernate.AssertionFailure;
+import org.hibernate.event.EventListeners;
+
+/**
+ * This class has no hard depenmdency on Bean Validation APIs
+ * It must uses reflectione very time BV is required.
+ * @author Emmanuel Bernard
+ */
+public class BeanValidationActivator {
+
+ private static final String BV_DISCOVERY_CLASS = "javax.validation.Validation";
+ private static final String TYPE_SAFE_ACTIVATOR_CLASS = "org.hibernate.cfg.beanvalidation.TypeSafeActivator";
+ private static final String TYPE_SAFE_ACTIVATOR_METHOD = "activateBeanValidation";
+ private static final String MODE_PROPERTY = "javax.persistence.validation.mode";
+
+ public static void activateBeanValidation(EventListeners eventListeners, Properties properties) {
+ ValidationMode mode = ValidationMode.getMode( properties.get( MODE_PROPERTY ) );
+ if (mode == ValidationMode.NONE) return;
+ try {
+ //load Validation
+ ReflectHelper.classForName( BV_DISCOVERY_CLASS, BeanValidationActivator.class );
+ }
+ catch ( ClassNotFoundException e ) {
+
+ if (mode == ValidationMode.CALLBACK) {
+ throw new HibernateException( "Bean Validation not available in the class path but required in " + MODE_PROPERTY );
+ }
+ else if (mode == ValidationMode.AUTO) {
+ //nothing to activate
+ return;
+ }
+ else {
+ throw new AssertionFailure( "Unexpected ValidationMode: " + mode );
+ }
+ }
+ try {
+ Class> activator = ReflectHelper.classForName( TYPE_SAFE_ACTIVATOR_CLASS, BeanValidationActivator.class );
+ Method buildDefaultValidatorFactory =
+ activator.getMethod( TYPE_SAFE_ACTIVATOR_METHOD, EventListeners.class, Properties.class );
+ buildDefaultValidatorFactory.invoke( null, eventListeners, properties );
+ }
+ catch ( NoSuchMethodException e ) {
+ throw new HibernateException( "Unable to get the default Bean Validation factory", e);
+ }
+ catch ( IllegalAccessException e ) {
+ throw new HibernateException( "Unable to get the default Bean Validation factory", e);
+ }
+ catch ( InvocationTargetException e ) {
+ throw new HibernateException( "Unable to get the default Bean Validation factory", e);
+ }
+ catch ( ClassNotFoundException e ) {
+ throw new HibernateException( "Unable to get the default Bean Validation factory", e);
+ }
+ }
+
+ private static enum ValidationMode {
+ AUTO,
+ CALLBACK,
+ NONE;
+
+ public static ValidationMode getMode(Object modeProperty) {
+ if (modeProperty == null) {
+ return AUTO;
+ }
+ else {
+ try {
+ return valueOf( modeProperty.toString().toUpperCase() );
+ }
+ catch ( IllegalArgumentException e ) {
+ throw new HibernateException( "Unknown validation mode in " + MODE_PROPERTY + ": " + modeProperty.toString() );
+ }
+ }
+ }
+ }
+}
diff --git a/annotations/src/main/java/org/hibernate/cfg/beanvalidation/BeanValidationEventListener.java b/annotations/src/main/java/org/hibernate/cfg/beanvalidation/BeanValidationEventListener.java
new file mode 100644
index 0000000000..0d2ad49e50
--- /dev/null
+++ b/annotations/src/main/java/org/hibernate/cfg/beanvalidation/BeanValidationEventListener.java
@@ -0,0 +1,156 @@
+package org.hibernate.cfg.beanvalidation;
+
+import java.util.Set;
+import java.util.Properties;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ArrayList;
+import javax.validation.ValidatorFactory;
+import javax.validation.ConstraintViolation;
+import javax.validation.TraversableResolver;
+import javax.validation.Validator;
+import javax.validation.ConstraintViolationException;
+import javax.validation.groups.Default;
+
+import org.hibernate.event.PreInsertEventListener;
+import org.hibernate.event.PreUpdateEventListener;
+import org.hibernate.event.PreDeleteEventListener;
+import org.hibernate.event.PreInsertEvent;
+import org.hibernate.event.PreUpdateEvent;
+import org.hibernate.event.PreDeleteEvent;
+import org.hibernate.EntityMode;
+import org.hibernate.HibernateException;
+import org.hibernate.util.ReflectHelper;
+
+/**
+ * @author Emmanuel Bernard
+ */
+//FIXME review exception model
+public class BeanValidationEventListener implements
+ PreInsertEventListener, PreUpdateEventListener, PreDeleteEventListener {
+ private static final String JPA_GROUP_PREFIX = "javax.persistence.validation.group.";
+ private static final Class>[] DEFAULT_GROUPS = new Class>[] { Default.class };
+ private static final Class>[] EMPTY_GROUPS = new Class>[] { };
+
+ private ValidatorFactory factory;
+ private TraversableResolver tr;
+ private Map[]> groupsPerOperation = new HashMap[]>(3);
+
+
+ public BeanValidationEventListener(ValidatorFactory factory, Properties properties) {
+ this.factory = factory;
+ setGroupsForOperation( Operation.INSERT, properties );
+ setGroupsForOperation( Operation.UPDATE, properties );
+ setGroupsForOperation( Operation.DELETE, properties );
+ }
+
+ private void setGroupsForOperation(Operation operation, Properties properties) {
+ Object property = properties.get( JPA_GROUP_PREFIX + operation.getGroupPropertyName() );
+
+ Class>[] groups;
+ if ( property == null ) {
+ groups = operation == Operation.DELETE ? EMPTY_GROUPS : DEFAULT_GROUPS;
+ }
+ else {
+ if ( property instanceof String ) {
+ String stringProperty = (String) property;
+ String[] groupNames = stringProperty.split( "," );
+ if ( groupNames.length == 1 && groupNames[0].equals( "" ) ) {
+ groups = EMPTY_GROUPS;
+ }
+ else {
+ List> groupsList = new ArrayList>(groupNames.length);
+ for (String groupName : groupNames) {
+ String cleanedGroupName = groupName.trim();
+ if ( cleanedGroupName.length() > 0) {
+ try {
+ groupsList.add( ReflectHelper.classForName( cleanedGroupName ) );
+ }
+ catch ( ClassNotFoundException e ) {
+ throw new HibernateException( "Unable to load class " + cleanedGroupName, e );
+ }
+ }
+
+ }
+ groups = groupsList.toArray( new Class>[groupsList.size()] );
+ }
+ }
+ else if ( property instanceof Class>[] ) {
+ groups = (Class>[]) property;
+ }
+ else {
+ //null is bad and excluded by instanceof => exception is raised
+ throw new HibernateException( JPA_GROUP_PREFIX + operation.getGroupPropertyName() + " is of unknown type: String or Class>[] only");
+ }
+ }
+ groupsPerOperation.put( operation, groups );
+ }
+
+ public boolean onPreInsert(PreInsertEvent event) {
+ validate( event.getEntity(), event.getSession().getEntityMode(), Operation.INSERT );
+ return false;
+ }
+
+ public boolean onPreUpdate(PreUpdateEvent event) {
+ validate( event.getEntity(), event.getSession().getEntityMode(), Operation.UPDATE );
+ return false;
+ }
+
+ public boolean onPreDelete(PreDeleteEvent event) {
+ validate( event.getEntity(), event.getSession().getEntityMode(), Operation.DELETE );
+ return false;
+ }
+
+ private void validate(T object, EntityMode mode, Operation operation) {
+ if ( object == null || mode != EntityMode.POJO ) return;
+ Validator validator = factory.usingContext()
+ //.traversableResolver( tr )
+ .getValidator();
+ final Class>[] groups = groupsPerOperation.get( operation );
+ if ( groups.length > 0 ) {
+ final Set> constraintViolations =
+ validator.validate( object, groups );
+ //FIXME CV should no longer be generics
+ Object unsafeViolations = constraintViolations;
+ if (constraintViolations.size() > 0 ) {
+ //FIXME add Set>
+ throw new ConstraintViolationException(
+ "Invalid object at " + operation.getName() + " time for groups " + toString( groups ),
+ (Set) unsafeViolations);
+ }
+ }
+ }
+
+ private String toString(Class>[] groups) {
+ StringBuilder toString = new StringBuilder( "[");
+ for ( Class> group : groups ) {
+ toString.append( group.getName() ).append( ", " );
+ }
+ toString.append( "]" );
+ return toString.toString();
+ }
+
+ private static enum Operation {
+ INSERT("persist", "pre-persist"),
+ UPDATE("update", "pre-update"),
+ DELETE("remove", "pre-remove");
+
+ private String exposedName;
+ private String groupPropertyName;
+
+ Operation(String exposedName, String groupProperty) {
+ this.exposedName = exposedName;
+ this.groupPropertyName = groupProperty;
+ }
+
+ public String getName() {
+ return exposedName;
+ }
+
+ public String getGroupPropertyName() {
+ return groupPropertyName;
+ }
+ }
+
+}
diff --git a/annotations/src/main/java/org/hibernate/cfg/beanvalidation/TypeSafeActivator.java b/annotations/src/main/java/org/hibernate/cfg/beanvalidation/TypeSafeActivator.java
new file mode 100644
index 0000000000..61bc99a26d
--- /dev/null
+++ b/annotations/src/main/java/org/hibernate/cfg/beanvalidation/TypeSafeActivator.java
@@ -0,0 +1,79 @@
+package org.hibernate.cfg.beanvalidation;
+
+import java.util.Map;
+import java.util.Arrays;
+import java.util.Properties;
+import javax.validation.ValidatorFactory;
+import javax.validation.Validation;
+
+import org.hibernate.HibernateException;
+import org.hibernate.event.EventListeners;
+import org.hibernate.event.PreInsertEventListener;
+import org.hibernate.event.PreUpdateEventListener;
+import org.hibernate.event.PreDeleteEventListener;
+
+/**
+ * @author Emmanuel Bernard
+ */
+class TypeSafeActivator {
+
+ private static final String FACTORY_PROPERTY = "javax.persistence.validation.factory";
+
+ public static void activateBeanValidation(EventListeners eventListeners, Properties properties) {
+ ValidatorFactory factory = getValidatorFactory( properties );
+ BeanValidationEventListener beanValidationEventListener = new BeanValidationEventListener( factory, properties );
+
+ {
+ PreInsertEventListener[] listeners = eventListeners.getPreInsertEventListeners();
+ int length = listeners.length + 1;
+ PreInsertEventListener[] newListeners = new PreInsertEventListener[length];
+ System.arraycopy( listeners, 0, newListeners, 0, length - 1 );
+ newListeners[length - 1] = beanValidationEventListener;
+ eventListeners.setPreInsertEventListeners( newListeners );
+ }
+
+ {
+ PreUpdateEventListener[] listeners = eventListeners.getPreUpdateEventListeners();
+ int length = listeners.length + 1;
+ PreUpdateEventListener[] newListeners = new PreUpdateEventListener[length];
+ System.arraycopy( listeners, 0, newListeners, 0, length - 1 );
+ newListeners[length - 1] = beanValidationEventListener;
+ eventListeners.setPreUpdateEventListeners( newListeners );
+ }
+
+ {
+ PreDeleteEventListener[] listeners = eventListeners.getPreDeleteEventListeners();
+ int length = listeners.length + 1;
+ PreDeleteEventListener[] newListeners = new PreDeleteEventListener[length];
+ System.arraycopy( listeners, 0, newListeners, 0, length - 1 );
+ newListeners[length - 1] = beanValidationEventListener;
+ eventListeners.setPreDeleteEventListeners( newListeners );
+ }
+ }
+
+ static ValidatorFactory getValidatorFactory(Map