ANN-828 Use Bean Validation for DDL generation
git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@16516 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
parent
e8dfecb5f7
commit
a7930671d9
|
@ -396,6 +396,11 @@ public class AnnotationConfiguration extends Configuration {
|
|||
}
|
||||
}
|
||||
}
|
||||
applyDDLOnBeanValidation( (Collection<PersistentClass>) classes.values(), getProperties() );
|
||||
}
|
||||
|
||||
private void applyDDLOnBeanValidation(Collection<PersistentClass> persistentClasses, Properties properties) {
|
||||
BeanValidationActivator.applyDDL( persistentClasses, properties );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,6 +2,9 @@ package org.hibernate.cfg.beanvalidation;
|
|||
|
||||
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.slf4j.LoggerFactory;
|
|||
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,73 @@ public class BeanValidationActivator {
|
|||
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -68,15 +112,37 @@ public class BeanValidationActivator {
|
|||
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() );
|
||||
|
|
|
@ -6,6 +6,7 @@ import java.util.Map;
|
|||
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.PreUpdateEvent;
|
|||
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 @@ import org.hibernate.util.ReflectHelper;
|
|||
//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 @@ public class BeanValidationEventListener implements
|
|||
//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 @@ public class BeanValidationEventListener implements
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -1,22 +1,49 @@
|
|||
package org.hibernate.cfg.beanvalidation;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import javax.validation.ValidatorFactory;
|
||||
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.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 @@ class TypeSafeActivator {
|
|||
}
|
||||
}
|
||||
|
||||
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 );
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue