[hibernate-commits] Hibernate SVN: r10483 - in trunk/HibernateExt/metadata/src: java/org/hibernate/validator java/org/hibernate/validator/interpolator test/org/hibernate/validator/test

hibernate-commits at lists.jboss.org hibernate-commits at lists.jboss.org
Thu Sep 14 09:41:03 EDT 2006


Author: epbernard
Date: 2006-09-14 09:41:02 -0400 (Thu, 14 Sep 2006)
New Revision: 10483

Added:
   trunk/HibernateExt/metadata/src/java/org/hibernate/validator/MessageInterpolator.java
   trunk/HibernateExt/metadata/src/java/org/hibernate/validator/interpolator/
   trunk/HibernateExt/metadata/src/java/org/hibernate/validator/interpolator/DefaultMessageInterpolator.java
   trunk/HibernateExt/metadata/src/java/org/hibernate/validator/interpolator/DefaultMessageInterpolatorAggerator.java
Modified:
   trunk/HibernateExt/metadata/src/java/org/hibernate/validator/ClassValidator.java
   trunk/HibernateExt/metadata/src/test/org/hibernate/validator/test/ValidatorTest.java
Log:
ANN-440 MessageIntrospector

Modified: trunk/HibernateExt/metadata/src/java/org/hibernate/validator/ClassValidator.java
===================================================================
--- trunk/HibernateExt/metadata/src/java/org/hibernate/validator/ClassValidator.java	2006-09-13 12:48:00 UTC (rev 10482)
+++ trunk/HibernateExt/metadata/src/java/org/hibernate/validator/ClassValidator.java	2006-09-14 13:41:02 UTC (rev 10483)
@@ -28,6 +28,7 @@
 import org.hibernate.AssertionFailure;
 import org.hibernate.Hibernate;
 import org.hibernate.MappingException;
+import org.hibernate.validator.interpolator.DefaultMessageInterpolatorAggerator;
 import org.hibernate.cfg.annotations.Version;
 import org.hibernate.cfg.BinderHelper;
 import org.hibernate.mapping.PersistentClass;
@@ -76,14 +77,15 @@
 	private transient List<Validator> beanValidators;
 	private transient List<Validator> memberValidators;
 	private transient List<XMember> memberGetters;
-	private transient Map<Validator, String> messages;
 	private transient List<XMember> childGetters;
+	private transient DefaultMessageInterpolatorAggerator defaultInterpolator;
+	private transient MessageInterpolator userInterpolator;
 
 	/**
 	 * create the validator engine for this bean type
 	 */
 	public ClassValidator(Class<T> beanClass) {
-		this( beanClass, null );
+		this( beanClass, (ResourceBundle) null );
 	}
 
 	/**
@@ -91,11 +93,19 @@
 	 * for message rendering on violation
 	 */
 	public ClassValidator(Class<T> beanClass, ResourceBundle resourceBundle) {
-		this( beanClass, resourceBundle, new HashMap<XClass, ClassValidator>() );
+		this( beanClass, resourceBundle, null, new HashMap<XClass, ClassValidator>() );
 	}
 
+	/**
+	 * create the validator engine for a particular bean class, using a custom message interpolator
+	 * for message rendering on violation
+	 */
+	public ClassValidator(Class<T> beanClass, MessageInterpolator interpolator) {
+		this( beanClass, null, interpolator, new HashMap<XClass, ClassValidator>() );
+	}
+
 	protected ClassValidator(
-			Class<T> beanClass, ResourceBundle resourceBundle, Map<XClass, ClassValidator> childClassValidators
+			Class<T> beanClass, ResourceBundle resourceBundle, MessageInterpolator interpolator, Map<XClass, ClassValidator> childClassValidators
 	) {
 		XClass beanXClass = reflectionManager.toXClass( beanClass );
 		this.beanClass = beanClass;
@@ -103,21 +113,24 @@
 				getDefaultResourceBundle() :
 				resourceBundle;
 		this.defaultMessageBundle = ResourceBundle.getBundle( DEFAULT_VALIDATOR_MESSAGE );
+		this.userInterpolator = interpolator;
 		this.childClassValidators = childClassValidators;
-		initValidator( beanXClass, childClassValidators, this.messageBundle );
+		initValidator( beanXClass, childClassValidators );
 	}
 
 	@SuppressWarnings("unchecked")
 	protected ClassValidator(
-			XClass beanXClass, ResourceBundle resourceBundle, Map<XClass, ClassValidator> childClassValidators
+			XClass beanXClass, ResourceBundle resourceBundle, MessageInterpolator userInterpolator,
+			Map<XClass, ClassValidator> childClassValidators
 	) {
 		this.beanClass = reflectionManager.toClass( beanXClass );
 		this.messageBundle = resourceBundle == null ?
 				getDefaultResourceBundle() :
 				resourceBundle;
 		this.defaultMessageBundle = ResourceBundle.getBundle( DEFAULT_VALIDATOR_MESSAGE );
+		this.userInterpolator = userInterpolator;
 		this.childClassValidators = childClassValidators;
-		initValidator( beanXClass, childClassValidators, this.messageBundle );
+		initValidator( beanXClass, childClassValidators );
 	}
 
 	private ResourceBundle getDefaultResourceBundle() {
@@ -157,14 +170,14 @@
 	}
 
 	private void initValidator(
-			XClass xClass, Map<XClass, ClassValidator> childClassValidators,
-			ResourceBundle resourceBundle
+			XClass xClass, Map<XClass, ClassValidator> childClassValidators
 	) {
 		beanValidators = new ArrayList<Validator>();
 		memberValidators = new ArrayList<Validator>();
 		memberGetters = new ArrayList<XMember>();
-		messages = new HashMap<Validator, String>();
 		childGetters = new ArrayList<XMember>();
+		defaultInterpolator = new DefaultMessageInterpolatorAggerator();
+		defaultInterpolator.initialize( messageBundle, defaultMessageBundle );
 
 		//build the class hierarchy to look for members in
 		childClassValidators.put( xClass, this );
@@ -184,7 +197,7 @@
 			List<XMethod> methods = currClass.getDeclaredMethods();
 			for ( XMethod method : methods ) {
 				createMemberValidator( method );
-				createChildValidator( resourceBundle, method );
+				createChildValidator( method );
 			}
 
 			List<XProperty> fields = currClass.getDeclaredProperties(
@@ -200,7 +213,7 @@
 			);
 			for ( XProperty field : fields ) {
 				createMemberValidator( field );
-				createChildValidator( resourceBundle, field );
+				createChildValidator( field );
 			}
 		}
 	}
@@ -216,7 +229,7 @@
 	}
 
 	@SuppressWarnings("unchecked")
-	private void createChildValidator(ResourceBundle resourceBundle, XMember member) {
+	private void createChildValidator( XMember member) {
 		if ( member.isAnnotationPresent( Valid.class ) ) {
 			setAccessible( member );
 			childGetters.add( member );
@@ -228,7 +241,7 @@
 				clazz = member.getType();
 			}
 			if ( !childClassValidators.containsKey( clazz ) ) {
-				new ClassValidator( clazz, resourceBundle, childClassValidators );
+				new ClassValidator( clazz, messageBundle, userInterpolator, childClassValidators );
 			}
 		}
 	}
@@ -266,11 +279,7 @@
 			}
 			Validator beanValidator = validatorClass.value().newInstance();
 			beanValidator.initialize( annotation );
-			String messageTemplate = (String) annotation.getClass()
-					.getMethod( "message", (Class[]) null )
-					.invoke( annotation );
-			String message = replace( messageTemplate, annotation );
-			messages.put( beanValidator, message );
+			defaultInterpolator.addInterpolator( annotation, beanValidator );
 			return beanValidator;
 		}
 		catch (Exception e) {
@@ -312,7 +321,7 @@
 		for ( int i = 0; i < beanValidators.size() ; i++ ) {
 			Validator validator = beanValidators.get( i );
 			if ( !validator.isValid( bean ) ) {
-				results.add( new InvalidValue( messages.get( validator ), beanClass, null, bean, bean ) );
+				results.add( new InvalidValue( interpolate(validator), beanClass, null, bean, bean ) );
 			}
 		}
 
@@ -323,7 +332,7 @@
 				Validator validator = memberValidators.get( i );
 				if ( !validator.isValid( value ) ) {
 					String propertyName = getPropertyName( getter );
-					results.add( new InvalidValue( messages.get( validator ), beanClass, propertyName, value, bean ) );
+					results.add( new InvalidValue( interpolate(validator), beanClass, propertyName, value, bean ) );
 				}
 			}
 		}
@@ -403,6 +412,16 @@
 		return results.toArray( new InvalidValue[results.size()] );
 	}
 
+	private String interpolate(Validator validator) {
+		String message = defaultInterpolator.getAnnotationMessage( validator );
+		if (userInterpolator != null) {
+			return userInterpolator.interpolate( message, validator, defaultInterpolator );
+		}
+		else {
+			return defaultInterpolator.interpolate( message, validator, null);
+		}
+	}
+
 	@SuppressWarnings("unchecked")
 	private ClassValidator getClassValidator(Object value) {
 		Class clazz = value.getClass();
@@ -427,7 +446,7 @@
 				Object value = getMemberValue( bean, getter );
 				Validator validator = memberValidators.get( i );
 				if ( !validator.isValid( value ) ) {
-					results.add( new InvalidValue( messages.get( validator ), beanClass, propertyName, value, bean ) );
+					results.add( new InvalidValue( interpolate(validator), beanClass, propertyName, value, bean ) );
 				}
 			}
 		}
@@ -449,7 +468,7 @@
 			if ( getPropertyName( getter ).equals( propertyName ) ) {
 				Validator validator = memberValidators.get( i );
 				if ( !validator.isValid( value ) ) {
-					results.add( new InvalidValue( messages.get( validator ), beanClass, propertyName, value, null ) );
+					results.add( new InvalidValue( interpolate(validator), beanClass, propertyName, value, null ) );
 				}
 			}
 		}
@@ -490,6 +509,7 @@
 		return propertyName;
 	}
 
+	/** @deprecated */
 	private String replace(String message, Annotation parameters) {
 		StringTokenizer tokens = new StringTokenizer( message, "#{}", true );
 		StringBuilder buf = new StringBuilder( 30 );
@@ -595,6 +615,7 @@
 
 	private void writeObject(ObjectOutputStream oos) throws IOException {
 		ResourceBundle rb = messageBundle;
+		MessageInterpolator interpolator = this.userInterpolator;
 		if ( rb != null && ! ( rb instanceof Serializable ) ) {
 			messageBundle = null;
 			if ( ! isUserProvidedResourceBundle ) {
@@ -603,9 +624,15 @@
 				);
 			}
 		}
+		if (interpolator != null && ! (interpolator instanceof Serializable) ) {
+			userInterpolator = null;
+			log.warn( "Serializing a non serializable MessageInterpolator" );
+		}
 		oos.defaultWriteObject();
 		oos.writeObject( messageBundle );
+		oos.writeObject( userInterpolator );
 		messageBundle = rb;
+		userInterpolator = interpolator;
 	}
 
 	private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
@@ -613,8 +640,9 @@
 		ResourceBundle rb = (ResourceBundle) ois.readObject();
 		if ( rb == null ) rb = getDefaultResourceBundle();
 		this.messageBundle = rb;
+		this.userInterpolator = (MessageInterpolator) ois.readObject();
 		this.defaultMessageBundle = ResourceBundle.getBundle( DEFAULT_VALIDATOR_MESSAGE );
 		reflectionManager = new JavaXFactory();
-		initValidator( reflectionManager.toXClass( beanClass ), new HashMap<XClass, ClassValidator>(), this.messageBundle );
+		initValidator( reflectionManager.toXClass( beanClass ), new HashMap<XClass, ClassValidator>() );
 	}
 }

Added: trunk/HibernateExt/metadata/src/java/org/hibernate/validator/MessageInterpolator.java
===================================================================
--- trunk/HibernateExt/metadata/src/java/org/hibernate/validator/MessageInterpolator.java	2006-09-13 12:48:00 UTC (rev 10482)
+++ trunk/HibernateExt/metadata/src/java/org/hibernate/validator/MessageInterpolator.java	2006-09-14 13:41:02 UTC (rev 10483)
@@ -0,0 +1,17 @@
+//$Id: $
+package org.hibernate.validator;
+
+/**
+ * Responsible for validator message interpolation (variable replacement etc)
+ * this extension point is useful if the call has some contextual informations to
+ * interpolate in validator messages
+ *
+ * @author Emmanuel Bernard
+ */
+public interface MessageInterpolator {
+	/**
+	 * Interpolate a given validator message.
+	 * The implementation is free to delegate to the default interpolator or not.
+	 */
+	String interpolate(String message, Validator validator, MessageInterpolator defaultInterpolator);
+}

Added: trunk/HibernateExt/metadata/src/java/org/hibernate/validator/interpolator/DefaultMessageInterpolator.java
===================================================================
--- trunk/HibernateExt/metadata/src/java/org/hibernate/validator/interpolator/DefaultMessageInterpolator.java	2006-09-13 12:48:00 UTC (rev 10482)
+++ trunk/HibernateExt/metadata/src/java/org/hibernate/validator/interpolator/DefaultMessageInterpolator.java	2006-09-14 13:41:02 UTC (rev 10483)
@@ -0,0 +1,135 @@
+//$Id: $
+package org.hibernate.validator.interpolator;
+
+import java.io.Serializable;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+import java.util.StringTokenizer;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.hibernate.util.StringHelper;
+import org.hibernate.validator.MessageInterpolator;
+import org.hibernate.validator.Validator;
+
+/**
+ * Resource bundle based interpolator
+ * Also interpolate annotation parameters inside the message
+ *
+ * @author Emmanuel Bernard
+ */
+public class DefaultMessageInterpolator implements MessageInterpolator, Serializable {
+	private static Log log = LogFactory.getLog( DefaultMessageInterpolator.class );
+	private Map<String, Object> annotationParameters = new HashMap<String, Object>();
+	private transient ResourceBundle messageBundle;
+	private transient ResourceBundle defaultMessageBundle;
+	private String annotationMessage;
+	private String interpolateMessage;
+
+	//not an interface method
+	public void initialize(ResourceBundle messageBundle, ResourceBundle defaultMessageBundle) {
+		this.messageBundle = messageBundle;
+		this.defaultMessageBundle = defaultMessageBundle;
+	}
+
+	public void initialize(Annotation annotation, MessageInterpolator defaultInterpolator) {
+		Class clazz = annotation.getClass();
+		for ( Method method  : clazz.getMethods() ) {
+			try {
+				//FIXME remove non serilalization elements on writeObject?
+				if ( method.getReturnType() != void.class
+						&& method.getParameterTypes().length == 0
+						&& ! Modifier.isStatic( method.getModifiers() ) ) {
+					//cannot use an exclude list because the parameter name could match a method name
+					annotationParameters.put( method.getName(), method.invoke( annotation ) );
+				}
+			}
+			catch (IllegalAccessException e) {
+				//really should not happen, but we degrade nicely
+				log.warn( "Unable to access " + StringHelper.qualify( clazz.toString(), method.getName() ) );
+			}
+			catch (InvocationTargetException e) {
+				//really should not happen, but we degrade nicely
+				log.warn( "Unable to access " + StringHelper.qualify( clazz.toString(), method.getName() ) );
+			}
+		}
+		annotationMessage = (String) annotationParameters.get( "message" );
+		if (annotationMessage == null) {
+			throw new IllegalArgumentException( "Annotation " + clazz + " does not have an (accessible) message attribute");
+		}
+		interpolateMessage = replace( annotationMessage );
+	}
+
+	private String replace(String message) {
+		StringTokenizer tokens = new StringTokenizer( message, "#{}", true );
+		StringBuilder buf = new StringBuilder( 30 );
+		boolean escaped = false;
+		boolean el = false;
+		while ( tokens.hasMoreTokens() ) {
+			String token = tokens.nextToken();
+			if ( !escaped && "#".equals( token ) ) {
+				el = true;
+			}
+			if ( !el && "{".equals( token ) ) {
+				escaped = true;
+			}
+			else if ( escaped && "}".equals( token ) ) {
+				escaped = false;
+			}
+			else if ( !escaped ) {
+				if ( "{".equals( token ) ) el = false;
+				buf.append( token );
+			}
+			else {
+				Object variable = annotationParameters.get( token );
+				if ( variable != null ) {
+					buf.append( variable );
+				}
+				else {
+					String string = null;
+					try {
+						string = messageBundle != null ? messageBundle.getString( token ) : null;
+					}
+					catch( MissingResourceException e ) {
+						//give a second chance with the default resource bundle
+					}
+					if (string == null) {
+						try {
+							string = defaultMessageBundle.getString( token );
+						}
+						catch( MissingResourceException e) {
+							throw new MissingResourceException(
+									"Can't find resource in validator bundles, key " + token,
+									defaultMessageBundle.getClass().getName(),
+									token
+							);
+						}
+					}
+					if ( string != null ) buf.append( replace( string ) );
+				}
+			}
+		}
+		return buf.toString();
+	}
+
+	public String interpolate(String message, Validator validator, MessageInterpolator defaultInterpolator) {
+		if ( annotationMessage.equals( message ) ) {
+			//short cut
+			return interpolateMessage;
+		}
+		else {
+			//TODO keep them in a weak hash map, but this might not even be useful
+			return replace( message );
+		}
+	}
+
+	public String getAnnotationMessage() {
+		return annotationMessage;
+	}
+}

Added: trunk/HibernateExt/metadata/src/java/org/hibernate/validator/interpolator/DefaultMessageInterpolatorAggerator.java
===================================================================
--- trunk/HibernateExt/metadata/src/java/org/hibernate/validator/interpolator/DefaultMessageInterpolatorAggerator.java	2006-09-13 12:48:00 UTC (rev 10482)
+++ trunk/HibernateExt/metadata/src/java/org/hibernate/validator/interpolator/DefaultMessageInterpolatorAggerator.java	2006-09-14 13:41:02 UTC (rev 10483)
@@ -0,0 +1,55 @@
+//$Id: $
+package org.hibernate.validator.interpolator;
+
+import java.lang.annotation.Annotation;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.ResourceBundle;
+import java.io.Serializable;
+
+import org.hibernate.validator.MessageInterpolator;
+import org.hibernate.validator.Validator;
+import org.hibernate.AssertionFailure;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public class DefaultMessageInterpolatorAggerator implements MessageInterpolator, Serializable {
+	private Map<Validator, DefaultMessageInterpolator> interpolators = new HashMap<Validator, DefaultMessageInterpolator>();
+	private transient ResourceBundle messageBundle;
+	private transient ResourceBundle defaultMessageBundle;
+
+	//not an interface method
+	public void initialize(ResourceBundle messageBundle, ResourceBundle defaultMessageBundle) {
+		this.messageBundle = messageBundle;
+		this.defaultMessageBundle = defaultMessageBundle;
+		//useful when we deserialize
+		for ( DefaultMessageInterpolator interpolator : interpolators.values() ) {
+			interpolator.initialize( messageBundle, defaultMessageBundle );
+		}
+	}
+
+	public void addInterpolator(Annotation annotation, Validator validator) {
+		DefaultMessageInterpolator interpolator = new DefaultMessageInterpolator();
+		interpolator.initialize(messageBundle, defaultMessageBundle );
+		interpolator.initialize( annotation, null );
+		interpolators.put( validator, interpolator );
+	}
+
+	public String interpolate(String message, Validator validator, MessageInterpolator defaultInterpolator) {
+		DefaultMessageInterpolator defaultMessageInterpolator = interpolators.get( validator );
+		if (defaultMessageInterpolator == null) {
+			return message;
+		}
+		else {
+			return defaultMessageInterpolator.interpolate( message, validator, defaultInterpolator );
+		}
+	}
+
+	public String getAnnotationMessage(Validator validator) {
+		DefaultMessageInterpolator defaultMessageInterpolator = interpolators.get( validator );
+		String message = defaultMessageInterpolator != null ? defaultMessageInterpolator.getAnnotationMessage() : null;
+		if (message == null) throw new AssertionFailure("Validator not registred to the messageInterceptorAggregator");
+		return message;
+	}
+}

Modified: trunk/HibernateExt/metadata/src/test/org/hibernate/validator/test/ValidatorTest.java
===================================================================
--- trunk/HibernateExt/metadata/src/test/org/hibernate/validator/test/ValidatorTest.java	2006-09-13 12:48:00 UTC (rev 10482)
+++ trunk/HibernateExt/metadata/src/test/org/hibernate/validator/test/ValidatorTest.java	2006-09-14 13:41:02 UTC (rev 10483)
@@ -5,10 +5,13 @@
 import java.util.Locale;
 import java.util.ResourceBundle;
 import java.math.BigInteger;
+import java.io.Serializable;
 
 import junit.framework.TestCase;
 import org.hibernate.validator.ClassValidator;
 import org.hibernate.validator.InvalidValue;
+import org.hibernate.validator.MessageInterpolator;
+import org.hibernate.validator.Validator;
 
 /**
  * @author Gavin King
@@ -111,7 +114,9 @@
 			assertEquals( "length must be between 0 and 2", invalidValues[0].getMessage() );
 		}
 		else if ( loc.toString().startsWith( "fr" ) ) {
-			assertEquals( "la longueur doit être entre 0 et 2", invalidValues[0].getMessage() );
+			String message = invalidValues[0].getMessage();
+			String message2 ="la longueur doit être entre 0 et 2"; 
+			assertEquals( message2, message );
 		}
 		else if ( loc.toString().startsWith( "da" ) ) {
 			assertEquals( "længden skal være mellem 0 og 2", invalidValues[0].getMessage() );
@@ -120,9 +125,45 @@
 			// if default not found then it must be english
 			assertEquals( "length must be between 0 and 2", invalidValues[0].getMessage() );
 		}
+	}
 
+	public class PrefixMessageInterpolator implements MessageInterpolator, Serializable {
+		private String prefix;
+
+		public PrefixMessageInterpolator(String prefix) {
+			this.prefix = prefix;
+		}
+
+		public String interpolate(String message, Validator validator, MessageInterpolator defaultInterpolator) {
+			return prefix + defaultInterpolator.interpolate( message, validator, defaultInterpolator );
+		}
 	}
 
+	public void testMessageInterpolator() throws Exception {
+		Tv tv = new Tv();
+		tv.serial = "FS";
+		tv.name = "TooLong";
+		tv.expDate = new Date( new Date().getTime() + 1000 * 60 * 60 * 24 * 10 );
+		String prefix = "Prefix";
+		ClassValidator<Tv> classValidator = new ClassValidator<Tv>( Tv.class, new PrefixMessageInterpolator( prefix )  );
+		InvalidValue[] invalidValues = classValidator.getInvalidValues( tv );
+		assertEquals( 1, invalidValues.length );
+		Locale loc = Locale.getDefault();
+		if ( loc.toString().startsWith( "en" ) ) {
+			assertEquals( prefix + "length must be between 0 and 2", invalidValues[0].getMessage() );
+		}
+		else if ( loc.toString().startsWith( "fr" ) ) {
+			assertEquals( prefix + "la longueur doit être entre 0 et 2", invalidValues[0].getMessage() );
+		}
+		else if ( loc.toString().startsWith( "da" ) ) {
+			assertEquals( prefix + "længden skal være mellem 0 og 2", invalidValues[0].getMessage() );
+		}
+		else {
+			// if default not found then it must be english
+			assertEquals( prefix + "length must be between 0 and 2", invalidValues[0].getMessage() );
+		}
+	}
+
 	public void testBigInteger() throws Exception {
 		Tv tv = new Tv();
 		tv.lifetime = new BigInteger("9223372036854775808");




More information about the hibernate-commits mailing list