Author: epbernard Date: 2006-03-11 20:43:12 -0500 (Sat, 11 Mar 2006) New Revision: 9606 Added: trunk/HibernateExt/metadata/src/test/org/hibernate/validator/test/collections/ trunk/HibernateExt/metadata/src/test/org/hibernate/validator/test/collections/Movie.java trunk/HibernateExt/metadata/src/test/org/hibernate/validator/test/collections/Presenter.java trunk/HibernateExt/metadata/src/test/org/hibernate/validator/test/collections/Show.java trunk/HibernateExt/metadata/src/test/org/hibernate/validator/test/collections/Tv.java trunk/HibernateExt/metadata/src/test/org/hibernate/validator/test/collections/ValidationCollectionTest.java Modified: trunk/HibernateExt/metadata/src/java/org/hibernate/validator/ClassValidator.java Log: ANN-208 Modified: trunk/HibernateExt/metadata/src/java/org/hibernate/validator/ClassValidator.java =================================================================== --- trunk/HibernateExt/metadata/src/java/org/hibernate/validator/ClassValidator.java 2006-03-11 20:42:40 UTC (rev 9605) +++ trunk/HibernateExt/metadata/src/java/org/hibernate/validator/ClassValidator.java 2006-03-12 01:43:12 UTC (rev 9606) @@ -9,6 +9,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -29,12 +30,12 @@ import org.hibernate.MappingException; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; +import org.hibernate.reflection.Filter; import org.hibernate.reflection.ReflectionManager; import org.hibernate.reflection.XClass; import org.hibernate.reflection.XMember; import org.hibernate.reflection.XMethod; import org.hibernate.reflection.XProperty; -import org.hibernate.reflection.Filter; import org.hibernate.util.IdentitySet; @@ -48,6 +49,15 @@ //TODO Define magic number private static Log log = LogFactory.getLog( ClassValidator.class ); private static final InvalidValue[] EMPTY_INVALID_VALUE_ARRAY = new InvalidValue[]{}; + private static final String DEFAULT_VALIDATOR_MESSAGE = "org.hibernate.validator.resources.DefaultValidatorMessages"; + private static final Set<Class> INDEXABLE_CLASS = new HashSet<Class>(); + + static { + INDEXABLE_CLASS.add( Integer.class ); + INDEXABLE_CLASS.add( Long.class ); + INDEXABLE_CLASS.add( String.class ); + } + private final Class<T> beanClass; private transient ResourceBundle messageBundle; private transient boolean defaultResourceBundle; @@ -58,9 +68,7 @@ private transient List<XMember> memberGetters; private transient Map<Validator, String> messages; private transient List<XMember> childGetters; - private static final String DEFAULT_VALIDATOR_MESSAGE = "org.hibernate.validator.resources.DefaultValidatorMessages"; - /** * create the validator engine for this bean type */ @@ -99,8 +107,8 @@ try { //use context class loader as a first citizen ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); - if (contextClassLoader == null) { - throw new MissingResourceException("No context classloader", null, "ValidatorMessage"); + if ( contextClassLoader == null ) { + throw new MissingResourceException( "No context classloader", null, "ValidatorMessage" ); } rb = ResourceBundle.getBundle( "ValidatorMessages", @@ -108,18 +116,20 @@ contextClassLoader ); } - catch( MissingResourceException e) { - log.trace( "ResourceBundle ValidatorMessages not found in thread context classloader"); + catch (MissingResourceException e) { + log.trace( "ResourceBundle ValidatorMessages not found in thread context classloader" ); //then use the Validator Framework classloader try { rb = ResourceBundle.getBundle( - "ValidatorMessages", - Locale.getDefault(), - this.getClass().getClassLoader() + "ValidatorMessages", + Locale.getDefault(), + this.getClass().getClassLoader() ); } - catch( MissingResourceException ee) { - log.debug( "ResourceBundle ValidatorMessages not found in Validator classloader. Delegate to " + DEFAULT_VALIDATOR_MESSAGE); + catch (MissingResourceException ee) { + log.debug( + "ResourceBundle ValidatorMessages not found in Validator classloader. Delegate to " + DEFAULT_VALIDATOR_MESSAGE + ); //the user did not override the default ValidatorMessages rb = ResourceBundle.getBundle( DEFAULT_VALIDATOR_MESSAGE ); } @@ -138,30 +148,30 @@ messages = new HashMap<Validator, String>(); childGetters = new ArrayList<XMember>(); + //build the class hierarchy to look for members in childClassValidators.put( xClass, this ); Collection<XClass> classes = new HashSet<XClass>(); addSuperClassesAndInterfaces( xClass, classes ); - for (XClass currentClass : classes) { + for ( XClass currentClass : classes ) { Annotation[] classAnnotations = currentClass.getAnnotations(); for ( int i = 0; i < classAnnotations.length ; i++ ) { Annotation classAnnotation = classAnnotations[i]; Validator beanValidator = createValidator( classAnnotation ); if ( beanValidator != null ) beanValidators.add( beanValidator ); } - }; - //build the class hierarchy to look for members in + } + ; - //Check on all selected classes for ( XClass currClass : classes ) { List<XMethod> methods = currClass.getDeclaredMethods(); - for (XMethod method : methods) { + for ( XMethod method : methods ) { createMemberValidator( method ); - createChildValidator( resourceBundle, method, method.getType() ); + createChildValidator( resourceBundle, method ); } - List<XProperty> fields = currClass.getDeclaredProperties("field", new Filter() { - + List<XProperty> fields = currClass.getDeclaredProperties( + "field", new Filter() { public boolean returnStatic() { return true; } @@ -169,12 +179,14 @@ public boolean returnTransient() { return true; } - } ); + } + ); for ( XProperty field : fields ) { - if( !field.isTypeResolved() ) - throw new IllegalStateException( "Couldn't bind the type of property field " + field ); + if ( !field.isTypeResolved() ) { + throw new IllegalStateException( "Couldn't bind the type of property field " + field ); + } createMemberValidator( field ); - createChildValidator( resourceBundle, field, field.getType() ); + createChildValidator( resourceBundle, field ); } } } @@ -183,21 +195,24 @@ for ( XClass currClass = clazz; currClass != null ; currClass = currClass.getSuperclass() ) { if ( ! classes.add( currClass ) ) return; XClass[] interfaces = currClass.getInterfaces(); - for (XClass interf : interfaces) { + for ( XClass interf : interfaces ) { addSuperClassesAndInterfaces( interf, classes ); } } } @SuppressWarnings("unchecked") - private void createChildValidator(ResourceBundle resourceBundle, XMember member, XClass clazz) { + private void createChildValidator(ResourceBundle resourceBundle, XMember member) { if ( member.isAnnotationPresent( Valid.class ) ) { setAccessible( member ); childGetters.add( member ); - //if ( Iterable.class.isAssignableFrom(clazz) || Map.class.isAssignableFrom(clazz) ) { - - // } - // else + XClass clazz; + if ( member.isCollection() || member.isArray() ) { + clazz = member.getElementClass(); + } + else { + clazz = member.getType(); + } if ( !childClassValidators.containsKey( clazz ) ) { new ClassValidator( clazz, resourceBundle, childClassValidators ); } @@ -284,7 +299,7 @@ for ( int i = 0; i < memberValidators.size() ; i++ ) { XMember getter = memberGetters.get( i ); - if ( Hibernate.isPropertyInitialized(bean, getPropertyName( getter ) ) ) { + if ( Hibernate.isPropertyInitialized( bean, getPropertyName( getter ) ) ) { Object value = getMemberValue( bean, getter ); Validator validator = memberValidators.get( i ); if ( !validator.isValid( value ) ) { @@ -296,16 +311,72 @@ for ( int i = 0; i < childGetters.size() ; i++ ) { XMember getter = childGetters.get( i ); - if ( Hibernate.isPropertyInitialized(bean, getPropertyName( getter ) ) ) { + if ( Hibernate.isPropertyInitialized( bean, getPropertyName( getter ) ) ) { Object value = getMemberValue( bean, getter ); if ( value != null && Hibernate.isInitialized( value ) ) { String propertyName = getPropertyName( getter ); - InvalidValue[] invalidValues = getClassValidator( value ) - .getInvalidValues( value, circularityState ); - for ( InvalidValue invalidValue : invalidValues ) { - invalidValue.addParentBean( bean, propertyName ); - results.add( invalidValue ); + if ( getter.isCollection() ) { + int index = 0; + boolean isIterable = value instanceof Iterable; + Map map = ! isIterable ? (Map) value : null; + Iterable elements = isIterable ? + (Iterable) value : + map.keySet(); + for ( Object element : elements ) { + Object actualElement = isIterable ? element : map.get( element ); + if (actualElement == null) { + index++; + continue; + } + InvalidValue[] invalidValues = getClassValidator( actualElement ) + .getInvalidValues( actualElement, circularityState ); + + String indexedPropName = MessageFormat.format( + "{0}[{1}]", + propertyName, + INDEXABLE_CLASS.contains( element.getClass() ) ? + ("'" + element + "'") : + index + ); + index++; + + for ( InvalidValue invalidValue : invalidValues ) { + invalidValue.addParentBean( bean, indexedPropName ); + results.add( invalidValue ); + } + } } + if ( getter.isArray() ) { + int index = 0; + for ( Object element : (Object[]) value ) { + if (element == null) { + index++; + continue; + } + InvalidValue[] invalidValues = getClassValidator( element ) + .getInvalidValues( element, circularityState ); + + String indexedPropName = MessageFormat.format( + "{0}[{1}]", + propertyName, + index + ); + index++; + + for ( InvalidValue invalidValue : invalidValues ) { + invalidValue.addParentBean( bean, indexedPropName ); + results.add( invalidValue ); + } + } + } + else { + InvalidValue[] invalidValues = getClassValidator( value ) + .getInvalidValues( value, circularityState ); + for ( InvalidValue invalidValue : invalidValues ) { + invalidValue.addParentBean( bean, propertyName ); + results.add( invalidValue ); + } + } } } } @@ -485,8 +556,11 @@ ResourceBundle rb = messageBundle; if ( rb != null && ! ( rb instanceof Serializable ) ) { messageBundle = null; - if ( ! defaultResourceBundle ) - log.warn( "Serializing a ClassValidator with a not serializable ResourceBundle: ResourceBundle ignored" ); + if ( ! defaultResourceBundle ) { + log.warn( + "Serializing a ClassValidator with a not serializable ResourceBundle: ResourceBundle ignored" + ); + } } oos.defaultWriteObject(); oos.writeObject( messageBundle ); @@ -496,7 +570,7 @@ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ois.defaultReadObject(); ResourceBundle rb = (ResourceBundle) ois.readObject(); - if (rb == null) rb = getDefaultResourceBundle(); + if ( rb == null ) rb = getDefaultResourceBundle(); initValidator( ReflectionManager.INSTANCE.toXClass( beanClass ), new HashMap<XClass, ClassValidator>(), rb ); } } Added: trunk/HibernateExt/metadata/src/test/org/hibernate/validator/test/collections/Movie.java =================================================================== --- trunk/HibernateExt/metadata/src/test/org/hibernate/validator/test/collections/Movie.java 2006-03-11 20:42:40 UTC (rev 9605) +++ trunk/HibernateExt/metadata/src/test/org/hibernate/validator/test/collections/Movie.java 2006-03-12 01:43:12 UTC (rev 9606) @@ -0,0 +1,12 @@ +//$Id: $ +package org.hibernate.validator.test.collections; + +import org.hibernate.validator.NotNull; + +/** + * @author Emmanuel Bernard + */ +public class Movie { + @NotNull + public String name; +} Added: trunk/HibernateExt/metadata/src/test/org/hibernate/validator/test/collections/Presenter.java =================================================================== --- trunk/HibernateExt/metadata/src/test/org/hibernate/validator/test/collections/Presenter.java 2006-03-11 20:42:40 UTC (rev 9605) +++ trunk/HibernateExt/metadata/src/test/org/hibernate/validator/test/collections/Presenter.java 2006-03-12 01:43:12 UTC (rev 9606) @@ -0,0 +1,12 @@ +//$Id: $ +package org.hibernate.validator.test.collections; + +import org.hibernate.validator.NotNull; + +/** + * @author Emmanuel Bernard + */ +public class Presenter { + @NotNull + public String name; +} Added: trunk/HibernateExt/metadata/src/test/org/hibernate/validator/test/collections/Show.java =================================================================== --- trunk/HibernateExt/metadata/src/test/org/hibernate/validator/test/collections/Show.java 2006-03-11 20:42:40 UTC (rev 9605) +++ trunk/HibernateExt/metadata/src/test/org/hibernate/validator/test/collections/Show.java 2006-03-12 01:43:12 UTC (rev 9606) @@ -0,0 +1,12 @@ +//$Id: $ +package org.hibernate.validator.test.collections; + +import org.hibernate.validator.NotNull; + +/** + * @author Emmanuel Bernard + */ +public class Show { + @NotNull + public String name; +} Added: trunk/HibernateExt/metadata/src/test/org/hibernate/validator/test/collections/Tv.java =================================================================== --- trunk/HibernateExt/metadata/src/test/org/hibernate/validator/test/collections/Tv.java 2006-03-11 20:42:40 UTC (rev 9605) +++ trunk/HibernateExt/metadata/src/test/org/hibernate/validator/test/collections/Tv.java 2006-03-12 01:43:12 UTC (rev 9606) @@ -0,0 +1,26 @@ +//$Id: $ +package org.hibernate.validator.test.collections; + +import java.util.List; +import java.util.Map; +import java.util.ArrayList; +import java.util.HashMap; + +import org.hibernate.validator.NotNull; +import org.hibernate.validator.Valid; + +/** + * @author Emmanuel Bernard + */ +public class Tv { + @NotNull + public String name; + + @Valid + public List<Presenter> presenters = new ArrayList<Presenter>(); + + @Valid + public Map<String, Show> shows = new HashMap<String, Show>(); + + public @Valid Movie[] movies; +} Added: trunk/HibernateExt/metadata/src/test/org/hibernate/validator/test/collections/ValidationCollectionTest.java =================================================================== --- trunk/HibernateExt/metadata/src/test/org/hibernate/validator/test/collections/ValidationCollectionTest.java 2006-03-11 20:42:40 UTC (rev 9605) +++ trunk/HibernateExt/metadata/src/test/org/hibernate/validator/test/collections/ValidationCollectionTest.java 2006-03-12 01:43:12 UTC (rev 9606) @@ -0,0 +1,59 @@ +//$Id: $ +package org.hibernate.validator.test.collections; + +import org.hibernate.validator.ClassValidator; +import org.hibernate.validator.InvalidValue; +import junit.framework.TestCase; + +/** + * @author Emmanuel Bernard + */ +public class ValidationCollectionTest extends TestCase { + public void testCollection() throws Exception { + Tv tv = new Tv(); + tv.name = "France 2"; + Presenter presNok = new Presenter(); + presNok.name = null; + Presenter presOk = new Presenter(); + presOk.name = "Thierry Ardisson"; + tv.presenters.add(presOk); + tv.presenters.add( presNok ); + ClassValidator validator = new ClassValidator(Tv.class); + InvalidValue[] values = validator.getInvalidValues( tv ); + assertEquals( 1, values.length ); + assertEquals( "presenters[1].name", values[0].getPropertyPath() ); + } + + public void testMap() throws Exception { + Tv tv = new Tv(); + tv.name = "France 2"; + Show showOk = new Show(); + showOk.name = "Tout le monde en parle"; + Show showNok = new Show(); + showNok.name = null; + tv.shows.put("Midnight", showOk); + tv.shows.put("Primetime", showNok); + ClassValidator validator = new ClassValidator(Tv.class); + InvalidValue[] values = validator.getInvalidValues( tv ); + assertEquals( 1, values.length ); + assertEquals( "shows['Primetime'].name", values[0].getPropertyPath() ); + } + + public void testArray() throws Exception { + Tv tv = new Tv(); + tv.name = "France 2"; + Movie movieOk = new Movie(); + movieOk.name = "Kill Bill"; + Movie movieNok = new Movie(); + movieNok.name = null; + tv.movies = new Movie[] { + movieOk, + null, + movieNok + }; + ClassValidator validator = new ClassValidator(Tv.class); + InvalidValue[] values = validator.getInvalidValues( tv ); + assertEquals( 1, values.length ); + assertEquals( "movies[2].name", values[0].getPropertyPath() ); + } +} |