diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/ActivationContext.java b/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/ActivationContext.java new file mode 100644 index 0000000000..b80054e95f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/ActivationContext.java @@ -0,0 +1,70 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.cfg.beanvalidation; + +import java.util.Set; + +import org.hibernate.cfg.Configuration; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.service.spi.SessionFactoryServiceRegistry; + +/** + * Defines the context needed to call the {@link TypeSafeActivator} + * + * @author Steve Ebersole + */ +public interface ActivationContext { + /** + * Access the requested validation mode(s). + *

+ * IMPL NOTE : the legacy code allowed multiple mode values to be specified, so that is why it is multi-valued here. + * However, I cannot find any good reasoning why it was defined that way and even JPA states it should be a single + * value. For 4.1 (in maintenance) I think it makes the most sense to not mess with it. Discuss for + * 4.2 and beyond. + * + * @return The requested validation modes + */ + public Set getValidationModes(); + + /** + * Access the Configuration + * + * @return The Hibernate Configuration object + */ + public Configuration getConfiguration(); + + /** + * Access the SessionFactory being built to trigger this BV activation + * + * @return The SessionFactory being built + */ + public SessionFactoryImplementor getSessionFactory(); + + /** + * Access the ServiceRegistry specific to the SessionFactory being built. + * + * @return The SessionFactoryServiceRegistry + */ + public SessionFactoryServiceRegistry getServiceRegistry(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/BeanValidationIntegrator.java b/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/BeanValidationIntegrator.java index 927e4d6f8f..7aa074000d 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/BeanValidationIntegrator.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/BeanValidationIntegrator.java @@ -25,23 +25,15 @@ package org.hibernate.cfg.beanvalidation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.Collection; -import java.util.HashSet; -import java.util.Properties; import java.util.Set; import org.jboss.logging.Logger; import org.hibernate.HibernateException; import org.hibernate.cfg.Configuration; -import org.hibernate.cfg.Environment; -import org.hibernate.dialect.Dialect; -import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.event.service.spi.EventListenerRegistry; import org.hibernate.integrator.spi.Integrator; import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.metamodel.source.MetadataImplementor; import org.hibernate.service.classloading.spi.ClassLoaderService; import org.hibernate.service.spi.SessionFactoryServiceRegistry; @@ -50,7 +42,10 @@ import org.hibernate.service.spi.SessionFactoryServiceRegistry; * @author Steve Ebersole */ public class BeanValidationIntegrator implements Integrator { - private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, BeanValidationIntegrator.class.getName()); + private static final CoreMessageLogger LOG = Logger.getMessageLogger( + CoreMessageLogger.class, + BeanValidationIntegrator.class.getName() + ); public static final String APPLY_CONSTRAINTS = "hibernate.validator.apply_to_ddl"; @@ -58,16 +53,22 @@ public class BeanValidationIntegrator implements Integrator { public static final String MODE_PROPERTY = "javax.persistence.validation.mode"; - private static final String ACTIVATOR_CLASS = "org.hibernate.cfg.beanvalidation.TypeSafeActivator"; - private static final String DDL_METHOD = "applyDDL"; - private static final String ACTIVATE_METHOD = "activateBeanValidation"; - private static final String VALIDATE_METHOD = "validateFactory"; + private static final String ACTIVATOR_CLASS_NAME = "org.hibernate.cfg.beanvalidation.TypeSafeActivator"; + private static final String VALIDATE_SUPPLIED_FACTORY_METHOD_NAME = "validateSuppliedFactory"; + private static final String ACTIVATE_METHOD_NAME = "activate"; + /** + * Used to validate the type of an explicitly passed ValidatorFactory instance + * + * @param object The supposed ValidatorFactory instance + */ + @SuppressWarnings("unchecked") public static void validateFactory(Object object) { try { - final Class activatorClass = BeanValidationIntegrator.class.getClassLoader().loadClass( ACTIVATOR_CLASS ); + // this direct usage of ClassLoader should be fine since the classes exist in the same jar + final Class activatorClass = BeanValidationIntegrator.class.getClassLoader().loadClass( ACTIVATOR_CLASS_NAME ); try { - final Method validateMethod = activatorClass.getMethod( VALIDATE_METHOD, Object.class ); + final Method validateMethod = activatorClass.getMethod( VALIDATE_SUPPLIED_FACTORY_METHOD_NAME, Object.class ); if ( ! validateMethod.isAccessible() ) { validateMethod.setAccessible( true ); } @@ -101,291 +102,114 @@ public class BeanValidationIntegrator implements Integrator { @Override public void integrate( - Configuration configuration, - SessionFactoryImplementor sessionFactory, - SessionFactoryServiceRegistry serviceRegistry) { - // determine requested validation modes. + final Configuration configuration, + final SessionFactoryImplementor sessionFactory, + final SessionFactoryServiceRegistry serviceRegistry) { + // IMPL NOTE : see the comments on ActivationContext.getValidationModes() as to why this is multi-valued... final Set modes = ValidationMode.getModes( configuration.getProperties().get( MODE_PROPERTY ) ); + if ( modes.size() > 1 ) { + LOG.multipleValidationModes( ValidationMode.loggable( modes ) ); + } + if ( modes.size() == 1 && modes.contains( ValidationMode.NONE ) ) { + // we have nothing to do; just return + return; + } final ClassLoaderService classLoaderService = serviceRegistry.getService( ClassLoaderService.class ); - Dialect dialect = serviceRegistry.getService( JdbcServices.class ).getDialect(); - // try to locate a BV class to see if it is available on the classpath - boolean isBeanValidationAvailable; + + // see if the Bean Validation API is available on the classpath + if ( isBeanValidationApiAvailable( classLoaderService ) ) { + // and if so, call out to the TypeSafeActivator + try { + final Class typeSafeActivatorClass = loadTypeSafeActivatorClass( classLoaderService ); + @SuppressWarnings("unchecked") + final Method activateMethod = typeSafeActivatorClass.getMethod( ACTIVATE_METHOD_NAME, ActivationContext.class ); + final ActivationContext activationContext = new ActivationContext() { + @Override + public Set getValidationModes() { + return modes; + } + + @Override + public Configuration getConfiguration() { + return configuration; + } + + @Override + public SessionFactoryImplementor getSessionFactory() { + return sessionFactory; + } + + @Override + public SessionFactoryServiceRegistry getServiceRegistry() { + return serviceRegistry; + } + }; + + try { + activateMethod.invoke( null, activationContext ); + } + catch (InvocationTargetException e) { + if ( HibernateException.class.isInstance( e.getTargetException() ) ) { + throw ( (HibernateException) e.getTargetException() ); + } + throw new IntegrationException( "Error activating Bean Validation integration", e.getTargetException() ); + } + catch (Exception e) { + throw new IntegrationException( "Error activating Bean Validation integration", e ); + } + } + catch (NoSuchMethodException e) { + throw new HibernateException( "Unable to locate TypeSafeActivator#activate method", e ); + } + } + else { + // otherwise check the validation modes + // todo : in many ways this duplicates thew checks done on the TypeSafeActivator when a ValidatorFactory could not be obtained + validateMissingBeanValidationApi( modes ); + } + } + + private boolean isBeanValidationApiAvailable(ClassLoaderService classLoaderService) { try { classLoaderService.classForName( BV_CHECK_CLASS ); - isBeanValidationAvailable = true; + return true; } - catch ( Exception e ) { - isBeanValidationAvailable = false; + catch (Exception e) { + return false; } - - // locate the type safe activator class - final Class typeSafeActivatorClass = loadTypeSafeActivatorClass( serviceRegistry ); - - // todo : if this works out, probably better to simply alter TypeSafeActivator into a single method... - applyRelationalConstraints( - modes, - isBeanValidationAvailable, - typeSafeActivatorClass, - configuration, - dialect - - ); - applyHibernateListeners( - modes, - isBeanValidationAvailable, - typeSafeActivatorClass, - configuration, - sessionFactory, - serviceRegistry - ); } /** - * {@inheritDoc} + * Used to validate the case when the Bean Validation API is not available. * - * @see org.hibernate.integrator.spi.Integrator#integrate(org.hibernate.metamodel.source.MetadataImplementor, org.hibernate.engine.spi.SessionFactoryImplementor, org.hibernate.service.spi.SessionFactoryServiceRegistry) + * @param modes The requested validation modes. */ + private void validateMissingBeanValidationApi(Set modes) { + if ( modes.contains( ValidationMode.CALLBACK ) ) { + throw new IntegrationException( "Bean Validation API was not available, but 'callback' validation was requested" ); + } + if ( modes.contains( ValidationMode.DDL ) ) { + throw new IntegrationException( "Bean Validation API was not available, but 'ddl' validation was requested" ); + } + } + + private Class loadTypeSafeActivatorClass(ClassLoaderService classLoaderService) { + try { + return classLoaderService.classForName( ACTIVATOR_CLASS_NAME ); + } + catch (Exception e) { + throw new HibernateException( "Unable to load TypeSafeActivator class", e ); + } + } + + + @Override - public void integrate( MetadataImplementor metadata, - SessionFactoryImplementor sessionFactory, - SessionFactoryServiceRegistry serviceRegistry ) { -// Properties props = sessionFactory.getProperties(); -// final Set modes = ValidationMode.getModes(props.get(MODE_PROPERTY)); -// final ClassLoaderService classLoaderService = serviceRegistry.getService( ClassLoaderService.class ); -// // try to locate a BV class to see if it is available on the classpath -// boolean isBeanValidationAvailable; -// try { -// classLoaderService.classForName( BV_CHECK_CLASS ); -// isBeanValidationAvailable = true; -// } catch (Exception error) { -// isBeanValidationAvailable = false; -// } -// // locate the type safe activator class -// final Class typeSafeActivatorClass = loadTypeSafeActivatorClass(serviceRegistry); -// // todo : if this works out, probably better to simply alter TypeSafeActivator into a single method... -// applyRelationalConstraints(modes, isBeanValidationAvailable, typeSafeActivatorClass, props, metadata); -// applyHibernateListeners(modes, isBeanValidationAvailable, typeSafeActivatorClass, sessionFactory, serviceRegistry); - } - - private Class loadTypeSafeActivatorClass(SessionFactoryServiceRegistry serviceRegistry) { - try { - return serviceRegistry.getService( ClassLoaderService.class ).classForName( ACTIVATOR_CLASS ); - } - catch (Exception e) { - return null; - } - } - - private void applyRelationalConstraints( - Set modes, - boolean beanValidationAvailable, - Class typeSafeActivatorClass, - Configuration configuration, - Dialect dialect) { - if ( ! ConfigurationHelper.getBoolean( APPLY_CONSTRAINTS, configuration.getProperties(), true ) ){ - LOG.debug( "Skipping application of relational constraints from legacy Hibernate Validator" ); - return; - } - - if ( ! ( modes.contains( ValidationMode.DDL ) || modes.contains( ValidationMode.AUTO ) ) ) { - return; - } - - if ( ! beanValidationAvailable ) { - if ( modes.contains( ValidationMode.DDL ) ) { - throw new HibernateException( "Bean Validation not available in the class path but required in " + MODE_PROPERTY ); - } - else if (modes.contains( ValidationMode.AUTO ) ) { - //nothing to activate - return; - } - } - - try { - Method applyDDLMethod = typeSafeActivatorClass.getMethod( DDL_METHOD, Collection.class, Properties.class, Dialect.class ); - try { - applyDDLMethod.invoke( - null, - configuration.createMappings().getClasses().values(), - configuration.getProperties(), - dialect - ); - } - catch (HibernateException e) { - throw e; - } - catch (Exception e) { - throw new HibernateException( "Error applying BeanValidation relational constraints", e ); - } - } - catch (HibernateException e) { - throw e; - } - catch (Exception e) { - throw new HibernateException( "Unable to locate TypeSafeActivator#applyDDL method", e ); - } - } - -// private void applyRelationalConstraints( Set modes, -// boolean beanValidationAvailable, -// Class typeSafeActivatorClass, -// Properties properties, -// MetadataImplementor metadata ) { -// if (!ConfigurationHelper.getBoolean(APPLY_CONSTRAINTS, properties, true)){ -// LOG.debug("Skipping application of relational constraints from legacy Hibernate Validator"); -// return; -// } -// if (!(modes.contains(ValidationMode.DDL) || modes.contains(ValidationMode.AUTO))) return; -// if (!beanValidationAvailable) { -// if (modes.contains(ValidationMode.DDL)) -// throw new HibernateException("Bean Validation not available in the class path but required in " + MODE_PROPERTY); -// if(modes.contains(ValidationMode.AUTO)) return; //nothing to activate -// } -// try { -// Method applyDDLMethod = typeSafeActivatorClass.getMethod(DDL_METHOD, Iterable.class, Properties.class, ClassLoaderService.class); -// try { -// applyDDLMethod.invoke(null, metadata.getEntityBindings(), properties, -// metadata.getServiceRegistry().getService(ClassLoaderService.class)); -// } catch (HibernateException error) { -// throw error; -// } catch (Exception error) { -// throw new HibernateException("Error applying BeanValidation relational constraints", error); -// } -// } catch (HibernateException error) { -// throw error; -// } catch (Exception error) { -// throw new HibernateException("Unable to locate TypeSafeActivator#applyDDL method", error); -// } -// } - - private void applyHibernateListeners( - Set modes, - boolean beanValidationAvailable, - Class typeSafeActivatorClass, - Configuration configuration, + public void integrate( + MetadataImplementor metadata, SessionFactoryImplementor sessionFactory, - SessionFactoryServiceRegistry serviceRegistry) { - // de-activate not-null tracking at the core level when Bean Validation is present unless the user explicitly - // asks for it - if ( configuration.getProperty( Environment.CHECK_NULLABILITY ) == null ) { - sessionFactory.getSettings().setCheckNullability( false ); - } - - if ( ! ( modes.contains( ValidationMode.CALLBACK ) || modes.contains( ValidationMode.AUTO ) ) ) { - return; - } - - if ( ! beanValidationAvailable ) { - if ( modes.contains( ValidationMode.CALLBACK ) ) { - throw new HibernateException( "Bean Validation not available in the class path but required in " + MODE_PROPERTY ); - } - else if (modes.contains( ValidationMode.AUTO ) ) { - //nothing to activate - return; - } - } - - try { - Method activateMethod = typeSafeActivatorClass.getMethod( ACTIVATE_METHOD, EventListenerRegistry.class, Configuration.class ); - try { - activateMethod.invoke( - null, - serviceRegistry.getService( EventListenerRegistry.class ), - configuration - ); - } - catch (HibernateException e) { - throw e; - } - catch (Exception e) { - throw new HibernateException( "Error applying BeanValidation relational constraints", e ); - } - } - catch (HibernateException e) { - throw e; - } - catch (Exception e) { - throw new HibernateException( "Unable to locate TypeSafeActivator#applyDDL method", e ); - } - } - -// private void applyHibernateListeners( Set modes, -// boolean beanValidationAvailable, -// Class typeSafeActivatorClass, -// SessionFactoryImplementor sessionFactory, -// SessionFactoryServiceRegistry serviceRegistry ) { -// // de-activate not-null tracking at the core level when Bean Validation is present unless the user explicitly -// // asks for it -// if (sessionFactory.getProperties().getProperty(Environment.CHECK_NULLABILITY) == null) -// sessionFactory.getSettings().setCheckNullability( false ); -// if (!(modes.contains( ValidationMode.CALLBACK) || modes.contains(ValidationMode.AUTO))) return; -// if (!beanValidationAvailable) { -// if (modes.contains(ValidationMode.CALLBACK)) -// throw new HibernateException("Bean Validation not available in the class path but required in " + MODE_PROPERTY); -// if (modes.contains(ValidationMode.AUTO)) return; //nothing to activate -// } -// try { -// Method activateMethod = typeSafeActivatorClass.getMethod(ACTIVATE_METHOD, EventListenerRegistry.class); -// try { -// activateMethod.invoke(null, serviceRegistry.getService(EventListenerRegistry.class)); -// } -// catch (HibernateException e) { -// throw e; -// } -// catch (Exception e) { -// throw new HibernateException( "Error applying BeanValidation relational constraints", e ); -// } -// } -// catch (HibernateException e) { -// throw e; -// } -// catch (Exception e) { -// throw new HibernateException( "Unable to locate TypeSafeActivator#applyDDL method", e ); -// } -// } - - // Because the javax validation classes might not be on the runtime classpath - private static enum ValidationMode { - AUTO, - CALLBACK, - NONE, - DDL; - - public static Set getModes(Object modeProperty) { - Set modes = new HashSet(3); - if (modeProperty == null) { - modes.add(ValidationMode.AUTO); - } - else { - final String[] modesInString = modeProperty.toString().split( "," ); - for ( String modeInString : modesInString ) { - modes.add( getMode(modeInString) ); - } - } - if ( modes.size() > 1 && ( modes.contains( ValidationMode.AUTO ) || modes.contains( ValidationMode.NONE ) ) ) { - StringBuilder message = new StringBuilder( "Incompatible validation modes mixed: " ); - for (ValidationMode mode : modes) { - message.append( mode ).append( ", " ); - } - throw new HibernateException( message.substring( 0, message.length() - 2 ) ); - } - return modes; - } - - private static ValidationMode getMode(String modeProperty) { - if (modeProperty == null || modeProperty.length() == 0) { - return AUTO; - } - else { - try { - return valueOf( modeProperty.trim().toUpperCase() ); - } - catch ( IllegalArgumentException e ) { - throw new HibernateException( "Unknown validation mode in " + MODE_PROPERTY + ": " + modeProperty ); - } - } - } + SessionFactoryServiceRegistry serviceRegistry ) { } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/IntegrationException.java b/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/IntegrationException.java new file mode 100644 index 0000000000..782fae2e1c --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/IntegrationException.java @@ -0,0 +1,41 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.cfg.beanvalidation; + +import org.hibernate.HibernateException; + +/** + * Indicates a problem integrating Hibernate and the Bean Validation spec. + * + * @author Steve Ebersole + */ +public class IntegrationException extends HibernateException { + public IntegrationException(String message) { + super( message ); + } + + public IntegrationException(String message, Throwable root) { + super( message, root ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/TypeSafeActivator.java b/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/TypeSafeActivator.java index d4bba1cec4..fb5733adc8 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/TypeSafeActivator.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/TypeSafeActivator.java @@ -23,15 +23,6 @@ */ package org.hibernate.cfg.beanvalidation; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Properties; -import java.util.Set; -import java.util.StringTokenizer; import javax.validation.Validation; import javax.validation.ValidatorFactory; import javax.validation.constraints.Digits; @@ -42,19 +33,29 @@ import javax.validation.constraints.Size; import javax.validation.metadata.BeanDescriptor; import javax.validation.metadata.ConstraintDescriptor; import javax.validation.metadata.PropertyDescriptor; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.StringTokenizer; import org.jboss.logging.Logger; import org.hibernate.AssertionFailure; -import org.hibernate.HibernateException; import org.hibernate.MappingException; -import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.event.service.spi.EventListenerRegistry; import org.hibernate.event.spi.EventType; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.ReflectHelper; import org.hibernate.internal.util.StringHelper; +import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.mapping.Column; import org.hibernate.mapping.Component; import org.hibernate.mapping.PersistentClass; @@ -64,6 +65,7 @@ import org.hibernate.mapping.SingleTableSubclass; /** * @author Emmanuel Bernard * @author Hardy Ferentschik + * @author Steve Ebersole */ class TypeSafeActivator { @@ -71,51 +73,97 @@ class TypeSafeActivator { private static final String FACTORY_PROPERTY = "javax.persistence.validation.factory"; + /** + * Used to validate a supplied ValidatorFactory instance as being castable to ValidatorFactory. + * + * @param object The supplied ValidatorFactory instance. + */ @SuppressWarnings( {"UnusedDeclaration"}) - public static void validateFactory(Object object) { + public static void validateSuppliedFactory(Object object) { if ( ! ValidatorFactory.class.isInstance( object ) ) { - throw new HibernateException( + throw new IntegrationException( "Given object was not an instance of " + ValidatorFactory.class.getName() + "[" + object.getClass().getName() + "]" ); } } + @SuppressWarnings("UnusedDeclaration") + public static void activate(ActivationContext activationContext) { + final Properties properties = activationContext.getConfiguration().getProperties(); + final ValidatorFactory factory; + try { + factory = getValidatorFactory( properties ); + } + catch (IntegrationException e) { + if ( activationContext.getValidationModes().contains( ValidationMode.CALLBACK ) ) { + throw new IntegrationException( "Bean Validation provider was not available, but 'callback' validation was requested", e ); + } + if ( activationContext.getValidationModes().contains( ValidationMode.DDL ) ) { + throw new IntegrationException( "Bean Validation provider was not available, but 'ddl' validation was requested", e ); + } + + LOG.debug( "Unable to acquire Bean Validation ValidatorFactory, skipping activation" ); + return; + } + + applyRelationalConstraints( factory, activationContext ); + + applyCallbackListeners( factory, activationContext ); + } + @SuppressWarnings( {"UnusedDeclaration"}) - public static void activateBeanValidation(EventListenerRegistry listenerRegistry, Configuration configuration) { - final Properties properties = configuration.getProperties(); - ValidatorFactory factory = getValidatorFactory( properties ); - BeanValidationEventListener listener = new BeanValidationEventListener( - factory, properties + public static void applyCallbackListeners(ValidatorFactory validatorFactory, ActivationContext activationContext) { + final Set modes = activationContext.getValidationModes(); + if ( ! ( modes.contains( ValidationMode.CALLBACK ) || modes.contains( ValidationMode.AUTO ) ) ) { + return; + } + + // de-activate not-null tracking at the core level when Bean Validation is present unless the user explicitly + // asks for it + if ( activationContext.getConfiguration().getProperty( Environment.CHECK_NULLABILITY ) == null ) { + activationContext.getSessionFactory().getSettings().setCheckNullability( false ); + } + + final BeanValidationEventListener listener = new BeanValidationEventListener( + validatorFactory, + activationContext.getConfiguration().getProperties() ); + final EventListenerRegistry listenerRegistry = activationContext.getServiceRegistry() + .getService( EventListenerRegistry.class ); + listenerRegistry.addDuplicationStrategy( DuplicationStrategyImpl.INSTANCE ); listenerRegistry.appendListeners( EventType.PRE_INSERT, listener ); listenerRegistry.appendListeners( EventType.PRE_UPDATE, listener ); listenerRegistry.appendListeners( EventType.PRE_DELETE, listener ); - listener.initialize( configuration ); + listener.initialize( activationContext.getConfiguration() ); } -// public static void activateBeanValidation( EventListenerRegistry listenerRegistry ) { -// final Properties properties = configuration.getProperties(); -// ValidatorFactory factory = getValidatorFactory( properties ); -// BeanValidationEventListener listener = new BeanValidationEventListener( -// factory, properties -// ); -// -// listenerRegistry.addDuplicationStrategy( DuplicationStrategyImpl.INSTANCE ); -// -// listenerRegistry.appendListeners( EventType.PRE_INSERT, listener ); -// listenerRegistry.appendListeners( EventType.PRE_UPDATE, listener ); -// listenerRegistry.appendListeners( EventType.PRE_DELETE, listener ); -// -// listener.initialize( configuration ); -// } + @SuppressWarnings({"unchecked", "UnusedParameters"}) + private static void applyRelationalConstraints(ValidatorFactory factory, ActivationContext activationContext) { + final Properties properties = activationContext.getConfiguration().getProperties(); + if ( ! ConfigurationHelper.getBoolean( BeanValidationIntegrator.APPLY_CONSTRAINTS, properties, true ) ){ + LOG.debug( "Skipping application of relational constraints from legacy Hibernate Validator" ); + return; + } + + final Set modes = activationContext.getValidationModes(); + if ( ! ( modes.contains( ValidationMode.DDL ) || modes.contains( ValidationMode.AUTO ) ) ) { + return; + } + + applyRelationalConstraints( + activationContext.getConfiguration().createMappings().getClasses().values(), + properties, + activationContext.getServiceRegistry().getService( JdbcServices.class ).getDialect() + ); + } @SuppressWarnings( {"UnusedDeclaration"}) - public static void applyDDL(Collection persistentClasses, Properties properties, Dialect dialect) { + public static void applyRelationalConstraints(Collection persistentClasses, Properties properties, Dialect dialect) { ValidatorFactory factory = getValidatorFactory( properties ); Class[] groupsArray = new GroupsPerOperation( properties ).get( GroupsPerOperation.Operation.DDL ); Set> groups = new HashSet>( Arrays.asList( groupsArray ) ); @@ -143,32 +191,14 @@ class TypeSafeActivator { } } -// public static void applyDDL( Iterable bindings, -// Properties properties, -// ClassLoaderService classLoaderService ) { -// ValidatorFactory factory = getValidatorFactory(properties); -// Class[] groupsArray = new GroupsPerOperation(properties).get(GroupsPerOperation.Operation.DDL); -// Set> groups = new HashSet>(Arrays.asList(groupsArray)); -// for (EntityBinding binding : bindings) { -// final String className = binding.getEntity().getClassName(); -// if (className == null || className.length() == 0) continue; -// try { -// applyDDL("", binding, classLoaderService.classForName(className), factory, groups, true); -// } catch (ClassLoadingException error) { -// throw new AssertionFailure("Entity class not found", error); -// } catch (Exception error) { -// LOG.unableToApplyConstraints(className, error); -// } -// } -// } - - private static void applyDDL(String prefix, - PersistentClass persistentClass, - Class clazz, - ValidatorFactory factory, - Set> groups, - boolean activateNotNull, - Dialect dialect) { + private static void applyDDL( + String prefix, + PersistentClass persistentClass, + Class clazz, + ValidatorFactory factory, + Set> groups, + boolean activateNotNull, + Dialect dialect) { final BeanDescriptor descriptor = factory.getValidator().getConstraintsForClass( clazz ); //no bean level constraints can be applied, go to the properties @@ -200,30 +230,13 @@ class TypeSafeActivator { } } -// private static void applyDDL( String prefix, -// EntityBinding binding, -// Class clazz, -// ValidatorFactory factory, -// Set> groups, -// boolean activateNotNull ) { -// final BeanDescriptor descriptor = factory.getValidator().getConstraintsForClass(clazz); -// //no bean level constraints can be applied, go to the properties -// for (PropertyDescriptor propertyDesc : descriptor.getConstrainedProperties()) { -// AttributeBinding attrBinding = findAttributeBindingByName(binding, prefix + propertyDesc.getPropertyName()); -// if (attrBinding != null) { -// applyConstraints(propertyDesc.getConstraintDescriptors(), attrBinding, propertyDesc, groups, activateNotNull); -// // TODO: Handle composite attributes when possible -// } -// } -// } - - private static boolean applyConstraints(Set> constraintDescriptors, - Property property, - PropertyDescriptor propertyDesc, - Set> groups, - boolean canApplyNotNull, - Dialect dialect - ) { + private static boolean applyConstraints( + Set> constraintDescriptors, + Property property, + PropertyDescriptor propertyDesc, + Set> groups, + boolean canApplyNotNull, + Dialect dialect) { boolean hasNotNull = false; for ( ConstraintDescriptor descriptor : constraintDescriptors ) { if ( groups != null && Collections.disjoint( descriptor.getGroups(), groups ) ) { @@ -256,37 +269,6 @@ class TypeSafeActivator { return hasNotNull; } -// private static boolean applyConstraints( Set> constraintDescriptors, -// AttributeBinding attributeBinding, -// PropertyDescriptor propertyDesc, -// Set> groups, -// boolean canApplyNotNull ) { -// boolean hasNotNull = false; -// for ( ConstraintDescriptor descriptor : constraintDescriptors ) { -// if (groups != null && Collections.disjoint(descriptor.getGroups(), groups)) continue; -// if (canApplyNotNull) hasNotNull = hasNotNull || applyNotNull(attributeBinding, descriptor); -// -// // apply bean validation specific constraints -// applyDigits( property, descriptor ); -// applySize( property, descriptor, propertyDesc ); -// applyMin( property, descriptor ); -// applyMax( property, descriptor ); -// -// // apply hibernate validator specific constraints - we cannot import any HV specific classes though! -// // no need to check explicitly for @Range. @Range is a composed constraint using @Min and @Max which -// // will be taken care later -// applyLength( property, descriptor, propertyDesc ); -// -// // pass an empty set as composing constraints inherit the main constraint and thus are matching already -// hasNotNull = hasNotNull || applyConstraints( -// descriptor.getComposingConstraints(), -// property, propertyDesc, null, -// canApplyNotNull -// ); -// } -// return hasNotNull; -// } - private static void applyMin(Property property, ConstraintDescriptor descriptor, Dialect dialect) { if ( Min.class.equals( descriptor.getAnnotation().annotationType() ) ) { @SuppressWarnings("unchecked") @@ -339,26 +321,6 @@ class TypeSafeActivator { return hasNotNull; } -// private static boolean applyNotNull( AttributeBinding attributeBinding, -// ConstraintDescriptor descriptor ) { -// boolean hasNotNull = false; -// if (NotNull.class.equals(descriptor.getAnnotation().annotationType())) { -// if ( !( attributeBinding.getPersistentClass() instanceof SingleTableSubclass ) ) { -// //single table should not be forced to null -// if ( !property.isComposite() ) { //composite should not add not-null on all columns -// @SuppressWarnings( "unchecked" ) -// Iterator iter = property.getColumnIterator(); -// while ( iter.hasNext() ) { -// iter.next().setNullable( false ); -// hasNotNull = true; -// } -// } -// } -// hasNotNull = true; -// } -// return hasNotNull; -// } - private static void applyDigits(Property property, ConstraintDescriptor descriptor) { if ( Digits.class.equals( descriptor.getAnnotation().annotationType() ) ) { @SuppressWarnings("unchecked") @@ -462,44 +424,6 @@ class TypeSafeActivator { return property; } -// /** -// * @param entityBinding -// * @param attrName -// * @return the attribute by path in a recursive way, including EntityIdentifier in the loop if attrName is -// * null. If attrName is null or empty, the EntityIdentifier is returned -// */ -// private static AttributeBinding findAttributeBindingByName( EntityBinding entityBinding, -// String attrName ) { -// AttributeBinding attrBinding = null; -// EntityIdentifier identifier = entityBinding.getHierarchyDetails().getEntityIdentifier(); -// BasicAttributeBinding idAttrBinding = identifier.getValueBinding(); -// String idAttrName = idAttrBinding != null ? idAttrBinding.getAttribute().getName() : null; -// try { -// if (attrName == null || attrName.length() == 0 || attrName.equals(idAttrName)) attrBinding = idAttrBinding; // default to id -// else { -// if (attrName.indexOf(idAttrName + ".") == 0) { -// attrBinding = idAttrBinding; -// attrName = attrName.substring(idAttrName.length() + 1); -// } -// for (StringTokenizer st = new StringTokenizer(attrName, "."); st.hasMoreElements();) { -// String element = st.nextToken(); -// if (attrBinding == null) attrBinding = entityBinding.locateAttributeBinding(element); -// else return null; // TODO: if (attrBinding.isComposite()) ... -// } -// } -// } catch (MappingException error) { -// try { -// //if we do not find it try to check the identifier mapper -// if (!identifier.isIdentifierMapper()) return null; -// // TODO: finish once composite/embedded/component IDs get worked out -// } -// catch ( MappingException ee ) { -// return null; -// } -// } -// return attrBinding; -// } - private static ValidatorFactory getValidatorFactory(Map properties) { ValidatorFactory factory = null; if ( properties != null ) { @@ -509,7 +433,7 @@ class TypeSafeActivator { factory = ValidatorFactory.class.cast( unsafeProperty ); } catch ( ClassCastException e ) { - throw new HibernateException( + throw new IntegrationException( "Property " + FACTORY_PROPERTY + " should contain an object of type " + ValidatorFactory.class.getName() ); @@ -521,7 +445,7 @@ class TypeSafeActivator { factory = Validation.buildDefaultValidatorFactory(); } catch ( Exception e ) { - throw new HibernateException( "Unable to build the default ValidatorFactory", e ); + throw new IntegrationException( "Unable to build the default ValidatorFactory", e ); } } return factory; diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/ValidationMode.java b/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/ValidationMode.java new file mode 100644 index 0000000000..29bbda0413 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cfg/beanvalidation/ValidationMode.java @@ -0,0 +1,91 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.cfg.beanvalidation; + +import java.util.HashSet; +import java.util.Set; + +import org.hibernate.HibernateException; + +/** + * Duplicates the javax.validation enum (because javax validation might not be on the runtime classpath) + * + * @author Steve Ebersole + */ +public enum ValidationMode { + AUTO( "auto" ), + CALLBACK( "callback" ), + NONE( "none" ), + DDL( "ddl" ); + + private final String externalForm; + + private ValidationMode(String externalForm) { + this.externalForm = externalForm; + } + + public static Set getModes(Object modeProperty) { + Set modes = new HashSet(3); + if (modeProperty == null) { + modes.add( ValidationMode.AUTO ); + } + else { + final String[] modesInString = modeProperty.toString().split( "," ); + for ( String modeInString : modesInString ) { + modes.add( getMode(modeInString) ); + } + } + if ( modes.size() > 1 && ( modes.contains( ValidationMode.AUTO ) || modes.contains( ValidationMode.NONE ) ) ) { + throw new HibernateException( "Incompatible validation modes mixed: " + loggable( modes ) ); + } + return modes; + } + + private static ValidationMode getMode(String modeProperty) { + if (modeProperty == null || modeProperty.length() == 0) { + return AUTO; + } + else { + try { + return valueOf( modeProperty.trim().toUpperCase() ); + } + catch ( IllegalArgumentException e ) { + throw new HibernateException( "Unknown validation mode in " + BeanValidationIntegrator.MODE_PROPERTY + ": " + modeProperty ); + } + } + } + + public static String loggable(Set modes) { + if ( modes == null || modes.isEmpty() ) { + return "[]"; + } + StringBuilder buffer = new StringBuilder( "[" ); + String sep = ""; + for ( ValidationMode mode : modes ) { + buffer.append( sep ).append( mode.externalForm ); + sep = ", "; + } + return buffer.append( "]" ).toString(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java index c1b09d9855..2b9c694218 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java @@ -1609,4 +1609,7 @@ public interface CoreMessageLogger extends BasicLogger { ) void embedXmlAttributesNoLongerSupported(); + @LogMessage(level = INFO) + @Message( value = "'javax.persistence.validation.mode' named multiple values : %s", id = 447 ) + void multipleValidationModes(String modes); }