From 29a9130377784432e67fd35c4fbac6e82e69137c Mon Sep 17 00:00:00 2001 From: Jeremy Grelle Date: Wed, 5 Aug 2009 21:45:57 +0000 Subject: [PATCH] SWF-1146 - Validator only runs if signature matches exact model object Class --- .../webflow/validation/ValidationHelper.java | 45 +++++--- .../validation/StubModelMessageContext.java | 2 +- .../validation/ValidationHelperTests.java | 105 ++++++++++++++++-- 3 files changed, 127 insertions(+), 25 deletions(-) diff --git a/spring-webflow/src/main/java/org/springframework/webflow/validation/ValidationHelper.java b/spring-webflow/src/main/java/org/springframework/webflow/validation/ValidationHelper.java index f62d2f55..3d45f384 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/validation/ValidationHelper.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/validation/ValidationHelper.java @@ -17,9 +17,13 @@ package org.springframework.webflow.validation; import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.aop.support.AopUtils; import org.springframework.beans.factory.BeanFactory; import org.springframework.binding.expression.ExpressionParser; import org.springframework.binding.mapping.MappingResults; @@ -39,6 +43,8 @@ import org.springframework.webflow.execution.RequestContext; * A helper class the encapsulates conventions to invoke validation logic. * * @author Scott Andrews + * @author Canny Duck + * @author Jeremy Grelle */ public class ValidationHelper { @@ -76,8 +82,7 @@ public class ValidationHelper { * @param mappingResults object mapping results */ public ValidationHelper(Object model, RequestContext requestContext, String eventId, String modelName, - ExpressionParser expressionParser, MessageCodesResolver messageCodesResolver, - MappingResults mappingResults) { + ExpressionParser expressionParser, MessageCodesResolver messageCodesResolver, MappingResults mappingResults) { Assert.notNull(model, "The model to validate is required"); Assert.notNull(requestContext, "The request context for the validator is required"); this.model = model; @@ -183,8 +188,7 @@ public class ValidationHelper { private boolean invokeValidatorValidateMethodForCurrentState(Object model, Object validator) { String methodName = "validate" + StringUtils.capitalize(requestContext.getCurrentState().getId()); // preferred - Method validateMethod = ReflectionUtils.findMethod(validator.getClass(), methodName, new Class[] { - model.getClass(), ValidationContext.class }); + Method validateMethod = findValidationMethod(model, validator, methodName, ValidationContext.class); if (validateMethod != null) { if (logger.isDebugEnabled()) { logger.debug("Invoking current state validator method '" @@ -196,8 +200,7 @@ public class ValidationHelper { return true; } // mvc 2 compatibility only - validateMethod = ReflectionUtils.findMethod(validator.getClass(), methodName, new Class[] { model.getClass(), - Errors.class }); + validateMethod = findValidationMethod(model, validator, methodName, Errors.class); if (validateMethod != null) { if (logger.isDebugEnabled()) { logger.debug("Invoking current state validator method '" @@ -210,8 +213,7 @@ public class ValidationHelper { return true; } // web flow 2.0.0 to 2.0.3 compatibility only [to remove in web flow 3] - validateMethod = ReflectionUtils.findMethod(validator.getClass(), methodName, new Class[] { model.getClass(), - MessageContext.class }); + validateMethod = findValidationMethod(model, validator, methodName, MessageContext.class); if (validateMethod != null) { ReflectionUtils.invokeMethod(validateMethod, validator, new Object[] { model, requestContext.getMessageContext() }); @@ -232,8 +234,7 @@ public class ValidationHelper { return true; } // preferred - Method validateMethod = ReflectionUtils.findMethod(validator.getClass(), "validate", new Class[] { - model.getClass(), ValidationContext.class }); + Method validateMethod = findValidationMethod(model, validator, "validate", ValidationContext.class); if (validateMethod != null) { if (logger.isDebugEnabled()) { logger.debug("Invoking default validator method '" + ClassUtils.getShortName(validator.getClass()) @@ -244,8 +245,7 @@ public class ValidationHelper { return true; } // mvc 2 compatibility only - validateMethod = ReflectionUtils.findMethod(validator.getClass(), "validate", new Class[] { model.getClass(), - Errors.class }); + validateMethod = findValidationMethod(model, validator, "validate", Errors.class); if (validateMethod != null) { if (logger.isDebugEnabled()) { logger.debug("Invoking default validator method '" + ClassUtils.getShortName(validator.getClass()) @@ -258,4 +258,23 @@ public class ValidationHelper { } return false; } -} \ No newline at end of file + + private Method findValidationMethod(Object model, Object validator, String methodName, Class context) { + Class modelClass = AopUtils.getTargetClass(model); + + List modelSearchClasses = new ArrayList(); + while (modelClass != null) { + modelSearchClasses.add(modelClass); + modelClass = modelClass.getSuperclass(); + } + for (Iterator iterator = modelSearchClasses.iterator(); iterator.hasNext();) { + Class searchClass = (Class) iterator.next(); + Method method = ReflectionUtils.findMethod(validator.getClass(), methodName, new Class[] { searchClass, + context }); + if (method != null) { + return method; + } + } + return null; + } +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/validation/StubModelMessageContext.java b/spring-webflow/src/test/java/org/springframework/webflow/validation/StubModelMessageContext.java index 299fc750..65ebfd6c 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/validation/StubModelMessageContext.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/validation/StubModelMessageContext.java @@ -19,7 +19,7 @@ import org.springframework.binding.message.MessageBuilder; import org.springframework.binding.message.MessageContext; /** - * Support class for {@link ValidationHelperTest} + * Support class for {@link ValidationHelperTests} */ public class StubModelMessageContext { diff --git a/spring-webflow/src/test/java/org/springframework/webflow/validation/ValidationHelperTests.java b/spring-webflow/src/test/java/org/springframework/webflow/validation/ValidationHelperTests.java index 1cfad67b..4de512ac 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/validation/ValidationHelperTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/validation/ValidationHelperTests.java @@ -163,7 +163,23 @@ public class ValidationHelperTests extends TestCase { applicationContext.getBeanFactory().registerSingleton("modelValidator", validator); requestContext.getRootFlow().setApplicationContext(applicationContext); - Object model = new Object(); + Model model = new Model(); + ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null, + new DefaultMessageCodesResolver(), null); + ViewState state1 = new ViewState(requestContext.getRootFlow(), "state1", new StubViewFactory()); + requestContext.setCurrentState(state1); + helper.validate(); + assertTrue(validator.state1Invoked); + assertTrue(validator.fallbackInvoked); + } + + public void testStateAndFallbackValidatorInvokedForSubclass() { + ModelValidator validator = new ModelValidator(); + StaticApplicationContext applicationContext = new StaticApplicationContext(); + applicationContext.getBeanFactory().registerSingleton("modelValidator", validator); + requestContext.getRootFlow().setApplicationContext(applicationContext); + + ExtendedModel model = new ExtendedModel(); ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null, new DefaultMessageCodesResolver(), null); ViewState state1 = new ViewState(requestContext.getRootFlow(), "state1", new StubViewFactory()); @@ -179,7 +195,23 @@ public class ValidationHelperTests extends TestCase { applicationContext.getBeanFactory().registerSingleton("modelValidator", validator); requestContext.getRootFlow().setApplicationContext(applicationContext); - Object model = new Object(); + Model model = new Model(); + ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null, + new DefaultMessageCodesResolver(), null); + ViewState state1 = new ViewState(requestContext.getRootFlow(), "state2", new StubViewFactory()); + requestContext.setCurrentState(state1); + helper.validate(); + assertFalse(validator.state1Invoked); + assertTrue(validator.fallbackInvoked); + } + + public void testFallbackValidatorInvokedForSubclass() { + ModelValidator validator = new ModelValidator(); + StaticApplicationContext applicationContext = new StaticApplicationContext(); + applicationContext.getBeanFactory().registerSingleton("modelValidator", validator); + requestContext.getRootFlow().setApplicationContext(applicationContext); + + ExtendedModel model = new ExtendedModel(); ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null, new DefaultMessageCodesResolver(), null); ViewState state1 = new ViewState(requestContext.getRootFlow(), "state2", new StubViewFactory()); @@ -195,7 +227,23 @@ public class ValidationHelperTests extends TestCase { applicationContext.getBeanFactory().registerSingleton("modelValidator", validator); requestContext.getRootFlow().setApplicationContext(applicationContext); - Object model = new Object(); + Model model = new Model(); + ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null, + new DefaultMessageCodesResolver(), null); + ViewState state1 = new ViewState(requestContext.getRootFlow(), "state1", new StubViewFactory()); + requestContext.setCurrentState(state1); + helper.validate(); + assertTrue(validator.state1Invoked); + assertTrue(validator.fallbackInvoked); + } + + public void testStateAndFallbackLegacyValidatorInvokedForSubclass() { + LegacyModelValidator validator = new LegacyModelValidator(); + StaticApplicationContext applicationContext = new StaticApplicationContext(); + applicationContext.getBeanFactory().registerSingleton("modelValidator", validator); + requestContext.getRootFlow().setApplicationContext(applicationContext); + + ExtendedModel model = new ExtendedModel(); ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null, new DefaultMessageCodesResolver(), null); ViewState state1 = new ViewState(requestContext.getRootFlow(), "state1", new StubViewFactory()); @@ -211,7 +259,7 @@ public class ValidationHelperTests extends TestCase { applicationContext.getBeanFactory().registerSingleton("modelValidator", validator); requestContext.getRootFlow().setApplicationContext(applicationContext); - Object model = new Object(); + Model model = new Model(); ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null, new DefaultMessageCodesResolver(), null); ViewState state1 = new ViewState(requestContext.getRootFlow(), "state2", new StubViewFactory()); @@ -227,7 +275,23 @@ public class ValidationHelperTests extends TestCase { applicationContext.getBeanFactory().registerSingleton("modelValidator", validator); requestContext.getRootFlow().setApplicationContext(applicationContext); - Object model = new Object(); + Model model = new Model(); + ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null, + new DefaultMessageCodesResolver(), null); + ViewState state1 = new ViewState(requestContext.getRootFlow(), "state1", new StubViewFactory()); + requestContext.setCurrentState(state1); + helper.validate(); + assertTrue(validator.state1Invoked); + assertTrue(validator.fallbackInvoked); + } + + public void testStateAndFallbackErrorsValidatorInvokedForSubclass() { + ErrorsModelValidator validator = new ErrorsModelValidator(); + StaticApplicationContext applicationContext = new StaticApplicationContext(); + applicationContext.getBeanFactory().registerSingleton("modelValidator", validator); + requestContext.getRootFlow().setApplicationContext(applicationContext); + + ExtendedModel model = new ExtendedModel(); ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null, new DefaultMessageCodesResolver(), null); ViewState state1 = new ViewState(requestContext.getRootFlow(), "state1", new StubViewFactory()); @@ -243,7 +307,23 @@ public class ValidationHelperTests extends TestCase { applicationContext.getBeanFactory().registerSingleton("modelValidator", validator); requestContext.getRootFlow().setApplicationContext(applicationContext); - Object model = new Object(); + Model model = new Model(); + ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null, + new DefaultMessageCodesResolver(), null); + ViewState state1 = new ViewState(requestContext.getRootFlow(), "state2", new StubViewFactory()); + requestContext.setCurrentState(state1); + helper.validate(); + assertFalse(validator.state1Invoked); + assertTrue(validator.fallbackInvoked); + } + + public void testFallbackErrorsValidatorInvokedForSubclass() { + ErrorsModelValidator validator = new ErrorsModelValidator(); + StaticApplicationContext applicationContext = new StaticApplicationContext(); + applicationContext.getBeanFactory().registerSingleton("modelValidator", validator); + requestContext.getRootFlow().setApplicationContext(applicationContext); + + Model model = new Model(); ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null, new DefaultMessageCodesResolver(), null); ViewState state1 = new ViewState(requestContext.getRootFlow(), "state2", new StubViewFactory()); @@ -266,6 +346,9 @@ public class ValidationHelperTests extends TestCase { } } + public static class ExtendedModel extends Model { + } + public static class ErrorsModel { private boolean state1Invoked; private boolean fallbackInvoked; @@ -283,7 +366,7 @@ public class ValidationHelperTests extends TestCase { private boolean state1Invoked; private boolean fallbackInvoked; - public void validateState1(Object object, Errors errors) { + public void validateState1(Model model, Errors errors) { state1Invoked = true; } @@ -300,11 +383,11 @@ public class ValidationHelperTests extends TestCase { private boolean state1Invoked; private boolean fallbackInvoked; - public void validateState1(Object object, ValidationContext context) { + public void validateState1(Model model, ValidationContext context) { state1Invoked = true; } - public void validate(Object object, ValidationContext context) { + public void validate(Model model, ValidationContext context) { fallbackInvoked = true; } } @@ -313,11 +396,11 @@ public class ValidationHelperTests extends TestCase { private boolean state1Invoked; private boolean fallbackInvoked; - public void validateState1(Object object, Errors context) { + public void validateState1(Model model, Errors context) { state1Invoked = true; } - public void validate(Object object, Errors context) { + public void validate(Model model, Errors context) { fallbackInvoked = true; } }