[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