Author: epbernard
Date: 2009-05-06 11:52:26 -0400 (Wed, 06 May 2009)
New Revision: 16516
Added:
core/trunk/annotations/src/main/java/org/hibernate/cfg/beanvalidation/GroupsPerOperation.java
core/trunk/annotations/src/main/java/org/hibernate/cfg/beanvalidation/HibernateTraversableResolver.java
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/Address.java
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/Button.java
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/Color.java
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/DDLTest.java
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/Display.java
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/DisplayConnector.java
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/HibernateTraversableResolverTest.java
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/Music.java
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/PowerSupply.java
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/Rock.java
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/Screen.java
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/Tv.java
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/TvOwner.java
Modified:
core/trunk/annotations/src/main/java/org/hibernate/cfg/AnnotationConfiguration.java
core/trunk/annotations/src/main/java/org/hibernate/cfg/beanvalidation/BeanValidationActivator.java
core/trunk/annotations/src/main/java/org/hibernate/cfg/beanvalidation/BeanValidationEventListener.java
core/trunk/annotations/src/main/java/org/hibernate/cfg/beanvalidation/TypeSafeActivator.java
Log:
ANN-828 Use Bean Validation for DDL generation
Modified:
core/trunk/annotations/src/main/java/org/hibernate/cfg/AnnotationConfiguration.java
===================================================================
---
core/trunk/annotations/src/main/java/org/hibernate/cfg/AnnotationConfiguration.java 2009-05-06
15:20:18 UTC (rev 16515)
+++
core/trunk/annotations/src/main/java/org/hibernate/cfg/AnnotationConfiguration.java 2009-05-06
15:52:26 UTC (rev 16516)
@@ -396,8 +396,13 @@
}
}
}
+ applyDDLOnBeanValidation( (Collection<PersistentClass>) classes.values(),
getProperties() );
}
+ private void applyDDLOnBeanValidation(Collection<PersistentClass>
persistentClasses, Properties properties) {
+ BeanValidationActivator.applyDDL( persistentClasses, properties );
+ }
+
/**
* Processes FKSecondPass instances trying to resolve any
* graph circularity (ie PK made of a many to one linking to
Modified:
core/trunk/annotations/src/main/java/org/hibernate/cfg/beanvalidation/BeanValidationActivator.java
===================================================================
---
core/trunk/annotations/src/main/java/org/hibernate/cfg/beanvalidation/BeanValidationActivator.java 2009-05-06
15:20:18 UTC (rev 16515)
+++
core/trunk/annotations/src/main/java/org/hibernate/cfg/beanvalidation/BeanValidationActivator.java 2009-05-06
15:52:26 UTC (rev 16516)
@@ -2,6 +2,9 @@
import java.util.Map;
import java.util.Properties;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Collection;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
@@ -11,6 +14,8 @@
import org.hibernate.util.ReflectHelper;
import org.hibernate.HibernateException;
import org.hibernate.AssertionFailure;
+import org.hibernate.cfg.Environment;
+import org.hibernate.mapping.PersistentClass;
import org.hibernate.event.EventListeners;
/**
@@ -22,34 +27,37 @@
private static final String BV_DISCOVERY_CLASS =
"javax.validation.Validation";
private static final String TYPE_SAFE_ACTIVATOR_CLASS =
"org.hibernate.cfg.beanvalidation.TypeSafeActivator";
+ private static final String TYPE_SAFE_DDL_METHOD = "applyDDL";
private static final String TYPE_SAFE_ACTIVATOR_METHOD =
"activateBeanValidation";
private static final String MODE_PROPERTY =
"javax.persistence.validation.mode";
public static void activateBeanValidation(EventListeners eventListeners, Properties
properties) {
- ValidationMode mode = ValidationMode.getMode( properties.get( MODE_PROPERTY ) );
- if (mode == ValidationMode.NONE) return;
+ Set<ValidationMode> modes = ValidationMode.getModes( properties.get(
MODE_PROPERTY ) );
+ if ( modes.contains( ValidationMode.NONE ) ) return;
+ //desactivate not-null tracking at the core level when Bean Validation is on unless the
user really ask for it
+ if ( properties.getProperty( Environment.CHECK_NULLABILITY ) == null ) {
+ properties.setProperty( Environment.CHECK_NULLABILITY, "false" );
+ }
+
try {
//load Validation
ReflectHelper.classForName( BV_DISCOVERY_CLASS, BeanValidationActivator.class );
}
catch ( ClassNotFoundException e ) {
- if (mode == ValidationMode.CALLBACK) {
+ if ( modes.contains( ValidationMode.CALLBACK ) ) {
throw new HibernateException( "Bean Validation not available in the class path
but required in " + MODE_PROPERTY );
}
- else if (mode == ValidationMode.AUTO) {
+ else if (modes.contains( ValidationMode.AUTO ) ) {
//nothing to activate
return;
}
- else {
- throw new AssertionFailure( "Unexpected ValidationMode: " + mode );
- }
}
try {
Class<?> activator = ReflectHelper.classForName( TYPE_SAFE_ACTIVATOR_CLASS,
BeanValidationActivator.class );
- Method buildDefaultValidatorFactory =
+ Method activateBeanValidation =
activator.getMethod( TYPE_SAFE_ACTIVATOR_METHOD, EventListeners.class,
Properties.class );
- buildDefaultValidatorFactory.invoke( null, eventListeners, properties );
+ activateBeanValidation.invoke( null, eventListeners, properties );
}
catch ( NoSuchMethodException e ) {
throw new HibernateException( "Unable to get the default Bean Validation
factory", e);
@@ -65,18 +73,76 @@
}
}
+ public static void applyDDL(Collection<PersistentClass> persistentClasses,
Properties properties) {
+ Set<ValidationMode> modes = ValidationMode.getModes( properties.get(
MODE_PROPERTY ) );
+ if ( ! ( modes.contains( ValidationMode.DDL ) || modes.contains( ValidationMode.AUTO )
) ) return;
+ try {
+ //load Validation
+ ReflectHelper.classForName( BV_DISCOVERY_CLASS, BeanValidationActivator.class );
+ }
+ catch ( ClassNotFoundException e ) {
+ 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 {
+ Class<?> activator = ReflectHelper.classForName( TYPE_SAFE_ACTIVATOR_CLASS,
BeanValidationActivator.class );
+ Method applyDDL =
+ activator.getMethod( TYPE_SAFE_DDL_METHOD, Collection.class, Properties.class );
+ applyDDL.invoke( null, persistentClasses, properties );
+ }
+ catch ( NoSuchMethodException e ) {
+ throw new HibernateException( "Unable to get the default Bean Validation
factory", e);
+ }
+ catch ( IllegalAccessException e ) {
+ throw new HibernateException( "Unable to get the default Bean Validation
factory", e);
+ }
+ catch ( InvocationTargetException e ) {
+ throw new HibernateException( "Unable to get the default Bean Validation
factory", e);
+ }
+ catch ( ClassNotFoundException e ) {
+ throw new HibernateException( "Unable to get the default Bean Validation
factory", e);
+ }
+ }
+
private static enum ValidationMode {
AUTO,
CALLBACK,
- NONE;
+ NONE,
+ DDL;
- public static ValidationMode getMode(Object modeProperty) {
+ 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.toString().toUpperCase() );
+ return valueOf( modeProperty.trim().toUpperCase() );
}
catch ( IllegalArgumentException e ) {
throw new HibernateException( "Unknown validation mode in " +
MODE_PROPERTY + ": " + modeProperty.toString() );
Modified:
core/trunk/annotations/src/main/java/org/hibernate/cfg/beanvalidation/BeanValidationEventListener.java
===================================================================
---
core/trunk/annotations/src/main/java/org/hibernate/cfg/beanvalidation/BeanValidationEventListener.java 2009-05-06
15:20:18 UTC (rev 16515)
+++
core/trunk/annotations/src/main/java/org/hibernate/cfg/beanvalidation/BeanValidationEventListener.java 2009-05-06
15:52:26 UTC (rev 16516)
@@ -6,6 +6,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
+import java.util.concurrent.ConcurrentHashMap;
import javax.validation.ValidatorFactory;
import javax.validation.ConstraintViolation;
import javax.validation.TraversableResolver;
@@ -21,6 +22,8 @@
import org.hibernate.event.PreDeleteEvent;
import org.hibernate.EntityMode;
import org.hibernate.HibernateException;
+import org.hibernate.engine.SessionFactoryImplementor;
+import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.util.ReflectHelper;
/**
@@ -29,83 +32,45 @@
//FIXME review exception model
public class BeanValidationEventListener implements
PreInsertEventListener, PreUpdateEventListener, PreDeleteEventListener {
- private static final String JPA_GROUP_PREFIX =
"javax.persistence.validation.group.";
- private static final Class<?>[] DEFAULT_GROUPS = new Class<?>[] {
Default.class };
- private static final Class<?>[] EMPTY_GROUPS = new Class<?>[] { };
+
private ValidatorFactory factory;
- private TraversableResolver tr;
- private Map<Operation, Class<?>[]> groupsPerOperation = new
HashMap<Operation, Class<?>[]>(3);
+ private ConcurrentHashMap<EntityPersister, Set<String>>
associationsPerEntityPersister =
+ new ConcurrentHashMap<EntityPersister, Set<String>>();
+ private GroupsPerOperation groupsPerOperation;
public BeanValidationEventListener(ValidatorFactory factory, Properties properties) {
this.factory = factory;
- setGroupsForOperation( Operation.INSERT, properties );
- setGroupsForOperation( Operation.UPDATE, properties );
- setGroupsForOperation( Operation.DELETE, properties );
+ groupsPerOperation = new GroupsPerOperation(properties);
}
- private void setGroupsForOperation(Operation operation, Properties properties) {
- Object property = properties.get( JPA_GROUP_PREFIX + operation.getGroupPropertyName()
);
- Class<?>[] groups;
- if ( property == null ) {
- groups = operation == Operation.DELETE ? EMPTY_GROUPS : DEFAULT_GROUPS;
- }
- else {
- if ( property instanceof String ) {
- String stringProperty = (String) property;
- String[] groupNames = stringProperty.split( "," );
- if ( groupNames.length == 1 && groupNames[0].equals( "" ) ) {
- groups = EMPTY_GROUPS;
- }
- else {
- List<Class<?>> groupsList = new
ArrayList<Class<?>>(groupNames.length);
- for (String groupName : groupNames) {
- String cleanedGroupName = groupName.trim();
- if ( cleanedGroupName.length() > 0) {
- try {
- groupsList.add( ReflectHelper.classForName( cleanedGroupName ) );
- }
- catch ( ClassNotFoundException e ) {
- throw new HibernateException( "Unable to load class " +
cleanedGroupName, e );
- }
- }
- }
- groups = groupsList.toArray( new Class<?>[groupsList.size()] );
- }
- }
- else if ( property instanceof Class<?>[] ) {
- groups = (Class<?>[]) property;
- }
- else {
- //null is bad and excluded by instanceof => exception is raised
- throw new HibernateException( JPA_GROUP_PREFIX + operation.getGroupPropertyName() +
" is of unknown type: String or Class<?>[] only");
- }
- }
- groupsPerOperation.put( operation, groups );
- }
-
public boolean onPreInsert(PreInsertEvent event) {
- validate( event.getEntity(), event.getSession().getEntityMode(), Operation.INSERT );
+ validate( event.getEntity(), event.getSession().getEntityMode(), event.getPersister(),
+ event.getSession().getFactory(), GroupsPerOperation.Operation.INSERT );
return false;
}
public boolean onPreUpdate(PreUpdateEvent event) {
- validate( event.getEntity(), event.getSession().getEntityMode(), Operation.UPDATE );
+ validate( event.getEntity(), event.getSession().getEntityMode(), event.getPersister(),
+ event.getSession().getFactory(), GroupsPerOperation.Operation.UPDATE );
return false;
}
public boolean onPreDelete(PreDeleteEvent event) {
- validate( event.getEntity(), event.getSession().getEntityMode(), Operation.DELETE );
+ validate( event.getEntity(), event.getSession().getEntityMode(), event.getPersister(),
+ event.getSession().getFactory(), GroupsPerOperation.Operation.DELETE );
return false;
}
- private <T> void validate(T object, EntityMode mode, Operation operation) {
+ private <T> void validate(T object, EntityMode mode, EntityPersister persister,
+ SessionFactoryImplementor sessionFactory, GroupsPerOperation.Operation
operation) {
if ( object == null || mode != EntityMode.POJO ) return;
+ TraversableResolver tr = new HibernateTraversableResolver( persister,
associationsPerEntityPersister, sessionFactory );
Validator validator = factory.usingContext()
- //.traversableResolver( tr )
+ .traversableResolver( tr )
.getValidator();
final Class<?>[] groups = groupsPerOperation.get( operation );
if ( groups.length > 0 ) {
@@ -117,7 +82,7 @@
//FIXME add Set<ConstraintViolation<?>>
throw new ConstraintViolationException(
"Invalid object at " + operation.getName() + " time for groups
" + toString( groups ),
- (Set<ConstraintViolation>) unsafeViolations);
+ (Set<ConstraintViolation<?>>) unsafeViolations);
}
}
}
@@ -131,26 +96,6 @@
return toString.toString();
}
- private static enum Operation {
- INSERT("persist", "pre-persist"),
- UPDATE("update", "pre-update"),
- DELETE("remove", "pre-remove");
- private String exposedName;
- private String groupPropertyName;
- Operation(String exposedName, String groupProperty) {
- this.exposedName = exposedName;
- this.groupPropertyName = groupProperty;
- }
-
- public String getName() {
- return exposedName;
- }
-
- public String getGroupPropertyName() {
- return groupPropertyName;
- }
- }
-
}
Added:
core/trunk/annotations/src/main/java/org/hibernate/cfg/beanvalidation/GroupsPerOperation.java
===================================================================
---
core/trunk/annotations/src/main/java/org/hibernate/cfg/beanvalidation/GroupsPerOperation.java
(rev 0)
+++
core/trunk/annotations/src/main/java/org/hibernate/cfg/beanvalidation/GroupsPerOperation.java 2009-05-06
15:52:26 UTC (rev 16516)
@@ -0,0 +1,101 @@
+package org.hibernate.cfg.beanvalidation;
+
+import java.util.Properties;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import javax.validation.groups.Default;
+
+import org.hibernate.util.ReflectHelper;
+import org.hibernate.HibernateException;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public class GroupsPerOperation {
+
+ private static final String JPA_GROUP_PREFIX =
"javax.persistence.validation.group.";
+ private static final String HIBERNATE_GROUP_PREFIX =
"org.hibernate.validator.group.";
+ private static final Class<?>[] DEFAULT_GROUPS = new Class<?>[] {
Default.class };
+ private static final Class<?>[] EMPTY_GROUPS = new Class<?>[] { };
+
+ private Map<Operation, Class<?>[]> groupsPerOperation = new
HashMap<Operation, Class<?>[]>(4);
+
+ public GroupsPerOperation(Properties properties) {
+ setGroupsForOperation( Operation.INSERT, properties );
+ setGroupsForOperation( Operation.UPDATE, properties );
+ setGroupsForOperation( Operation.DELETE, properties );
+ setGroupsForOperation( Operation.DDL, properties );
+ }
+
+ private void setGroupsForOperation(Operation operation, Properties properties) {
+ Object property = properties.get( operation.getGroupPropertyName() );
+
+ Class<?>[] groups;
+ if ( property == null ) {
+ groups = operation == Operation.DELETE ? EMPTY_GROUPS : DEFAULT_GROUPS;
+ }
+ else {
+ if ( property instanceof String ) {
+ String stringProperty = (String) property;
+ String[] groupNames = stringProperty.split( "," );
+ if ( groupNames.length == 1 && groupNames[0].equals( "" ) ) {
+ groups = EMPTY_GROUPS;
+ }
+ else {
+ List<Class<?>> groupsList = new
ArrayList<Class<?>>(groupNames.length);
+ for (String groupName : groupNames) {
+ String cleanedGroupName = groupName.trim();
+ if ( cleanedGroupName.length() > 0) {
+ try {
+ groupsList.add( ReflectHelper.classForName( cleanedGroupName ) );
+ }
+ catch ( ClassNotFoundException e ) {
+ throw new HibernateException( "Unable to load class " +
cleanedGroupName, e );
+ }
+ }
+ }
+ groups = groupsList.toArray( new Class<?>[groupsList.size()] );
+ }
+ }
+ else if ( property instanceof Class<?>[] ) {
+ groups = (Class<?>[]) property;
+ }
+ else {
+ //null is bad and excluded by instanceof => exception is raised
+ throw new HibernateException( JPA_GROUP_PREFIX + operation.getGroupPropertyName() +
" is of unknown type: String or Class<?>[] only");
+ }
+ }
+ groupsPerOperation.put( operation, groups );
+ }
+
+ public Class<?>[] get(Operation operation) {
+ return groupsPerOperation.get( operation );
+ }
+
+ public static enum Operation {
+ INSERT("persist", JPA_GROUP_PREFIX + "pre-persist"),
+ UPDATE("update", JPA_GROUP_PREFIX + "pre-update"),
+ DELETE("remove", JPA_GROUP_PREFIX + "pre-remove"),
+ DDL("ddl", HIBERNATE_GROUP_PREFIX + "ddl");
+
+
+ private String exposedName;
+ private String groupPropertyName;
+
+ Operation(String exposedName, String groupProperty) {
+ this.exposedName = exposedName;
+ this.groupPropertyName = groupProperty;
+ }
+
+ public String getName() {
+ return exposedName;
+ }
+
+ public String getGroupPropertyName() {
+ return groupPropertyName;
+ }
+ }
+
+}
Added:
core/trunk/annotations/src/main/java/org/hibernate/cfg/beanvalidation/HibernateTraversableResolver.java
===================================================================
---
core/trunk/annotations/src/main/java/org/hibernate/cfg/beanvalidation/HibernateTraversableResolver.java
(rev 0)
+++
core/trunk/annotations/src/main/java/org/hibernate/cfg/beanvalidation/HibernateTraversableResolver.java 2009-05-06
15:52:26 UTC (rev 16516)
@@ -0,0 +1,92 @@
+package org.hibernate.cfg.beanvalidation;
+
+import java.lang.annotation.ElementType;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.concurrent.ConcurrentHashMap;
+import javax.validation.TraversableResolver;
+
+import org.hibernate.persister.entity.EntityPersister;
+import org.hibernate.Hibernate;
+import org.hibernate.engine.SessionFactoryImplementor;
+import org.hibernate.type.Type;
+import org.hibernate.type.ComponentType;
+import org.hibernate.type.CollectionType;
+import org.hibernate.type.AbstractComponentType;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public class HibernateTraversableResolver implements TraversableResolver {
+ private Set<String> associations;
+
+ public HibernateTraversableResolver(
+ EntityPersister persister,
+ ConcurrentHashMap<EntityPersister, Set<String>>
associationsPerEntityPersister,
+ SessionFactoryImplementor factory) {
+ this.associations = associationsPerEntityPersister.get( persister );
+ if (this.associations == null) {
+ this.associations = new HashSet<String>();
+ addAssociationsToTheSetForAllProperties( persister.getPropertyNames(),
persister.getPropertyTypes(), "", factory );
+ associationsPerEntityPersister.put( persister, associations );
+ }
+ }
+
+ private void addAssociationsToTheSetForAllProperties(String[] names, Type[] types,
String prefix, SessionFactoryImplementor factory) {
+ final int length = names.length;
+ for( int index = 0 ; index < length; index++ ) {
+ addAssociationsToTheSetForOneProperty( names[index], types[index], prefix, factory );
+ }
+ }
+
+ private void addAssociationsToTheSetForOneProperty(String name, Type type, String
prefix, SessionFactoryImplementor factory) {
+
+ if ( type.isCollectionType() ) {
+ CollectionType collType = (CollectionType) type;
+ Type assocType = collType.getElementType( factory );
+ addAssociationsToTheSetForOneProperty(name, assocType, prefix, factory);
+ }
+ //ToOne association
+ else if ( type.isEntityType() || type.isAnyType() ) {
+ associations.add( prefix + name );
+ } else if ( type.isComponentType() ) {
+ AbstractComponentType componentType = (AbstractComponentType) type;
+ addAssociationsToTheSetForAllProperties(
+ componentType.getPropertyNames(),
+ componentType.getSubtypes(),
+ (prefix.equals( "" ) ? name : prefix + name) + ".",
+ factory);
+ }
+ }
+
+ private String getCleanPathWoBraket(String traversableProperty, String
pathToTraversableObject) {
+ String path = pathToTraversableObject.equals( "" ) ?
+ traversableProperty :
+ pathToTraversableObject + "." + traversableProperty;
+ String[] paths = path.split( "\\[.*\\]" );
+ path = "";
+ for (String subpath : paths) {
+ path += subpath;
+ }
+ return path;
+ }
+
+ public boolean isReachable(Object traversableObject,
+ String traversableProperty,
+ Class<?> rootBeanType,
+ String pathToTraversableObject,
+ ElementType elementType) {
+ //lazy, don't load
+ return Hibernate.isInitialized( traversableObject )
+ && Hibernate.isPropertyInitialized( traversableObject, traversableProperty
);
+ }
+
+ public boolean isCascadable(Object traversableObject,
+ String traversableProperty,
+ Class<?> rootBeanType,
+ String pathToTraversableObject,
+ ElementType elementType) {
+ String path = getCleanPathWoBraket( traversableProperty, pathToTraversableObject );
+ return ! associations.contains(path);
+ }
+}
Modified:
core/trunk/annotations/src/main/java/org/hibernate/cfg/beanvalidation/TypeSafeActivator.java
===================================================================
---
core/trunk/annotations/src/main/java/org/hibernate/cfg/beanvalidation/TypeSafeActivator.java 2009-05-06
15:20:18 UTC (rev 16515)
+++
core/trunk/annotations/src/main/java/org/hibernate/cfg/beanvalidation/TypeSafeActivator.java 2009-05-06
15:52:26 UTC (rev 16516)
@@ -1,22 +1,49 @@
package org.hibernate.cfg.beanvalidation;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
import java.util.Map;
-import java.util.Arrays;
import java.util.Properties;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.Collection;
+import javax.validation.BeanDescriptor;
+import javax.validation.ConstraintDescriptor;
+import javax.validation.PropertyDescriptor;
+import javax.validation.Validation;
import javax.validation.ValidatorFactory;
-import javax.validation.Validation;
+import javax.validation.constraints.Digits;
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.hibernate.AssertionFailure;
import org.hibernate.HibernateException;
+import org.hibernate.MappingException;
import org.hibernate.event.EventListeners;
+import org.hibernate.event.PreDeleteEventListener;
import org.hibernate.event.PreInsertEventListener;
import org.hibernate.event.PreUpdateEventListener;
-import org.hibernate.event.PreDeleteEventListener;
+import org.hibernate.mapping.Column;
+import org.hibernate.mapping.Component;
+import org.hibernate.mapping.PersistentClass;
+import org.hibernate.mapping.Property;
+import org.hibernate.mapping.SingleTableSubclass;
+import org.hibernate.util.ReflectHelper;
/**
* @author Emmanuel Bernard
*/
class TypeSafeActivator {
+ private static final Logger logger = LoggerFactory.getLogger( TypeSafeActivator.class
);
+
private static final String FACTORY_PROPERTY =
"javax.persistence.validation.factory";
public static void activateBeanValidation(EventListeners eventListeners, Properties
properties) {
@@ -51,7 +78,209 @@
}
}
- static ValidatorFactory getValidatorFactory(Map<Object, Object> properties) {
+ public static void applyDDL(Collection<PersistentClass> persistentClasses,
Properties properties) {
+ ValidatorFactory factory = getValidatorFactory( properties );
+ Class<?>[] groupsArray = new GroupsPerOperation( properties ).get(
GroupsPerOperation.Operation.DDL );
+ Set<Class<?>> groups = new HashSet<Class<?>>( Arrays.asList(
groupsArray ) );
+
+ for ( PersistentClass persistentClass : persistentClasses ) {
+ final String className = persistentClass.getClassName();
+
+ if ( className == null || className.length() == 0) continue;
+ Class<?> clazz;
+ try {
+ clazz = ReflectHelper.classForName( className, TypeSafeActivator.class );
+ }
+ catch ( ClassNotFoundException e ) {
+ throw new AssertionFailure( "Entity class not found", e);
+ }
+
+ try {
+ applyDDL( "", persistentClass, clazz, factory, groups, true );
+ }
+ catch (Exception e) {
+ logger.warn( "Unable to apply constraints on DDL for " + className, e );
+ }
+ }
+ }
+
+ private static void applyDDL(String prefix,
+ PersistentClass persistentClass,
+ Class<?> clazz,
+ ValidatorFactory factory,
+ Set<Class<?>> 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() ) {
+ Property property = findPropertyByName( persistentClass, prefix +
propertyDesc.getPropertyName() );
+ boolean hasNotNull = false;
+ if ( property != null ) {
+ hasNotNull = applyConstraints( propertyDesc.getConstraintDescriptors(), property,
propertyDesc, groups, activateNotNull );
+ if ( property.isComposite() && propertyDesc.isCascaded() ) {
+ Class<?> componentClass = ( ( Component ) property.getValue()
).getComponentClass();
+
+ /*
+ * we can apply not null if the upper component let's us activate not null
+ * and if the property is not null.
+ * Otherwise, all sub columns should be left nullable
+ */
+ final boolean canSetNotNullOnColumns = activateNotNull && hasNotNull;
+ applyDDL( prefix + propertyDesc.getPropertyName() + ".",
+ persistentClass, componentClass, factory, groups,
+ canSetNotNullOnColumns
+ );
+ }
+ //FIXME add collection of components
+ }
+ }
+ }
+
+ private static boolean applyConstraints(Set<ConstraintDescriptor<?>>
constraintDescriptors,
+ Property property,
+ 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( property, descriptor );
+ }
+ applyDigits( property, descriptor );
+ applySize( property, descriptor, propertyDesc );
+ applyMin( property, descriptor );
+ applyMax( property, 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,
+ canApplyNotNull );
+ }
+ return hasNotNull;
+ }
+
+ private static void applyMin(Property property, ConstraintDescriptor<?>
descriptor) {
+ 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();
+ col.setCheckConstraint( col.getName() + ">=" + min );
+ }
+ }
+
+ private static void applyMax(Property property, ConstraintDescriptor<?>
descriptor) {
+ 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();
+ col.setCheckConstraint( col.getName() + "<=" + max );
+ }
+ }
+
+ private static boolean applyNotNull(Property property, ConstraintDescriptor<?>
descriptor) {
+ boolean hasNotNull = false;
+ if ( NotNull.class.equals( descriptor.getAnnotation().annotationType() ) ) {
+ if ( ! ( property.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 = (Iterator<Column>)
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" )
+ 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 );
+ }
+ }
+
+ private static void applySize(Property property, ConstraintDescriptor<?>
descriptor, PropertyDescriptor propertyDesc) {
+ if ( Size.class.equals( descriptor.getAnnotation().annotationType() )
+ && String.class.equals( propertyDesc.getType() ) ) {
+ @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 );
+ }
+ }
+
+ /**
+ * Retrieve the property by path in a recursive way, including IndetifierProperty in the
loop
+ * If propertyName is null or empty, the IdentifierProperty is returned
+ */
+ private static Property findPropertyByName(PersistentClass associatedClass, String
propertyName) {
+ Property property = null;
+ Property idProperty = associatedClass.getIdentifierProperty();
+ String idName = idProperty != null ? idProperty.getName() : null;
+ try {
+ if ( propertyName == null
+ || propertyName.length() == 0
+ || propertyName.equals( idName ) ) {
+ //default to id
+ property = idProperty;
+ }
+ else {
+ if ( propertyName.indexOf( idName + "." ) == 0 ) {
+ property = idProperty;
+ propertyName = propertyName.substring( idName.length() + 1 );
+ }
+ StringTokenizer st = new StringTokenizer( propertyName, ".", false );
+ while ( st.hasMoreElements() ) {
+ String element = (String) st.nextElement();
+ if ( property == null ) {
+ property = associatedClass.getProperty( element );
+ }
+ else {
+ if ( ! property.isComposite() ) return null;
+ property = ( ( Component ) property.getValue() ).getProperty( element );
+ }
+ }
+ }
+ }
+ catch ( MappingException e) {
+ 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;
+ }
+
+ private static ValidatorFactory getValidatorFactory(Map<Object, Object>
properties) {
ValidatorFactory factory = null;
if ( properties != null ) {
Object unsafeProperty = properties.get( FACTORY_PROPERTY );
Added:
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/Address.java
===================================================================
---
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/Address.java
(rev 0)
+++
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/Address.java 2009-05-06
15:52:26 UTC (rev 16516)
@@ -0,0 +1,103 @@
+package org.hibernate.test.annotations.beanvalidation;
+
+import javax.persistence.Entity;
+import javax.persistence.Transient;
+import javax.persistence.Id;
+import javax.validation.constraints.Size;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.AssertTrue;
+
+@Entity
+public class Address {
+ @NotNull
+ public static String blacklistedZipCode;
+
+ private String line1;
+ private String line2;
+ private String zip;
+ private String state;
+ @Size(max = 20)
+ @NotNull
+ private String country;
+ private long id;
+ private boolean internalValid = true;
+ @Min(-2) @Max(value=50)
+ public int floor;
+
+ public String getCountry() {
+ return country;
+ }
+
+ public void setCountry(String country) {
+ this.country = country;
+ }
+
+ @NotNull
+ public String getLine1() {
+ return line1;
+ }
+
+ public void setLine1(String line1) {
+ this.line1 = line1;
+ }
+
+ public String getLine2() {
+ return line2;
+ }
+
+ public void setLine2(String line2) {
+ this.line2 = line2;
+ }
+
+ @Size(max = 3)
+ @NotNull
+ public String getState() {
+ return state;
+ }
+
+ public void setState(String state) {
+ this.state = state;
+ }
+
+ @Size(max = 5)
+ @Pattern(regexp = "[0-9]+")
+ @NotNull
+ public String getZip() {
+ return zip;
+ }
+
+ public void setZip(String zip) {
+ this.zip = zip;
+ }
+
+ @AssertTrue
+ @Transient
+ public boolean isValid() {
+ return true;
+ }
+
+ @AssertTrue
+ @Transient
+ private boolean isInternalValid() {
+ return internalValid;
+ }
+
+ public void setInternalValid(boolean internalValid) {
+ this.internalValid = internalValid;
+ }
+
+ @Id
+ @Min(1)
+ @Max(2000)
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+}
Added:
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/Button.java
===================================================================
---
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/Button.java
(rev 0)
+++
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/Button.java 2009-05-06
15:52:26 UTC (rev 16516)
@@ -0,0 +1,34 @@
+package org.hibernate.test.annotations.beanvalidation;
+
+import javax.persistence.Embeddable;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Max;
+
+/**
+ * @author Emmanuel Bernard
+ */
+@Embeddable
+public class Button {
+
+ private String name;
+
+ private Integer size;
+
+ @NotNull
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Max( 10 )
+ public Integer getSize() {
+ return size;
+ }
+
+ public void setSize(Integer size) {
+ this.size = size;
+ }
+}
Added:
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/Color.java
===================================================================
---
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/Color.java
(rev 0)
+++
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/Color.java 2009-05-06
15:52:26 UTC (rev 16516)
@@ -0,0 +1,33 @@
+package org.hibernate.test.annotations.beanvalidation;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.GeneratedValue;
+import javax.validation.constraints.NotNull;
+
+/**
+ * @author Emmanuel Bernard
+ */
+@Entity
+public class Color {
+ private Integer id;
+ private String name;
+
+ @Id @GeneratedValue
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ @NotNull
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
Added:
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/DDLTest.java
===================================================================
---
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/DDLTest.java
(rev 0)
+++
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/DDLTest.java 2009-05-06
15:52:26 UTC (rev 16516)
@@ -0,0 +1,60 @@
+package org.hibernate.test.annotations.beanvalidation;
+
+import org.hibernate.mapping.PersistentClass;
+import org.hibernate.mapping.Column;
+import org.hibernate.mapping.Property;
+import org.hibernate.test.annotations.TestCase;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public class DDLTest extends TestCase {
+
+ public void testBasicDDL() {
+ PersistentClass classMapping = getCfg().getClassMapping( Address.class.getName() );
+ //new ClassValidator( Address.class, ResourceBundle.getBundle("messages",
Locale.ENGLISH) ).apply( classMapping );
+ Column stateColumn = (Column) classMapping.getProperty( "state"
).getColumnIterator().next();
+ assertEquals( stateColumn.getLength(), 3 );
+ Column zipColumn = (Column) classMapping.getProperty( "zip"
).getColumnIterator().next();
+ assertEquals( zipColumn.getLength(), 5 );
+ assertFalse( zipColumn.isNullable() );
+ }
+
+ public void testApplyOnIdColumn() throws Exception {
+ PersistentClass classMapping = getCfg().getClassMapping( Tv.class.getName() );
+ Column serialColumn = (Column)
classMapping.getIdentifierProperty().getColumnIterator().next();
+ assertEquals( "Vaidator annotation not applied on ids", 2,
serialColumn.getLength() );
+ }
+
+ public void testApplyOnManyToOne() throws Exception {
+ PersistentClass classMapping = getCfg().getClassMapping( TvOwner.class.getName() );
+ Column serialColumn = (Column) classMapping.getProperty( "tv"
).getColumnIterator().next();
+ assertEquals( "Validator annotations not applied on associations", false,
serialColumn.isNullable() );
+ }
+
+ public void testSingleTableAvoidNotNull() throws Exception {
+ PersistentClass classMapping = getCfg().getClassMapping( Rock.class.getName() );
+ Column serialColumn = (Column) classMapping.getProperty( "bit"
).getColumnIterator().next();
+ assertTrue( "Notnull should not be applised on single tables",
serialColumn.isNullable() );
+ }
+
+ public void testNotNullOnlyAppliedIfEmbeddedIsNotNullItself() throws Exception {
+ PersistentClass classMapping = getCfg().getClassMapping( Tv.class.getName() );
+ Property property = classMapping.getProperty( "tuner.frequency" );
+ Column serialColumn = (Column) property.getColumnIterator().next();
+ assertEquals( "Validator annotations are applied on tunner as it is
@NotNull", false, serialColumn.isNullable() );
+
+ property = classMapping.getProperty( "recorder.time" );
+ serialColumn = (Column) property.getColumnIterator().next();
+ assertEquals( "Validator annotations are applied on tunner as it is
@NotNull", true, serialColumn.isNullable() );
+ }
+
+ protected Class<?>[] getMappings() {
+ return new Class<?>[] {
+ Address.class,
+ Tv.class,
+ TvOwner.class,
+ Rock.class
+ };
+ }
+}
Added:
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/Display.java
===================================================================
---
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/Display.java
(rev 0)
+++
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/Display.java 2009-05-06
15:52:26 UTC (rev 16516)
@@ -0,0 +1,35 @@
+package org.hibernate.test.annotations.beanvalidation;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.GeneratedValue;
+import javax.validation.constraints.NotNull;
+
+/**
+ * @author Emmanuel Bernard
+ */
+@Entity
+public class Display {
+ private Integer id;
+
+ private String brand;
+
+ @Id
+ @GeneratedValue
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ @NotNull
+ public String getBrand() {
+ return brand;
+ }
+
+ public void setBrand(String brand) {
+ this.brand = brand;
+ }
+}
Added:
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/DisplayConnector.java
===================================================================
---
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/DisplayConnector.java
(rev 0)
+++
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/DisplayConnector.java 2009-05-06
15:52:26 UTC (rev 16516)
@@ -0,0 +1,38 @@
+package org.hibernate.test.annotations.beanvalidation;
+
+import javax.persistence.Embeddable;
+import javax.persistence.OneToOne;
+import javax.persistence.CascadeType;
+import javax.persistence.ManyToOne;
+import javax.validation.Valid;
+import javax.validation.constraints.Min;
+
+
+/**
+ * @author Emmanuel Bernard
+ */
+@Embeddable
+public class DisplayConnector {
+
+ private int number;
+ private Display display;
+
+ @Min(1)
+ public int getNumber() {
+ return number;
+ }
+
+ public void setNumber(int number) {
+ this.number = number;
+ }
+
+ @ManyToOne(cascade = CascadeType.PERSIST)
+ @Valid
+ public Display getDisplay() {
+ return display;
+ }
+
+ public void setDisplay(Display display) {
+ this.display = display;
+ }
+}
Added:
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/HibernateTraversableResolverTest.java
===================================================================
---
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/HibernateTraversableResolverTest.java
(rev 0)
+++
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/HibernateTraversableResolverTest.java 2009-05-06
15:52:26 UTC (rev 16516)
@@ -0,0 +1,191 @@
+package org.hibernate.test.annotations.beanvalidation;
+
+import java.math.BigDecimal;
+import javax.validation.ConstraintViolationException;
+import javax.validation.ConstraintViolation;
+
+import org.hibernate.test.annotations.TestCase;
+import org.hibernate.Session;
+import org.hibernate.Transaction;
+import org.hibernate.cfg.Configuration;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public class HibernateTraversableResolverTest extends TestCase {
+ public void testNonLazyAssocFieldWithConstraintsFailureExpected() {
+ Session s = openSession( );
+ Transaction tx = s.beginTransaction();
+
+ Screen screen = new Screen();
+ screen.setPowerSupply( null );
+ try {
+ s.persist( screen );
+ s.flush();
+ fail("@NotNull on a non lazy association is not evaluated");
+ }
+ catch ( ConstraintViolationException e ) {
+ assertEquals( 1, e.getConstraintViolations().size() );
+ }
+
+ tx.rollback();
+ s.close();
+ }
+
+ public void testEmbedded() {
+ Session s = openSession( );
+ Transaction tx = s.beginTransaction();
+
+ Screen screen = new Screen();
+ PowerSupply ps = new PowerSupply();
+ screen.setPowerSupply( ps );
+ Button button = new Button();
+ button.setName( null );
+ button.setSize( 3 );
+ screen.setStopButton( button );
+ try {
+ s.persist( screen );
+ s.flush();
+ fail("@NotNull on empedded property is not evaluated");
+ }
+ catch ( ConstraintViolationException e ) {
+ assertEquals( 1, e.getConstraintViolations().size() );
+ ConstraintViolation<?> cv = e.getConstraintViolations().iterator().next();
+ assertEquals( Screen.class, cv.getRootBeanClass() );
+ assertEquals( "stopButton.name", cv.getPropertyPath() );
+ }
+
+ tx.rollback();
+ s.close();
+ }
+
+ public void testToOneAssocNotValidated() {
+ Session s = openSession( );
+ Transaction tx = s.beginTransaction();
+
+ Screen screen = new Screen();
+ PowerSupply ps = new PowerSupply();
+ ps.setPosition( "1" );
+ ps.setPower( new BigDecimal( 350 ) );
+ screen.setPowerSupply( ps );
+ try {
+ s.persist( screen );
+ s.flush();
+ fail("Associated objects should not be validated");
+ }
+ catch ( ConstraintViolationException e ) {
+ assertEquals( 1, e.getConstraintViolations().size() );
+ final ConstraintViolation constraintViolation =
e.getConstraintViolations().iterator().next();
+ assertEquals( PowerSupply.class, constraintViolation.getRootBeanClass() );
+ }
+
+ tx.rollback();
+ s.close();
+ }
+
+ public void testCollectionAssocNotValidated() {
+ Session s = openSession( );
+ Transaction tx = s.beginTransaction();
+
+ Screen screen = new Screen();
+ screen.setStopButton( new Button() );
+ screen.getStopButton().setName( "STOOOOOP" );
+ PowerSupply ps = new PowerSupply();
+ screen.setPowerSupply( ps );
+ Color c = new Color();
+ c.setName( "Blue" );
+ s.persist( c );
+ c.setName( null );
+ screen.getDisplayColors().add( c );
+ try {
+ s.persist( screen );
+ s.flush();
+ fail("Associated objects should not be validated");
+ }
+ catch ( ConstraintViolationException e ) {
+ assertEquals( 1, e.getConstraintViolations().size() );
+ final ConstraintViolation constraintViolation =
e.getConstraintViolations().iterator().next();
+ assertEquals( Color.class, constraintViolation.getRootBeanClass() );
+ }
+
+ tx.rollback();
+ s.close();
+ }
+
+ public void testEmbeddedCollection() {
+ Session s = openSession( );
+ Transaction tx = s.beginTransaction();
+
+ Screen screen = new Screen();
+ PowerSupply ps = new PowerSupply();
+ screen.setPowerSupply( ps );
+ DisplayConnector conn = new DisplayConnector();
+ conn.setNumber( 0 );
+ screen.getConnectors().add( conn );
+ try {
+ s.persist( screen );
+ s.flush();
+ fail("Collection of embedded objects should be validated");
+ }
+ catch ( ConstraintViolationException e ) {
+ assertEquals( 1, e.getConstraintViolations().size() );
+ final ConstraintViolation constraintViolation =
e.getConstraintViolations().iterator().next();
+ assertEquals( Screen.class, constraintViolation.getRootBeanClass() );
+ // connectors[0] should be connectors expect failure when bug is fixed in HV
+ assertEquals( "connectors[0].number", constraintViolation.getPropertyPath()
);
+ }
+
+ tx.rollback();
+ s.close();
+ }
+
+ public void testAssocInEmbeddedNotValidated() {
+ Session s = openSession( );
+ Transaction tx = s.beginTransaction();
+
+ Screen screen = new Screen();
+ screen.setStopButton( new Button() );
+ screen.getStopButton().setName( "STOOOOOP" );
+ PowerSupply ps = new PowerSupply();
+ screen.setPowerSupply( ps );
+ DisplayConnector conn = new DisplayConnector();
+ conn.setNumber( 1 );
+ screen.getConnectors().add( conn );
+ final Display display = new Display();
+ display.setBrand( "dell" );
+ conn.setDisplay( display );
+ s.persist( display );
+ s.flush();
+ try {
+ display.setBrand( null );
+ s.persist( screen );
+ s.flush();
+ fail("Collection of embedded objects should be validated");
+ }
+ catch ( ConstraintViolationException e ) {
+ assertEquals( 1, e.getConstraintViolations().size() );
+ final ConstraintViolation constraintViolation =
e.getConstraintViolations().iterator().next();
+ assertEquals( Display.class, constraintViolation.getRootBeanClass() );
+ }
+
+ tx.rollback();
+ s.close();
+ }
+
+ @Override
+ protected void configure(Configuration cfg) {
+ super.configure( cfg );
+ cfg.setProperty( "hibernate.validator.autoregister_listeners",
"false" );
+ }
+
+ protected Class<?>[] getMappings() {
+ return new Class<?>[] {
+ Button.class,
+ Color.class,
+ Display.class,
+ DisplayConnector.class,
+ PowerSupply.class,
+ Screen.class
+ };
+ }
+}
Added:
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/Music.java
===================================================================
---
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/Music.java
(rev 0)
+++
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/Music.java 2009-05-06
15:52:26 UTC (rev 16516)
@@ -0,0 +1,13 @@
+package org.hibernate.test.annotations.beanvalidation;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+
+/**
+ * @author Emmanuel Bernard
+ */
+@Entity
+public class Music {
+ @Id
+ public String name;
+}
Added:
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/PowerSupply.java
===================================================================
---
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/PowerSupply.java
(rev 0)
+++
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/PowerSupply.java 2009-05-06
15:52:26 UTC (rev 16516)
@@ -0,0 +1,46 @@
+package org.hibernate.test.annotations.beanvalidation;
+
+import java.math.BigDecimal;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Column;
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+
+/**
+ * @author Emmanuel Bernard
+ */
+@Entity
+public class PowerSupply {
+ private Integer id;
+ private BigDecimal power;
+ private String position;
+
+ @Id @GeneratedValue
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ @Min(100) @Max(250)
+ public BigDecimal getPower() {
+ return power;
+ }
+
+ public void setPower(BigDecimal power) {
+ this.power = power;
+ }
+
+ @Column(name="fld_pos")
+ public String getPosition() {
+ return position;
+ }
+
+ public void setPosition(String position) {
+ this.position = position;
+ }
+}
Added:
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/Rock.java
===================================================================
---
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/Rock.java
(rev 0)
+++
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/Rock.java 2009-05-06
15:52:26 UTC (rev 16516)
@@ -0,0 +1,13 @@
+package org.hibernate.test.annotations.beanvalidation;
+
+import javax.persistence.Entity;
+import javax.validation.constraints.NotNull;
+
+/**
+ * @author Emmanuel Bernard
+ */
+@Entity
+public class Rock extends Music {
+ @NotNull
+ public Integer bit;
+}
Added:
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/Screen.java
===================================================================
---
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/Screen.java
(rev 0)
+++
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/Screen.java 2009-05-06
15:52:26 UTC (rev 16516)
@@ -0,0 +1,74 @@
+package org.hibernate.test.annotations.beanvalidation;
+
+import java.util.Set;
+import java.util.HashSet;
+import javax.persistence.Entity;
+import javax.persistence.ManyToOne;
+import javax.persistence.ManyToMany;
+import javax.persistence.Id;
+import javax.persistence.GeneratedValue;
+import javax.persistence.CascadeType;
+import javax.validation.constraints.NotNull;
+import javax.validation.Valid;
+
+import org.hibernate.annotations.CollectionOfElements;
+import org.hibernate.annotations.Cascade;
+
+/**
+ * @author Emmanuel Bernard
+ */
+@Entity
+public class Screen {
+ private Integer id;
+ private Button stopButton;
+ private PowerSupply powerSupply;
+ private Set<DisplayConnector> connectors = new HashSet<DisplayConnector>();
+ private Set<Color> displayColors = new HashSet<Color>();
+
+ @Id @GeneratedValue
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ //@NotNull
+ @Valid
+ public Button getStopButton() {
+ return stopButton;
+ }
+
+ public void setStopButton(Button stopButton) {
+ this.stopButton = stopButton;
+ }
+
+ @ManyToOne(cascade = CascadeType.PERSIST) @Valid
+ @NotNull
+ public PowerSupply getPowerSupply() {
+ return powerSupply;
+ }
+
+ public void setPowerSupply(PowerSupply powerSupply) {
+ this.powerSupply = powerSupply;
+ }
+
+ @CollectionOfElements @Valid
+ public Set<DisplayConnector> getConnectors() {
+ return connectors;
+ }
+
+ public void setConnectors(Set<DisplayConnector> connectors) {
+ this.connectors = connectors;
+ }
+
+ @ManyToMany(cascade = CascadeType.PERSIST)
+ public Set<Color> getDisplayColors() {
+ return displayColors;
+ }
+
+ public void setDisplayColors(Set<Color> displayColors) {
+ this.displayColors = displayColors;
+ }
+}
Added:
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/Tv.java
===================================================================
---
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/Tv.java
(rev 0)
+++
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/Tv.java 2009-05-06
15:52:26 UTC (rev 16516)
@@ -0,0 +1,50 @@
+package org.hibernate.test.annotations.beanvalidation;
+
+import java.math.BigInteger;
+import java.math.BigDecimal;
+import java.util.Date;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Embeddable;
+import javax.validation.constraints.Future;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.Size;
+import javax.validation.constraints.NotNull;
+import javax.validation.Valid;
+
+/**
+ * @author Emmanuel Bernard
+ */
+@Entity
+public class Tv {
+
+ @Id
+ @Size(max = 2)
+ public String serial;
+ public int size;
+ @Size(max = 2)
+ public String name;
+ @Future
+ public Date expDate;
+ @Size(min = 0)
+ public String description;
+ @Min(1000)
+ public BigInteger lifetime;
+ @NotNull @Valid
+ public Tuner tuner;
+ @Valid
+ public Recorder recorder;
+
+ @Embeddable
+ public static class Tuner {
+ @NotNull
+ public String frequency;
+ }
+
+ @Embeddable
+ public static class Recorder {
+ @NotNull
+ public BigDecimal time;
+ }
+
+}
Added:
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/TvOwner.java
===================================================================
---
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/TvOwner.java
(rev 0)
+++
core/trunk/annotations/src/test/java/org/hibernate/test/annotations/beanvalidation/TvOwner.java 2009-05-06
15:52:26 UTC (rev 16516)
@@ -0,0 +1,21 @@
+package org.hibernate.test.annotations.beanvalidation;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.GeneratedValue;
+import javax.persistence.ManyToOne;
+import javax.validation.constraints.NotNull;
+
+/**
+ * @author Emmanuel Bernard
+ */
+@Entity
+public class TvOwner {
+ @Id
+ @GeneratedValue
+ public Integer id;
+
+ @ManyToOne
+ @NotNull
+ public Tv tv;
+}
\ No newline at end of file