HHH-8960 First cut of re-enabled BV integration

This commit is contained in:
Hardy Ferentschik 2014-02-11 14:22:39 +01:00
parent c46dbebc5b
commit bf611d4e18
1 changed files with 223 additions and 207 deletions

View File

@ -26,7 +26,7 @@ package org.hibernate.cfg.beanvalidation;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
@ -41,8 +41,9 @@ import javax.validation.metadata.BeanDescriptor;
import javax.validation.metadata.ConstraintDescriptor;
import javax.validation.metadata.PropertyDescriptor;
import org.jboss.logging.Logger;
import org.hibernate.AssertionFailure;
import org.hibernate.MappingException;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.spi.JdbcServices;
@ -52,16 +53,17 @@ 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.Property;
import org.hibernate.mapping.Selectable;
import org.hibernate.mapping.SingleTableSubclass;
import org.hibernate.metamodel.spi.binding.AbstractSingularAssociationAttributeBinding;
import org.hibernate.metamodel.spi.binding.AbstractSingularAttributeBinding;
import org.hibernate.metamodel.spi.binding.AttributeBinding;
import org.hibernate.metamodel.spi.binding.BasicAttributeBinding;
import org.hibernate.metamodel.spi.binding.CompositeAttributeBinding;
import org.hibernate.metamodel.spi.binding.EntityBinding;
import org.hibernate.metamodel.spi.binding.InheritanceType;
import org.hibernate.metamodel.spi.binding.RelationalValueBinding;
import org.hibernate.metamodel.spi.domain.Attribute;
import org.hibernate.metamodel.spi.domain.SingularAttribute;
import org.jboss.logging.Logger;
import org.hibernate.metamodel.spi.relational.Column;
/**
* @author Emmanuel Bernard
@ -70,16 +72,19 @@ import org.jboss.logging.Logger;
*/
class TypeSafeActivator {
private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, TypeSafeActivator.class.getName());
private static final CoreMessageLogger LOG = Logger.getMessageLogger(
CoreMessageLogger.class,
TypeSafeActivator.class.getName()
);
/**
* 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 validateSuppliedFactory(Object object) {
if ( ! ValidatorFactory.class.isInstance( object ) ) {
if ( !ValidatorFactory.class.isInstance( object ) ) {
throw new IntegrationException(
"Given object was not an instance of " + ValidatorFactory.class.getName()
+ "[" + object.getClass().getName() + "]"
@ -94,12 +99,18 @@ class TypeSafeActivator {
try {
factory = getValidatorFactory( activationContext );
}
catch (IntegrationException e) {
catch ( IntegrationException e ) {
if ( activationContext.getValidationModes().contains( ValidationMode.CALLBACK ) ) {
throw new IntegrationException( "Bean Validation provider was not available, but 'callback' validation was requested", e );
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 );
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" );
@ -111,10 +122,10 @@ class TypeSafeActivator {
applyCallbackListeners( factory, activationContext );
}
@SuppressWarnings( {"UnusedDeclaration"})
@SuppressWarnings({ "UnusedDeclaration" })
public static void applyCallbackListeners(ValidatorFactory validatorFactory, ActivationContext activationContext) {
final Set<ValidationMode> modes = activationContext.getValidationModes();
if ( ! ( modes.contains( ValidationMode.CALLBACK ) || modes.contains( ValidationMode.AUTO ) ) ) {
if ( !( modes.contains( ValidationMode.CALLBACK ) || modes.contains( ValidationMode.AUTO ) ) ) {
return;
}
@ -141,16 +152,15 @@ class TypeSafeActivator {
listener.initialize( activationContext.getSettings() );
}
@SuppressWarnings({"unchecked", "UnusedParameters"})
private static void applyRelationalConstraints(ValidatorFactory factory, ActivationContext activationContext) {
final Map properties = activationContext.getSettings();
if ( ! ConfigurationHelper.getBoolean( BeanValidationIntegrator.APPLY_CONSTRAINTS, properties, true ) ){
if ( !ConfigurationHelper.getBoolean( BeanValidationIntegrator.APPLY_CONSTRAINTS, properties, true ) ) {
LOG.debug( "Skipping application of relational constraints from legacy Hibernate Validator" );
return;
}
final Set<ValidationMode> modes = activationContext.getValidationModes();
if ( ! ( modes.contains( ValidationMode.DDL ) || modes.contains( ValidationMode.AUTO ) ) ) {
if ( !( modes.contains( ValidationMode.DDL ) || modes.contains( ValidationMode.AUTO ) ) ) {
return;
}
@ -159,8 +169,6 @@ class TypeSafeActivator {
Class<?>[] groupsArray = new GroupsPerOperation( properties ).get( GroupsPerOperation.Operation.DDL );
Set<Class<?>> groups = new HashSet<Class<?>>( Arrays.asList( groupsArray ) );
// Hardy : I started working on this as part of a big metamodel clean up I am doing, but this got to be too
// much of a rabbit hole. - Steve
for ( EntityBinding entityBinding : activationContext.getMetadata().getEntityBindings() ) {
final String className = entityBinding.getEntity().getClassReference().getName();
@ -178,7 +186,7 @@ class TypeSafeActivator {
try {
applyDDL( "", entityBinding, clazz, factory, groups, true, dialect );
}
catch (Exception e) {
catch ( Exception e ) {
LOG.unableToApplyConstraints( className, e );
}
}
@ -193,43 +201,45 @@ class TypeSafeActivator {
boolean activateNotNull,
Dialect dialect) {
final BeanDescriptor descriptor = factory.getValidator().getConstraintsForClass( clazz );
//no bean level constraints can be applied, go to the properties
for ( PropertyDescriptor propertyDesc : descriptor.getConstrainedProperties() ) {
// no bean level constraints can be applied, just iterate the properties
for ( PropertyDescriptor propertyDescriptor : descriptor.getConstrainedProperties() ) {
AttributeBinding attributeBinding = findPropertyByName(
entityBinding,
prefix + propertyDesc.getPropertyName()
prefix + propertyDescriptor.getPropertyName()
);
boolean hasNotNull;
if ( attributeBinding != null ) {
hasNotNull = applyConstraints(
propertyDesc.getConstraintDescriptors(),
attributeBinding,
propertyDesc,
groups,
activateNotNull,
dialect
);
if ( attributeBinding == null ) {
continue;
}
if ( propertyDesc.isCascaded() ) {
// if it is a composite, visit its attributes
final Attribute attribute = attributeBinding.getAttribute();
if ( attribute.isSingular() ) {
final SingularAttribute singularAttribute = (SingularAttribute) attribute;
if ( singularAttribute.getSingularAttributeType().isAggregate() ) {
final Class componentClass =
singularAttribute.getSingularAttributeType().getClassReference().getResolvedClass();
final boolean canSetNotNullOnColumns = activateNotNull && hasNotNull;
applyDDL(
prefix + propertyDesc.getPropertyName() + ".",
entityBinding,
componentClass,
factory,
groups,
canSetNotNullOnColumns,
dialect
);
}
boolean hasNotNull;
hasNotNull = applyConstraints(
propertyDescriptor.getConstraintDescriptors(),
attributeBinding,
propertyDescriptor,
groups,
activateNotNull,
dialect
);
if ( propertyDescriptor.isCascaded() ) {
// if it is a composite, visit its attributes
final Attribute attribute = attributeBinding.getAttribute();
if ( attribute.isSingular() ) {
final SingularAttribute singularAttribute = (SingularAttribute) attribute;
if ( singularAttribute.getSingularAttributeType().isAggregate() ) {
final Class<?> componentClass = singularAttribute.getSingularAttributeType()
.getClassReference().getResolvedClass();
final boolean canSetNotNullOnColumns = activateNotNull && hasNotNull;
applyDDL(
prefix + propertyDescriptor.getPropertyName() + ".",
entityBinding,
componentClass,
factory,
groups,
canSetNotNullOnColumns,
dialect
);
}
}
}
@ -238,8 +248,8 @@ class TypeSafeActivator {
private static boolean applyConstraints(
Set<ConstraintDescriptor<?>> constraintDescriptors,
AttributeBinding property,
PropertyDescriptor propertyDesc,
AttributeBinding attributeBinding,
PropertyDescriptor propertyDescriptor,
Set<Class<?>> groups,
boolean canApplyNotNull,
Dialect dialect) {
@ -249,197 +259,184 @@ class TypeSafeActivator {
continue;
}
// todo : Hardy - here are the rabbit holes...
// first...
// if ( canApplyNotNull ) {
// hasNotNull = hasNotNull || applyNotNull( property, descriptor );
// }
if ( canApplyNotNull ) {
hasNotNull = hasNotNull || applyNotNull( attributeBinding, descriptor );
}
// apply bean validation specific constraints
// second...
// applyDigits( property, descriptor );
// applySize( property, descriptor, propertyDesc );
// applyMin( property, descriptor, dialect );
// applyMax( property, descriptor, dialect );
applyDigits( attributeBinding, descriptor );
applySize( attributeBinding, descriptor, propertyDescriptor );
applyMin( attributeBinding, descriptor, dialect );
applyMax( attributeBinding, descriptor, dialect );
// 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
// third
// applyLength( property, descriptor, propertyDesc );
applyLength( attributeBinding, descriptor );
// pass an empty set as composing constraints inherit the main constraint and thus are matching already
hasNotNull = hasNotNull || applyConstraints(
descriptor.getComposingConstraints(),
property, propertyDesc, null,
attributeBinding, propertyDescriptor, null,
canApplyNotNull,
dialect
dialect
);
}
return hasNotNull;
}
private static void applyMin(Property property, ConstraintDescriptor<?> descriptor, Dialect dialect) {
if ( Min.class.equals( descriptor.getAnnotation().annotationType() ) ) {
@SuppressWarnings("unchecked")
ConstraintDescriptor<Min> minConstraint = (ConstraintDescriptor<Min>) descriptor;
long min = minConstraint.getAnnotation().value();
Column col = (Column) property.getColumnIterator().next();
String checkConstraint = col.getQuotedName(dialect) + ">=" + min;
applySQLCheck( col, checkConstraint );
}
}
private static void applyMax(Property property, ConstraintDescriptor<?> descriptor, Dialect dialect) {
if ( Max.class.equals( descriptor.getAnnotation().annotationType() ) ) {
@SuppressWarnings("unchecked")
ConstraintDescriptor<Max> maxConstraint = (ConstraintDescriptor<Max>) descriptor;
long max = maxConstraint.getAnnotation().value();
Column col = (Column) property.getColumnIterator().next();
String checkConstraint = col.getQuotedName(dialect) + "<=" + max;
applySQLCheck( col, checkConstraint );
}
}
private static void applySQLCheck(Column col, String checkConstraint) {
String existingCheck = col.getCheckConstraint();
private static void applySQLCheck(Column column, String checkConstraint) {
String existingCheck = column.getCheckCondition();
// need to check whether the new check is already part of the existing check, because applyDDL can be called
// multiple times
if ( StringHelper.isNotEmpty( existingCheck ) && !existingCheck.contains( checkConstraint ) ) {
checkConstraint = col.getCheckConstraint() + " AND " + checkConstraint;
checkConstraint = column.getCheckCondition() + " AND " + checkConstraint;
}
col.setCheckConstraint( checkConstraint );
column.setCheckCondition( checkConstraint );
}
private static boolean applyNotNull(Property property, ConstraintDescriptor<?> descriptor) {
boolean hasNotNull = false;
if ( NotNull.class.equals( descriptor.getAnnotation().annotationType() ) ) {
// single table inheritance should not be forced to null due to shared state
if ( !( property.getPersistentClass() instanceof SingleTableSubclass ) ) {
//composite should not add not-null on all columns
if ( !property.isComposite() ) {
final Iterator<Selectable> iter = property.getColumnIterator();
while ( iter.hasNext() ) {
final Selectable selectable = iter.next();
if ( Column.class.isInstance( selectable ) ) {
Column.class.cast( selectable ).setNullable( false );
}
else {
LOG.debugf(
"@NotNull was applied to attribute [%s] which is defined (at least partially) " +
"by formula(s); formula portions will be skipped",
property.getName()
);
}
}
}
}
hasNotNull = true;
private static boolean applyNotNull(AttributeBinding attributeBinding, ConstraintDescriptor<?> descriptor) {
if ( !NotNull.class.equals( descriptor.getAnnotation().annotationType() ) ) {
return false;
}
return hasNotNull;
}
private static void applyDigits(Property property, ConstraintDescriptor<?> descriptor) {
if ( Digits.class.equals( descriptor.getAnnotation().annotationType() ) ) {
@SuppressWarnings("unchecked")
ConstraintDescriptor<Digits> digitsConstraint = (ConstraintDescriptor<Digits>) descriptor;
int integerDigits = digitsConstraint.getAnnotation().integer();
int fractionalDigits = digitsConstraint.getAnnotation().fraction();
Column col = (Column) property.getColumnIterator().next();
col.setPrecision( integerDigits + fractionalDigits );
col.setScale( fractionalDigits );
if ( InheritanceType.SINGLE_TABLE.equals(
attributeBinding.getContainer()
.seekEntityBinding()
.getHierarchyDetails()
.getInheritanceType()
) ) {
return false;
}
}
private static void applySize(Property property, ConstraintDescriptor<?> descriptor, PropertyDescriptor propertyDescriptor) {
if ( Size.class.equals( descriptor.getAnnotation().annotationType() )
&& String.class.equals( propertyDescriptor.getElementClass() ) ) {
@SuppressWarnings("unchecked")
ConstraintDescriptor<Size> sizeConstraint = (ConstraintDescriptor<Size>) descriptor;
int max = sizeConstraint.getAnnotation().max();
Column col = (Column) property.getColumnIterator().next();
if ( max < Integer.MAX_VALUE ) {
col.setLength( max );
List<RelationalValueBinding> relationalValueBindings = Collections.emptyList();
if ( attributeBinding instanceof BasicAttributeBinding ) {
BasicAttributeBinding basicBinding = (BasicAttributeBinding) attributeBinding;
relationalValueBindings = basicBinding.getRelationalValueBindings();
}
if ( attributeBinding instanceof AbstractSingularAssociationAttributeBinding ) {
AbstractSingularAssociationAttributeBinding singularAttributeBinding = (AbstractSingularAssociationAttributeBinding) attributeBinding;
relationalValueBindings = singularAttributeBinding.getRelationalValueBindings();
}
if ( attributeBinding instanceof AbstractSingularAttributeBinding ) {
AbstractSingularAttributeBinding singularAttributeBinding = (AbstractSingularAttributeBinding) attributeBinding;
relationalValueBindings = singularAttributeBinding.getRelationalValueBindings();
}
for ( RelationalValueBinding relationalValueBinding : relationalValueBindings ) {
if ( relationalValueBinding.getValue() instanceof Column ) {
Column column = (Column) relationalValueBinding.getValue();
column.setNullable( false );
}
}
return true;
}
private static void applyLength(Property property, ConstraintDescriptor<?> descriptor, PropertyDescriptor propertyDescriptor) {
if ( "org.hibernate.validator.constraints.Length".equals(
private static void applyMin(AttributeBinding attributeBinding, ConstraintDescriptor<?> descriptor, Dialect dialect) {
if ( !Min.class.equals( descriptor.getAnnotation().annotationType() ) ) {
return;
}
long min = (Long) descriptor.getAttributes().get( "value" );
Column column = getSingleColumn( attributeBinding );
String checkConstraint = column.getColumnName().getText( dialect ) + ">=" + min;
applySQLCheck( column, checkConstraint );
}
private static void applyMax(AttributeBinding attributeBinding, ConstraintDescriptor<?> descriptor, Dialect dialect) {
if ( !Max.class.equals( descriptor.getAnnotation().annotationType() ) ) {
return;
}
long max = (Long) descriptor.getAttributes().get( "value" );
Column column = getSingleColumn( attributeBinding );
String checkConstraint = column.getColumnName().getText( dialect ) + "<=" + max;
applySQLCheck( column, checkConstraint );
}
private static void applySize(AttributeBinding attributeBinding, ConstraintDescriptor<?> descriptor, PropertyDescriptor propertyDescriptor) {
if ( !( Size.class.equals( descriptor.getAnnotation().annotationType() )
&& String.class.equals( propertyDescriptor.getElementClass() ) ) ) {
return;
}
int max = (Integer) descriptor.getAttributes().get( "max" );
Column column = getSingleColumn( attributeBinding );
if ( max < Integer.MAX_VALUE ) {
column.setSize( org.hibernate.metamodel.spi.relational.Size.length( max ) );
}
}
private static void applyLength(AttributeBinding attributeBinding, ConstraintDescriptor<?> descriptor) {
if ( !"org.hibernate.validator.constraints.Length".equals(
descriptor.getAnnotation().annotationType().getName()
)
&& String.class.equals( propertyDescriptor.getElementClass() ) ) {
@SuppressWarnings("unchecked")
int max = (Integer) descriptor.getAttributes().get( "max" );
Column col = (Column) property.getColumnIterator().next();
if ( max < Integer.MAX_VALUE ) {
col.setLength( max );
}
) ) {
return;
}
int max = (Integer) descriptor.getAttributes().get( "max" );
Column column = getSingleColumn( attributeBinding );
if ( max < Integer.MAX_VALUE ) {
column.setSize( org.hibernate.metamodel.spi.relational.Size.length( max ) );
}
}
private static AttributeBinding findPropertyByName(EntityBinding entityBinding, String propertyName) {
// Returns the property by path in a recursive way, including IdentifierProperty in the loop
// if propertyName is null. If propertyName is null or empty, the IdentifierProperty is returned
final AttributeBinding idAttributeBinding = entityBinding.getHierarchyDetails().getEntityIdentifier().getAttributeBinding();
private static void applyDigits(AttributeBinding attributeBinding, ConstraintDescriptor<?> descriptor) {
if ( !Digits.class.equals( descriptor.getAnnotation().annotationType() ) ) {
return;
}
@SuppressWarnings("unchecked")
ConstraintDescriptor<Digits> digitsConstraint = (ConstraintDescriptor<Digits>) descriptor;
int integerDigits = digitsConstraint.getAnnotation().integer();
int fractionalDigits = digitsConstraint.getAnnotation().fraction();
Column column = getSingleColumn( attributeBinding );
org.hibernate.metamodel.spi.relational.Size size = org.hibernate.metamodel.spi.relational.Size.precision(
integerDigits + fractionalDigits,
fractionalDigits
);
column.setSize( size );
}
/**
* Returns the {@code AttributeBinding} for the attribute/property specified by given path.
*
* @param entityBinding the root entity binding from which to start the search for the property
* @param propertyPath the property path
*
* @return Returns the {@code AttributeBinding} for the attribute/property specified by given path. If
* {@code propertyPath} is {@code null} or empty, the id attribute binding is returned.
*/
private static AttributeBinding findPropertyByName(EntityBinding entityBinding, String propertyPath) {
final AttributeBinding idAttributeBinding = entityBinding.getHierarchyDetails()
.getEntityIdentifier()
.getAttributeBinding();
final String idAttributeName = idAttributeBinding == null ? null : idAttributeBinding.getAttribute().getName();
AttributeBinding property = null;
try {
if ( propertyName == null
|| propertyName.length() == 0
|| propertyName.equals( idAttributeName ) ) {
//default to id
property = idAttributeBinding;
if ( propertyPath == null || propertyPath.length() == 0 || propertyPath.equals( idAttributeName ) ) {
//default to id
return idAttributeBinding;
}
AttributeBinding attributeBinding = null;
StringTokenizer tokenizer = new StringTokenizer( propertyPath, ".", false );
while ( tokenizer.hasMoreElements() ) {
String element = (String) tokenizer.nextElement();
if ( attributeBinding == null ) {
attributeBinding = entityBinding.locateAttributeBinding( element );
}
else {
if ( propertyName.indexOf( idAttributeName + "." ) == 0 ) {
property = idAttributeBinding;
propertyName = propertyName.substring( idAttributeName.length() + 1 );
}
StringTokenizer st = new StringTokenizer( propertyName, ".", false );
while ( st.hasMoreElements() ) {
String element = (String) st.nextElement();
if ( property == null ) {
property = entityBinding.locateAttributeBinding( element );
}
else {
if ( !isComposite( property ) ) {
return null;
}
// todo : Hardy - not sure exactly what to do here...
//property = ( (Component) property.getValue() ).getProperty( element );
}
if ( !isComposite( attributeBinding ) ) {
return null;
}
CompositeAttributeBinding compositeAttributeBinding = (CompositeAttributeBinding) attributeBinding;
attributeBinding = compositeAttributeBinding.locateAttributeBinding( element );
}
}
catch ( MappingException e ) {
// todo : Hardy - nor here...
// try {
// //if we do not find it try to check the identifier mapper
// if ( associatedClass.getIdentifierMapper() == null ) {
// return null;
// }
// StringTokenizer st = new StringTokenizer( propertyName, ".", false );
// while ( st.hasMoreElements() ) {
// String element = (String) st.nextElement();
// if ( property == null ) {
// property = associatedClass.getIdentifierMapper().getProperty( element );
// }
// else {
// if ( !property.isComposite() ) {
// return null;
// }
// property = ( (Component) property.getValue() ).getProperty( element );
// }
// }
// }
// catch ( MappingException ee ) {
// return null;
// }
}
return property;
return attributeBinding;
}
private static boolean isComposite(AttributeBinding property) {
@ -453,7 +450,9 @@ class TypeSafeActivator {
private static ValidatorFactory getValidatorFactory(ActivationContext activationContext) {
// first look for an explicitly passed ValidatorFactory
final Object reference = activationContext.getSessionFactory().getSessionFactoryOptions().getValidatorFactoryReference();
final Object reference = activationContext.getSessionFactory()
.getSessionFactoryOptions()
.getValidatorFactoryReference();
if ( reference != null ) {
try {
return ValidatorFactory.class.cast( reference );
@ -473,4 +472,21 @@ class TypeSafeActivator {
throw new IntegrationException( "Unable to build the default ValidatorFactory", e );
}
}
private static Column getSingleColumn(AttributeBinding attributeBinding) {
BasicAttributeBinding basicBinding = (BasicAttributeBinding) attributeBinding;
List<RelationalValueBinding> relationalValueBindings = basicBinding.getRelationalValueBindings();
if ( relationalValueBindings.size() > 1 ) {
throw new IntegrationException(
"Unexpected number of relational columns for attribute "
+ attributeBinding.getAttribute().getName()
);
}
for ( RelationalValueBinding relationalValueBinding : relationalValueBindings ) {
if ( relationalValueBinding.getValue() instanceof Column ) {
return (Column) relationalValueBinding.getValue();
}
}
return null;
}
}