diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowActionListener.java b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowActionListener.java index 58e74670..dd051472 100644 --- a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowActionListener.java +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowActionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2004-2012 the original author or authors. + * Copyright 2004-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,18 +34,20 @@ import org.springframework.webflow.definition.TransitionDefinition; import org.springframework.webflow.execution.RequestContext; import org.springframework.webflow.execution.RequestContextHolder; import org.springframework.webflow.execution.View; +import org.springframework.webflow.validation.BeanValidationHintResolver; import org.springframework.webflow.validation.ValidationHelper; +import org.springframework.webflow.validation.ValidationHintResolver; import org.springframework.webflow.validation.WebFlowMessageCodesResolver; /** * The default {@link ActionListener} implementation to be used with Web Flow. - * + * * This implementation bypasses the JSF {@link NavigationHandler} mechanism to instead let the event be handled directly * by Web Flow. *

* Web Flow's model-level validation will be invoked here after an event has been detected if the event is not an * immediate event. - * + * * @author Jeremy Grelle */ public class FlowActionListener implements ActionListener { @@ -58,10 +60,21 @@ public class FlowActionListener implements ActionListener { private final MessageCodesResolver messageCodesResolver = new WebFlowMessageCodesResolver(); + private ValidationHintResolver validationHintResolver = new BeanValidationHintResolver(); + + public FlowActionListener(ActionListener delegate) { this.delegate = delegate; } + public void setValidationHintResolver(ValidationHintResolver validationHintResolver) { + this.validationHintResolver = validationHintResolver; + } + + public ValidationHintResolver getValidationHintResolver() { + return validationHintResolver; + } + public void processAction(ActionEvent actionEvent) throws AbortProcessingException { if (!JsfUtils.isFlowRequest()) { this.delegate.processAction(actionEvent); @@ -151,7 +164,11 @@ public class FlowActionListener implements ActionListener { } private void validate(RequestContext requestContext, Object model, String eventId) { - new ValidationHelper(model, requestContext, eventId, getModelExpression(requestContext).getExpressionString(), - null, this.messageCodesResolver, null).validate(); + + String modelName = getModelExpression(requestContext).getExpressionString(); + + new ValidationHelper(model, requestContext, + eventId, modelName, null, this.messageCodesResolver, null, this.validationHintResolver).validate(); } + } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/AbstractMvcView.java b/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/AbstractMvcView.java index 7ab2b01d..b3b40807 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/AbstractMvcView.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/AbstractMvcView.java @@ -42,7 +42,6 @@ import org.springframework.binding.message.MessageBuilder; import org.springframework.binding.message.MessageResolver; import org.springframework.core.style.ToStringCreator; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; import org.springframework.validation.BindingResult; import org.springframework.validation.MessageCodesResolver; import org.springframework.validation.Validator; @@ -53,7 +52,6 @@ import org.springframework.webflow.definition.TransitionDefinition; import org.springframework.webflow.engine.builder.BinderConfiguration; import org.springframework.webflow.engine.builder.BinderConfiguration.Binding; import org.springframework.webflow.execution.Event; -import org.springframework.webflow.execution.FlowExecutionException; import org.springframework.webflow.execution.FlowExecutionKey; import org.springframework.webflow.execution.RequestContext; import org.springframework.webflow.execution.View; @@ -593,39 +591,6 @@ public abstract class AbstractMvcView implements View { return (Expression) requestContext.getCurrentState().getAttributes().get("model"); } - private Object[] getValidationHints(Object model, TransitionDefinition transition) { - Expression expr = null; - if (transition != null) { - expr = (Expression) transition.getAttributes().get("validationHints"); - } - if (expr == null) { - expr = (Expression) requestContext.getCurrentState().getAttributes().get("validationHints"); - } - if (expr == null) { - return null; - } - String flowId = requestContext.getActiveFlow().getId(); - String stateId = requestContext.getCurrentState().getId(); - try { - Object hintsValue = expr.getValue(requestContext); - if (hintsValue instanceof String) { - String[] hints = StringUtils.commaDelimitedListToStringArray((String) hintsValue); - return validationHintResolver.resolveValidationHints(model, flowId, stateId, hints); - } - else if (hintsValue instanceof Object[]) { - return (Object[]) hintsValue; - } - else { - throw new FlowExecutionException(flowId, stateId, - "Failed to resolve validation hints [" + hintsValue + "]"); - } - } - catch (EvaluationException e) { - throw new FlowExecutionException(flowId, stateId, - "Failed to resolve validation hints expression [" + expr + "]", e); - } - } - private Object getEmptyValue(Class fieldType) { if (fieldType != null && boolean.class.equals(fieldType) || Boolean.class.equals(fieldType)) { // Special handling of boolean property. @@ -676,9 +641,8 @@ public abstract class AbstractMvcView implements View { logger.debug("Validating model"); } ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, getModelExpression() - .getExpressionString(), expressionParser, messageCodesResolver, mappingResults); + .getExpressionString(), expressionParser, messageCodesResolver, mappingResults, validationHintResolver); helper.setValidator(this.validator); - helper.setValidationHints(getValidationHints(model, transition)); helper.validate(); } 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 2268f964..019b733f 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 @@ -24,6 +24,8 @@ 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.EvaluationException; +import org.springframework.binding.expression.Expression; import org.springframework.binding.expression.ExpressionParser; import org.springframework.binding.mapping.MappingResults; import org.springframework.binding.message.MessageContext; @@ -37,6 +39,8 @@ import org.springframework.validation.Errors; import org.springframework.validation.MessageCodesResolver; import org.springframework.validation.SmartValidator; import org.springframework.validation.Validator; +import org.springframework.webflow.definition.TransitionDefinition; +import org.springframework.webflow.execution.FlowExecutionException; import org.springframework.webflow.execution.RequestContext; /** @@ -86,8 +90,10 @@ public class ValidationHelper { * @param expressionParser the expression parser * @param mappingResults object mapping results */ - public ValidationHelper(Object model, RequestContext requestContext, String eventId, String modelName, - ExpressionParser expressionParser, MessageCodesResolver messageCodesResolver, MappingResults mappingResults) { + public ValidationHelper(Object model, RequestContext requestContext, String eventId, + String modelName, ExpressionParser expressionParser, MessageCodesResolver messageCodesResolver, + MappingResults mappingResults, ValidationHintResolver hintResolver) { + Assert.notNull(model, "The model to validate is required"); Assert.notNull(requestContext, "The request context for the validator is required"); this.model = model; @@ -97,6 +103,55 @@ public class ValidationHelper { this.expressionParser = expressionParser; this.messageCodesResolver = messageCodesResolver; this.mappingResults = mappingResults; + this.validationHints = initValidationHints(model, requestContext, eventId, hintResolver); + } + + /** + * An alternative constructor that creates an instance of {@link BeanValidationHintResolver}. + */ + public ValidationHelper(Object model, RequestContext requestContext, String eventId, + String modelName, ExpressionParser expressionParser, MessageCodesResolver messageCodesResolver, + MappingResults mappingResults) { + + this(model, requestContext, eventId, modelName, expressionParser, + messageCodesResolver, mappingResults, new BeanValidationHintResolver()); + } + + + private static Object[] initValidationHints(Object model, RequestContext requestContext, String eventId, + ValidationHintResolver hintResolver) { + + Expression expr = null; + TransitionDefinition transition = requestContext.getMatchingTransition(eventId); + if (transition != null) { + expr = (Expression) transition.getAttributes().get("validationHints"); + } + if (expr == null) { + expr = (Expression) requestContext.getCurrentState().getAttributes().get("validationHints"); + } + if (expr == null) { + return null; + } + String flowId = requestContext.getActiveFlow().getId(); + String stateId = requestContext.getCurrentState().getId(); + try { + Object hintsValue = expr.getValue(requestContext); + if (hintsValue instanceof String) { + String[] hints = StringUtils.commaDelimitedListToStringArray((String) hintsValue); + return hintResolver.resolveValidationHints(model, flowId, stateId, hints); + } + else if (hintsValue instanceof Object[]) { + return (Object[]) hintsValue; + } + else { + throw new FlowExecutionException(flowId, stateId, + "Failed to resolve validation hints [" + hintsValue + "]"); + } + } + catch (EvaluationException e) { + throw new FlowExecutionException(flowId, stateId, + "Failed to resolve validation hints expression [" + expr + "]", e); + } } /** @@ -107,7 +162,9 @@ public class ValidationHelper { } /** - * Provide validation hints such as validation groups to use against a JSR-303 provider. + * Provide validation hints (e.g. JSR-303 validation groups). Note that the constructor + * automatically detects validation hints specified on a view state or a transition. + * Therefore use of this method should not be needed. */ public void setValidationHints(Object[] validationHints) { this.validationHints = validationHints; 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 de88e673..86cac09b 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 @@ -17,6 +17,7 @@ package org.springframework.webflow.validation; import junit.framework.TestCase; +import org.springframework.binding.expression.support.StaticExpression; import org.springframework.binding.message.MessageContext; import org.springframework.binding.validation.ValidationContext; import org.springframework.context.support.StaticApplicationContext; @@ -25,7 +26,9 @@ import org.springframework.validation.Errors; import org.springframework.validation.SmartValidator; import org.springframework.webflow.engine.Flow; import org.springframework.webflow.engine.StubViewFactory; +import org.springframework.webflow.engine.Transition; import org.springframework.webflow.engine.ViewState; +import org.springframework.webflow.engine.support.DefaultTransitionCriteria; import org.springframework.webflow.test.MockRequestControlContext; /** @@ -337,16 +340,54 @@ public class ValidationHelperTests extends TestCase { assertTrue(validator.fallbackInvoked); } - public void testSmartValidatorWithStateIdHint() { + public void testSmartValidatorWithClassHint() { + ViewState state = new ViewState(requestContext.getRootFlow(), "state2", new StubViewFactory()); + state.getAttributes().put("validationHints", new StaticExpression(new Object[] { Model.State1.class })); + requestContext.setCurrentState(state); + LegacyModelValidator validator = new LegacyModelValidator(); ExtendedModel model = new ExtendedModel(); ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null, codesResolver, null); helper.setValidator(validator); - helper.setValidationHints(new Object[] { Model.State1.class }); + + helper.validate(); + + assertTrue(validator.fallbackInvoked); + assertTrue(validator.hints.length > 0); + assertEquals(Model.State1.class, validator.hints[0]); + } + + public void testSmartValidatorWithHintResolution() { + ViewState state = new ViewState(requestContext.getRootFlow(), "state2", new StubViewFactory()); + state.getAttributes().put("validationHints", new StaticExpression("State1")); + requestContext.setCurrentState(state); + + LegacyModelValidator validator = new LegacyModelValidator(); + ExtendedModel model = new ExtendedModel(); + ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null, codesResolver, null); + helper.setValidator(validator); + + helper.validate(); + + assertTrue(validator.fallbackInvoked); + assertTrue(validator.hints.length > 0); + assertEquals(Model.State1.class, validator.hints[0]); + } + + public void testSmartValidatorWithHintOnTransition() { + Transition transition = new Transition(); + transition.setMatchingCriteria(new DefaultTransitionCriteria(new StaticExpression(eventId))); + transition.getAttributes().put("validationHints", new StaticExpression("State1")); ViewState state = new ViewState(requestContext.getRootFlow(), "state2", new StubViewFactory()); + state.getTransitionSet().add(transition); requestContext.setCurrentState(state); + LegacyModelValidator validator = new LegacyModelValidator(); + ExtendedModel model = new ExtendedModel(); + ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null, codesResolver, null); + helper.setValidator(validator); + helper.validate(); assertTrue(validator.fallbackInvoked); @@ -367,7 +408,7 @@ public class ValidationHelperTests extends TestCase { fallbackInvoked = true; } - private static class State1 {} + private static interface State1 {} } public static class ExtendedModel extends Model {