/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2011, 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.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.HibernateLogger;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.spi.Integrator;
import org.hibernate.internal.util.config.ConfigurationHelper;
import org.hibernate.service.classloading.spi.ClassLoaderService;
import org.hibernate.service.event.spi.EventListenerRegistry;
import org.hibernate.service.spi.SessionFactoryServiceRegistry;
/**
* @author Steve Ebersole
*/
public class BeanValidationIntegrator implements Integrator {
private static final HibernateLogger LOG = Logger.getMessageLogger(HibernateLogger.class, BeanValidationIntegrator.class.getName());
public static final String APPLY_CONSTRAINTS = "hibernate.validator.apply_to_ddl";
public static final String BV_CHECK_CLASS = "javax.validation.Validation";
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";
public static void validateFactory(Object object) {
try {
final Class activatorClass = BeanValidationIntegrator.class.getClassLoader().loadClass( ACTIVATOR_CLASS );
try {
final Method validateMethod = activatorClass.getMethod( VALIDATE_METHOD, Object.class );
if ( ! validateMethod.isAccessible() ) {
validateMethod.setAccessible( true );
}
try {
validateMethod.invoke( null, object );
}
catch (InvocationTargetException e) {
if ( e.getTargetException() instanceof HibernateException ) {
throw (HibernateException) e.getTargetException();
}
throw new HibernateException( "Unable to check validity of passed ValidatorFactory", e );
}
catch (IllegalAccessException e) {
throw new HibernateException( "Unable to check validity of passed ValidatorFactory", e );
}
}
catch (HibernateException e) {
throw e;
}
catch (Exception e) {
throw new HibernateException( "Could not locate method needed for ValidatorFactory validation", e );
}
}
catch (HibernateException e) {
throw e;
}
catch (Exception e) {
throw new HibernateException( "Could not locate TypeSafeActivator class", e );
}
}
@Override
public void integrate(
Configuration configuration,
SessionFactoryImplementor sessionFactory,
SessionFactoryServiceRegistry serviceRegistry) {
// determine requested validation modes.
final Set<ValidationMode> modes = ValidationMode.getModes( configuration.getProperties().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 e ) {
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,
configuration
);
applyHibernateListeners(
modes,
isBeanValidationAvailable,
typeSafeActivatorClass,
configuration,
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) {
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 );
try {
applyDDLMethod.invoke(
null,
configuration.createMappings().getClasses().values(),
configuration.getProperties()
);
}
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,
Configuration configuration,
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, Properties.class );
try {
activateMethod.invoke(
null,
serviceRegistry.getService( EventListenerRegistry.class ),
configuration.getProperties()
);
}
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
public void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
// nothing to do here afaik
}
}