HHH-7990 - Bootstrapping Hibernate fails if javax.validation API is on classpath but no provider

(cherry picked from commit 898bab28ca)

Conflicts:

	hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java
This commit is contained in:
Steve Ebersole 2013-02-22 12:22:12 -06:00
parent 3fa669831f
commit 2e18376fba
6 changed files with 415 additions and 462 deletions

View File

@ -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).
* <p/>
* 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<ValidationMode> 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();
}

View File

@ -25,23 +25,15 @@ package org.hibernate.cfg.beanvalidation;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set; import java.util.Set;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.cfg.Configuration; 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.engine.spi.SessionFactoryImplementor;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.integrator.spi.Integrator; import org.hibernate.integrator.spi.Integrator;
import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.config.ConfigurationHelper;
import org.hibernate.metamodel.source.MetadataImplementor; import org.hibernate.metamodel.source.MetadataImplementor;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.service.spi.SessionFactoryServiceRegistry; import org.hibernate.service.spi.SessionFactoryServiceRegistry;
@ -50,7 +42,10 @@ import org.hibernate.service.spi.SessionFactoryServiceRegistry;
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public class BeanValidationIntegrator implements Integrator { 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"; 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"; 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 ACTIVATOR_CLASS_NAME = "org.hibernate.cfg.beanvalidation.TypeSafeActivator";
private static final String DDL_METHOD = "applyDDL"; private static final String VALIDATE_SUPPLIED_FACTORY_METHOD_NAME = "validateSuppliedFactory";
private static final String ACTIVATE_METHOD = "activateBeanValidation"; private static final String ACTIVATE_METHOD_NAME = "activate";
private static final String VALIDATE_METHOD = "validateFactory";
/**
* 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) { public static void validateFactory(Object object) {
try { 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 { 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() ) { if ( ! validateMethod.isAccessible() ) {
validateMethod.setAccessible( true ); validateMethod.setAccessible( true );
} }
@ -101,291 +102,114 @@ public class BeanValidationIntegrator implements Integrator {
@Override @Override
public void integrate( public void integrate(
Configuration configuration, final Configuration configuration,
SessionFactoryImplementor sessionFactory, final SessionFactoryImplementor sessionFactory,
SessionFactoryServiceRegistry serviceRegistry) { final SessionFactoryServiceRegistry serviceRegistry) {
// determine requested validation modes. // IMPL NOTE : see the comments on ActivationContext.getValidationModes() as to why this is multi-valued...
final Set<ValidationMode> modes = ValidationMode.getModes( configuration.getProperties().get( MODE_PROPERTY ) ); final Set<ValidationMode> 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 ); 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 // see if the Bean Validation API is available on the classpath
boolean isBeanValidationAvailable; 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<ValidationMode> 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 { try {
classLoaderService.classForName( BV_CHECK_CLASS ); classLoaderService.classForName( BV_CHECK_CLASS );
isBeanValidationAvailable = true; return true;
} }
catch ( Exception e ) { catch (Exception e) {
isBeanValidationAvailable = false; 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<ValidationMode> 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 @Override
public void integrate( MetadataImplementor metadata, public void integrate(
SessionFactoryImplementor sessionFactory, MetadataImplementor metadata,
SessionFactoryServiceRegistry serviceRegistry ) {
// Properties props = sessionFactory.getProperties();
// final Set<ValidationMode> 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<ValidationMode> 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<ValidationMode> 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<ValidationMode> modes,
boolean beanValidationAvailable,
Class typeSafeActivatorClass,
Configuration configuration,
SessionFactoryImplementor sessionFactory, SessionFactoryImplementor sessionFactory,
SessionFactoryServiceRegistry serviceRegistry) { 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<ValidationMode> 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<ValidationMode> getModes(Object modeProperty) {
Set<ValidationMode> modes = new HashSet<ValidationMode>(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 );
}
}
}
} }
@Override @Override

View File

@ -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 );
}
}

View File

@ -23,15 +23,6 @@
*/ */
package org.hibernate.cfg.beanvalidation; 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.Validation;
import javax.validation.ValidatorFactory; import javax.validation.ValidatorFactory;
import javax.validation.constraints.Digits; import javax.validation.constraints.Digits;
@ -42,19 +33,29 @@ import javax.validation.constraints.Size;
import javax.validation.metadata.BeanDescriptor; import javax.validation.metadata.BeanDescriptor;
import javax.validation.metadata.ConstraintDescriptor; import javax.validation.metadata.ConstraintDescriptor;
import javax.validation.metadata.PropertyDescriptor; 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.jboss.logging.Logger;
import org.hibernate.AssertionFailure; import org.hibernate.AssertionFailure;
import org.hibernate.HibernateException;
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Environment;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.event.service.spi.EventListenerRegistry; import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.EventType; import org.hibernate.event.spi.EventType;
import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.ReflectHelper; import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.config.ConfigurationHelper;
import org.hibernate.mapping.Column; import org.hibernate.mapping.Column;
import org.hibernate.mapping.Component; import org.hibernate.mapping.Component;
import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.PersistentClass;
@ -64,6 +65,7 @@ import org.hibernate.mapping.SingleTableSubclass;
/** /**
* @author Emmanuel Bernard * @author Emmanuel Bernard
* @author Hardy Ferentschik * @author Hardy Ferentschik
* @author Steve Ebersole
*/ */
class TypeSafeActivator { class TypeSafeActivator {
@ -71,51 +73,97 @@ class TypeSafeActivator {
private static final String FACTORY_PROPERTY = "javax.persistence.validation.factory"; 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"}) @SuppressWarnings( {"UnusedDeclaration"})
public static void validateFactory(Object object) { public static void validateSuppliedFactory(Object object) {
if ( ! ValidatorFactory.class.isInstance( object ) ) { if ( ! ValidatorFactory.class.isInstance( object ) ) {
throw new HibernateException( throw new IntegrationException(
"Given object was not an instance of " + ValidatorFactory.class.getName() "Given object was not an instance of " + ValidatorFactory.class.getName()
+ "[" + object.getClass().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"}) @SuppressWarnings( {"UnusedDeclaration"})
public static void activateBeanValidation(EventListenerRegistry listenerRegistry, Configuration configuration) { public static void applyCallbackListeners(ValidatorFactory validatorFactory, ActivationContext activationContext) {
final Properties properties = configuration.getProperties(); final Set<ValidationMode> modes = activationContext.getValidationModes();
ValidatorFactory factory = getValidatorFactory( properties ); if ( ! ( modes.contains( ValidationMode.CALLBACK ) || modes.contains( ValidationMode.AUTO ) ) ) {
BeanValidationEventListener listener = new BeanValidationEventListener( return;
factory, properties }
// 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.addDuplicationStrategy( DuplicationStrategyImpl.INSTANCE );
listenerRegistry.appendListeners( EventType.PRE_INSERT, listener ); listenerRegistry.appendListeners( EventType.PRE_INSERT, listener );
listenerRegistry.appendListeners( EventType.PRE_UPDATE, listener ); listenerRegistry.appendListeners( EventType.PRE_UPDATE, listener );
listenerRegistry.appendListeners( EventType.PRE_DELETE, listener ); listenerRegistry.appendListeners( EventType.PRE_DELETE, listener );
listener.initialize( configuration ); listener.initialize( activationContext.getConfiguration() );
} }
// public static void activateBeanValidation( EventListenerRegistry listenerRegistry ) { @SuppressWarnings({"unchecked", "UnusedParameters"})
// final Properties properties = configuration.getProperties(); private static void applyRelationalConstraints(ValidatorFactory factory, ActivationContext activationContext) {
// ValidatorFactory factory = getValidatorFactory( properties ); final Properties properties = activationContext.getConfiguration().getProperties();
// BeanValidationEventListener listener = new BeanValidationEventListener( if ( ! ConfigurationHelper.getBoolean( BeanValidationIntegrator.APPLY_CONSTRAINTS, properties, true ) ){
// factory, properties LOG.debug( "Skipping application of relational constraints from legacy Hibernate Validator" );
// ); return;
// }
// listenerRegistry.addDuplicationStrategy( DuplicationStrategyImpl.INSTANCE );
// final Set<ValidationMode> modes = activationContext.getValidationModes();
// listenerRegistry.appendListeners( EventType.PRE_INSERT, listener ); if ( ! ( modes.contains( ValidationMode.DDL ) || modes.contains( ValidationMode.AUTO ) ) ) {
// listenerRegistry.appendListeners( EventType.PRE_UPDATE, listener ); return;
// listenerRegistry.appendListeners( EventType.PRE_DELETE, listener ); }
//
// listener.initialize( configuration ); applyRelationalConstraints(
// } activationContext.getConfiguration().createMappings().getClasses().values(),
properties,
activationContext.getServiceRegistry().getService( JdbcServices.class ).getDialect()
);
}
@SuppressWarnings( {"UnusedDeclaration"}) @SuppressWarnings( {"UnusedDeclaration"})
public static void applyDDL(Collection<PersistentClass> persistentClasses, Properties properties, Dialect dialect) { public static void applyRelationalConstraints(Collection<PersistentClass> persistentClasses, Properties properties, Dialect dialect) {
ValidatorFactory factory = getValidatorFactory( properties ); ValidatorFactory factory = getValidatorFactory( properties );
Class<?>[] groupsArray = new GroupsPerOperation( properties ).get( GroupsPerOperation.Operation.DDL ); Class<?>[] groupsArray = new GroupsPerOperation( properties ).get( GroupsPerOperation.Operation.DDL );
Set<Class<?>> groups = new HashSet<Class<?>>( Arrays.asList( groupsArray ) ); Set<Class<?>> groups = new HashSet<Class<?>>( Arrays.asList( groupsArray ) );
@ -143,32 +191,14 @@ class TypeSafeActivator {
} }
} }
// public static void applyDDL( Iterable<EntityBinding> bindings, private static void applyDDL(
// Properties properties, String prefix,
// ClassLoaderService classLoaderService ) { PersistentClass persistentClass,
// ValidatorFactory factory = getValidatorFactory(properties); Class<?> clazz,
// Class<?>[] groupsArray = new GroupsPerOperation(properties).get(GroupsPerOperation.Operation.DDL); ValidatorFactory factory,
// Set<Class<?>> groups = new HashSet<Class<?>>(Arrays.asList(groupsArray)); Set<Class<?>> groups,
// for (EntityBinding binding : bindings) { boolean activateNotNull,
// final String className = binding.getEntity().getClassName(); Dialect dialect) {
// 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<Class<?>> groups,
boolean activateNotNull,
Dialect dialect) {
final BeanDescriptor descriptor = factory.getValidator().getConstraintsForClass( clazz ); final BeanDescriptor descriptor = factory.getValidator().getConstraintsForClass( clazz );
//no bean level constraints can be applied, go to the properties //no bean level constraints can be applied, go to the properties
@ -200,30 +230,13 @@ class TypeSafeActivator {
} }
} }
// private static void applyDDL( String prefix, private static boolean applyConstraints(
// EntityBinding binding, Set<ConstraintDescriptor<?>> constraintDescriptors,
// Class<?> clazz, Property property,
// ValidatorFactory factory, PropertyDescriptor propertyDesc,
// Set<Class<?>> groups, Set<Class<?>> groups,
// boolean activateNotNull ) { boolean canApplyNotNull,
// final BeanDescriptor descriptor = factory.getValidator().getConstraintsForClass(clazz); Dialect dialect) {
// //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<ConstraintDescriptor<?>> constraintDescriptors,
Property property,
PropertyDescriptor propertyDesc,
Set<Class<?>> groups,
boolean canApplyNotNull,
Dialect dialect
) {
boolean hasNotNull = false; boolean hasNotNull = false;
for ( ConstraintDescriptor<?> descriptor : constraintDescriptors ) { for ( ConstraintDescriptor<?> descriptor : constraintDescriptors ) {
if ( groups != null && Collections.disjoint( descriptor.getGroups(), groups ) ) { if ( groups != null && Collections.disjoint( descriptor.getGroups(), groups ) ) {
@ -256,37 +269,6 @@ class TypeSafeActivator {
return hasNotNull; return hasNotNull;
} }
// private static boolean applyConstraints( Set<ConstraintDescriptor<?>> constraintDescriptors,
// AttributeBinding attributeBinding,
// PropertyDescriptor propertyDesc,
// Set<Class<?>> 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) { private static void applyMin(Property property, ConstraintDescriptor<?> descriptor, Dialect dialect) {
if ( Min.class.equals( descriptor.getAnnotation().annotationType() ) ) { if ( Min.class.equals( descriptor.getAnnotation().annotationType() ) ) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -339,26 +321,6 @@ class TypeSafeActivator {
return hasNotNull; 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<Column> iter = property.getColumnIterator();
// while ( iter.hasNext() ) {
// iter.next().setNullable( false );
// hasNotNull = true;
// }
// }
// }
// hasNotNull = true;
// }
// return hasNotNull;
// }
private static void applyDigits(Property property, ConstraintDescriptor<?> descriptor) { private static void applyDigits(Property property, ConstraintDescriptor<?> descriptor) {
if ( Digits.class.equals( descriptor.getAnnotation().annotationType() ) ) { if ( Digits.class.equals( descriptor.getAnnotation().annotationType() ) ) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -462,44 +424,6 @@ class TypeSafeActivator {
return property; return property;
} }
// /**
// * @param entityBinding
// * @param attrName
// * @return the attribute by path in a recursive way, including EntityIdentifier in the loop if attrName is
// * <code>null</code>. If attrName is <code>null</code> 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<Object, Object> properties) { private static ValidatorFactory getValidatorFactory(Map<Object, Object> properties) {
ValidatorFactory factory = null; ValidatorFactory factory = null;
if ( properties != null ) { if ( properties != null ) {
@ -509,7 +433,7 @@ class TypeSafeActivator {
factory = ValidatorFactory.class.cast( unsafeProperty ); factory = ValidatorFactory.class.cast( unsafeProperty );
} }
catch ( ClassCastException e ) { catch ( ClassCastException e ) {
throw new HibernateException( throw new IntegrationException(
"Property " + FACTORY_PROPERTY "Property " + FACTORY_PROPERTY
+ " should contain an object of type " + ValidatorFactory.class.getName() + " should contain an object of type " + ValidatorFactory.class.getName()
); );
@ -521,7 +445,7 @@ class TypeSafeActivator {
factory = Validation.buildDefaultValidatorFactory(); factory = Validation.buildDefaultValidatorFactory();
} }
catch ( Exception e ) { 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; return factory;

View File

@ -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<ValidationMode> getModes(Object modeProperty) {
Set<ValidationMode> modes = new HashSet<ValidationMode>(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<ValidationMode> modes) {
if ( modes == null || modes.isEmpty() ) {
return "[<empty>]";
}
StringBuilder buffer = new StringBuilder( "[" );
String sep = "";
for ( ValidationMode mode : modes ) {
buffer.append( sep ).append( mode.externalForm );
sep = ", ";
}
return buffer.append( "]" ).toString();
}
}

View File

@ -1616,4 +1616,7 @@ public interface CoreMessageLogger extends BasicLogger {
) )
void explicitSkipLockedLockCombo(); void explicitSkipLockedLockCombo();
@LogMessage(level = INFO)
@Message( value = "'javax.persistence.validation.mode' named multiple values : %s", id = 448 )
void multipleValidationModes(String modes);
} }