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 6dea3c8f..41e7079a 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 @@ -28,6 +28,7 @@ import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; import org.springframework.validation.Errors; +import org.springframework.validation.Validator; import org.springframework.webflow.execution.RequestContext; /** @@ -82,82 +83,117 @@ public class ValidationHelper { * Invoke the validators available by convention. */ public void validate() { - String validateMethodName = "validate" + StringUtils.capitalize(requestContext.getCurrentState().getId()); - validateWithContext(model, validateMethodName); - BeanFactory beanFactory = requestContext.getActiveFlow().getApplicationContext(); - if (beanFactory != null && StringUtils.hasText(modelName)) { - String validatorName = modelName + "Validator"; - if (beanFactory.containsBean(validatorName)) { - Object validator = beanFactory.getBean(validatorName); - if (!validateWithModelAndContext(model, validator, validateMethodName)) { - validateWithModelAndErrors(model, validator, validateMethodName); - } - } + invokeModelValidationMethod(model); + Object validator = getModelValidator(); + if (validator != null) { + invokeModelValidator(model, validator); } } - /* - * Invoke validate method on the model for the current state passing either a MessageContext or ValidationContext. - * Preference is given to the ValidationContext method. - */ - private boolean validateWithContext(Object model, String validateMethodName) { - boolean validationInvoked = false; - Method validateMethod = ReflectionUtils.findMethod(model.getClass(), validateMethodName, + private boolean invokeModelValidationMethod(Object model) { + boolean methodInvoked = invokeValidateMethodForCurrentState(model); + if (!methodInvoked) { + methodInvoked = invokeDefaultValidateMethod(model); + } + return methodInvoked; + } + + private boolean invokeValidateMethodForCurrentState(Object model) { + String methodName = "validate" + StringUtils.capitalize(requestContext.getCurrentState().getId()); + Method validateMethod = ReflectionUtils.findMethod(model.getClass(), methodName, new Class[] { ValidationContext.class }); if (validateMethod != null) { ReflectionUtils.invokeMethod(validateMethod, model, new Object[] { new DefaultValidationContext( requestContext, eventId, mappingResults) }); - validationInvoked = true; + return true; } else { - validateMethod = ReflectionUtils.findMethod(model.getClass(), validateMethodName, + validateMethod = ReflectionUtils.findMethod(model.getClass(), methodName, new Class[] { MessageContext.class }); if (validateMethod != null) { ReflectionUtils .invokeMethod(validateMethod, model, new Object[] { requestContext.getMessageContext() }); - validationInvoked = true; + return true; } } - return validationInvoked; + return false; } - /* - * Invoke validate method on a distinct validator providing the model to validate and either a MessageContext or - * ValidationContext. Preference is given to the ValidationContext method. - */ - private boolean validateWithModelAndContext(Object model, Object validator, String validateMethodName) { - boolean validationInvoked = false; - Method validateMethod = ReflectionUtils.findMethod(validator.getClass(), validateMethodName, new Class[] { - model.getClass(), ValidationContext.class }); + private boolean invokeDefaultValidateMethod(Object model) { + Method validateMethod = ReflectionUtils.findMethod(model.getClass(), "validate", + new Class[] { ValidationContext.class }); + if (validateMethod != null) { + ReflectionUtils.invokeMethod(validateMethod, model, new Object[] { new DefaultValidationContext( + requestContext, eventId, mappingResults) }); + return true; + } else { + return false; + } + } + + private Object getModelValidator() { + BeanFactory beanFactory = requestContext.getActiveFlow().getApplicationContext(); + if (beanFactory != null && StringUtils.hasText(modelName)) { + String validatorName = modelName + "Validator"; + if (beanFactory.containsBean(validatorName)) { + return beanFactory.getBean(validatorName); + } + } + return null; + } + + private boolean invokeModelValidator(Object model, Object validator) { + if (validator instanceof Validator) { + // existing validator, just invoke it + MessageContextErrors errors = new MessageContextErrors(requestContext.getMessageContext(), modelName, + model, expressionParser, mappingResults); + ((Validator) validator).validate(model, errors); + return true; + } + // try reflection + boolean methodInvoked = invokeValidatorValidateMethodForCurrentState(model, validator); + if (!methodInvoked) { + methodInvoked = invokeValidatorDefaultValidateMethod(model, validator); + } + return methodInvoked; + } + + private boolean invokeValidatorValidateMethodForCurrentState(Object model, Object validator) { + String methodName = "validate" + StringUtils.capitalize(requestContext.getCurrentState().getId()); + Method validateMethod = ReflectionUtils.findMethod(validator.getClass(), methodName, + new Class[] { ValidationContext.class }); if (validateMethod != null) { ReflectionUtils.invokeMethod(validateMethod, validator, new Object[] { model, new DefaultValidationContext(requestContext, eventId, mappingResults) }); - validationInvoked = true; - } else { - validateMethod = ReflectionUtils.findMethod(validator.getClass(), validateMethodName, new Class[] { - model.getClass(), MessageContext.class }); - if (validateMethod != null) { - ReflectionUtils.invokeMethod(validateMethod, validator, new Object[] { model, - requestContext.getMessageContext() }); - validationInvoked = true; - } + return true; } - return validationInvoked; - } - - /* - * Invoke validate method on a distinct validator providing the model to validate and an Errors object. - */ - private boolean validateWithModelAndErrors(Object model, Object validator, String validateMethodName) { - boolean validationInvoked = false; - Method validateMethod = ReflectionUtils.findMethod(validator.getClass(), validateMethodName, new Class[] { - model.getClass(), Errors.class }); + // web flow 2.0.3 or < compatibility only + validateMethod = ReflectionUtils.findMethod(validator.getClass(), methodName, new Class[] { model.getClass(), + Errors.class }); if (validateMethod != null) { MessageContextErrors errors = new MessageContextErrors(requestContext.getMessageContext(), modelName, model, expressionParser, mappingResults); ReflectionUtils.invokeMethod(validateMethod, validator, new Object[] { model, errors }); - validationInvoked = true; + return true; } - return validationInvoked; + validateMethod = ReflectionUtils.findMethod(validator.getClass(), methodName, + new Class[] { MessageContext.class }); + if (validateMethod != null) { + ReflectionUtils.invokeMethod(validateMethod, model, new Object[] { requestContext.getMessageContext() }); + return true; + } + return false; } -} + private boolean invokeValidatorDefaultValidateMethod(Object model, Object validator) { + Method validateMethod = ReflectionUtils.findMethod(validator.getClass(), "validate", new Class[] { + model.getClass(), ValidationContext.class }); + if (validateMethod != null) { + ReflectionUtils.invokeMethod(validateMethod, model, new Object[] { new DefaultValidationContext( + requestContext, eventId, mappingResults) }); + return true; + } else { + return false; + } + } + +} \ No newline at end of file diff --git a/spring-webflow/src/test/java/org/springframework/webflow/mvc/view/MvcViewTests.java b/spring-webflow/src/test/java/org/springframework/webflow/mvc/view/MvcViewTests.java index d2a57b6f..c60bab5e 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/mvc/view/MvcViewTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/mvc/view/MvcViewTests.java @@ -14,6 +14,7 @@ import junit.framework.TestCase; import org.springframework.binding.convert.service.DefaultConversionService; import org.springframework.binding.expression.support.StaticExpression; +import org.springframework.binding.validation.ValidationContext; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockMultipartFile; @@ -201,6 +202,7 @@ public class MvcViewTests extends TestCase { assertEquals("1", bindBean.getListProperty().get(0)); assertEquals("2", bindBean.getListProperty().get(1)); assertEquals("3", bindBean.getListProperty().get(2)); + assertFalse(bindBean.validationMethodInvoked); } public void testResumeEventBindingErrors() throws Exception { @@ -307,6 +309,54 @@ public class MvcViewTests extends TestCase { assertEquals(true, bindBean.getBooleanProperty()); } + public void testResumeEventModelBindAndValidate() throws Exception { + MockRequestContext context = new MockRequestContext(); + context.putRequestParameter("_eventId", "submit"); + context.putRequestParameter("stringProperty", "foo"); + context.putRequestParameter("integerProperty", "5"); + context.putRequestParameter("dateProperty", "2007-01-01"); + BindBean bindBean = new ValidatingBindBean(); + StaticExpression modelObject = new StaticExpression(bindBean); + modelObject.setExpressionString("bindBean"); + context.getCurrentState().getAttributes().put("model", modelObject); + context.getFlowScope().put("bindBean", bindBean); + context.getMockExternalContext().setNativeContext(new MockServletContext()); + context.getMockExternalContext().setNativeRequest(new MockHttpServletRequest()); + context.getMockExternalContext().setNativeResponse(new MockHttpServletResponse()); + context.getMockFlowExecutionContext().setKey(new MockFlowExecutionKey("c1v1")); + org.springframework.web.servlet.View mvcView = new MockView(); + AbstractMvcView view = new MockMvcView(mvcView, context); + view.setExpressionParser(DefaultExpressionParserFactory.getExpressionParser()); + view.processUserEvent(); + assertTrue(view.hasFlowEvent()); + assertEquals("submit", view.getFlowEvent().getId()); + assertTrue(bindBean.validationMethodInvoked); + } + + public void testResumeEventModelBindAndValidateDefaultValidatorFallback() throws Exception { + MockRequestContext context = new MockRequestContext(); + context.putRequestParameter("_eventId", "submit"); + context.putRequestParameter("stringProperty", "foo"); + context.putRequestParameter("integerProperty", "5"); + context.putRequestParameter("dateProperty", "2007-01-01"); + BindBean bindBean = new ValidatingBindBeanFallback(); + StaticExpression modelObject = new StaticExpression(bindBean); + modelObject.setExpressionString("bindBean"); + context.getCurrentState().getAttributes().put("model", modelObject); + context.getFlowScope().put("bindBean", bindBean); + context.getMockExternalContext().setNativeContext(new MockServletContext()); + context.getMockExternalContext().setNativeRequest(new MockHttpServletRequest()); + context.getMockExternalContext().setNativeResponse(new MockHttpServletResponse()); + context.getMockFlowExecutionContext().setKey(new MockFlowExecutionKey("c1v1")); + org.springframework.web.servlet.View mvcView = new MockView(); + AbstractMvcView view = new MockMvcView(mvcView, context); + view.setExpressionParser(DefaultExpressionParserFactory.getExpressionParser()); + view.processUserEvent(); + assertTrue(view.hasFlowEvent()); + assertEquals("submit", view.getFlowEvent().getId()); + assertTrue(bindBean.validationMethodInvoked); + } + private class MockMvcView extends AbstractMvcView { public MockMvcView(View view, RequestContext context) { @@ -346,6 +396,7 @@ public class MvcViewTests extends TestCase { private int[] primitiveArrayProperty; private List listProperty; private Map mapProperty; + private boolean validationMethodInvoked; public BindBean() { Calendar cal = Calendar.getInstance(); @@ -445,6 +496,23 @@ public class MvcViewTests extends TestCase { } + public static class ValidatingBindBean extends BindBean { + + public void validateMockState(ValidationContext context) { + super.validationMethodInvoked = true; + } + } + + public static class ValidatingBindBeanFallback extends BindBean { + + public void validate(ValidationContext context) { + assertEquals("submit", context.getUserEvent()); + assertNull(context.getUserPrincipal()); + assertEquals("foo", context.getUserValue("stringProperty")); + super.validationMethodInvoked = true; + } + } + public static class NestedBean { private String name;