Support validation hints in FlowActionListener
Issue: SWF-1626
This commit is contained in:
@@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.RequestContext;
|
||||||
import org.springframework.webflow.execution.RequestContextHolder;
|
import org.springframework.webflow.execution.RequestContextHolder;
|
||||||
import org.springframework.webflow.execution.View;
|
import org.springframework.webflow.execution.View;
|
||||||
|
import org.springframework.webflow.validation.BeanValidationHintResolver;
|
||||||
import org.springframework.webflow.validation.ValidationHelper;
|
import org.springframework.webflow.validation.ValidationHelper;
|
||||||
|
import org.springframework.webflow.validation.ValidationHintResolver;
|
||||||
import org.springframework.webflow.validation.WebFlowMessageCodesResolver;
|
import org.springframework.webflow.validation.WebFlowMessageCodesResolver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default {@link ActionListener} implementation to be used with Web Flow.
|
* 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
|
* This implementation bypasses the JSF {@link NavigationHandler} mechanism to instead let the event be handled directly
|
||||||
* by Web Flow.
|
* by Web Flow.
|
||||||
* <p>
|
* <p>
|
||||||
* Web Flow's model-level validation will be invoked here after an event has been detected if the event is not an
|
* Web Flow's model-level validation will be invoked here after an event has been detected if the event is not an
|
||||||
* immediate event.
|
* immediate event.
|
||||||
*
|
*
|
||||||
* @author Jeremy Grelle
|
* @author Jeremy Grelle
|
||||||
*/
|
*/
|
||||||
public class FlowActionListener implements ActionListener {
|
public class FlowActionListener implements ActionListener {
|
||||||
@@ -58,10 +60,21 @@ public class FlowActionListener implements ActionListener {
|
|||||||
|
|
||||||
private final MessageCodesResolver messageCodesResolver = new WebFlowMessageCodesResolver();
|
private final MessageCodesResolver messageCodesResolver = new WebFlowMessageCodesResolver();
|
||||||
|
|
||||||
|
private ValidationHintResolver validationHintResolver = new BeanValidationHintResolver();
|
||||||
|
|
||||||
|
|
||||||
public FlowActionListener(ActionListener delegate) {
|
public FlowActionListener(ActionListener delegate) {
|
||||||
this.delegate = delegate;
|
this.delegate = delegate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setValidationHintResolver(ValidationHintResolver validationHintResolver) {
|
||||||
|
this.validationHintResolver = validationHintResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidationHintResolver getValidationHintResolver() {
|
||||||
|
return validationHintResolver;
|
||||||
|
}
|
||||||
|
|
||||||
public void processAction(ActionEvent actionEvent) throws AbortProcessingException {
|
public void processAction(ActionEvent actionEvent) throws AbortProcessingException {
|
||||||
if (!JsfUtils.isFlowRequest()) {
|
if (!JsfUtils.isFlowRequest()) {
|
||||||
this.delegate.processAction(actionEvent);
|
this.delegate.processAction(actionEvent);
|
||||||
@@ -151,7 +164,11 @@ public class FlowActionListener implements ActionListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void validate(RequestContext requestContext, Object model, String eventId) {
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ import org.springframework.binding.message.MessageBuilder;
|
|||||||
import org.springframework.binding.message.MessageResolver;
|
import org.springframework.binding.message.MessageResolver;
|
||||||
import org.springframework.core.style.ToStringCreator;
|
import org.springframework.core.style.ToStringCreator;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
import org.springframework.validation.BindingResult;
|
import org.springframework.validation.BindingResult;
|
||||||
import org.springframework.validation.MessageCodesResolver;
|
import org.springframework.validation.MessageCodesResolver;
|
||||||
import org.springframework.validation.Validator;
|
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;
|
||||||
import org.springframework.webflow.engine.builder.BinderConfiguration.Binding;
|
import org.springframework.webflow.engine.builder.BinderConfiguration.Binding;
|
||||||
import org.springframework.webflow.execution.Event;
|
import org.springframework.webflow.execution.Event;
|
||||||
import org.springframework.webflow.execution.FlowExecutionException;
|
|
||||||
import org.springframework.webflow.execution.FlowExecutionKey;
|
import org.springframework.webflow.execution.FlowExecutionKey;
|
||||||
import org.springframework.webflow.execution.RequestContext;
|
import org.springframework.webflow.execution.RequestContext;
|
||||||
import org.springframework.webflow.execution.View;
|
import org.springframework.webflow.execution.View;
|
||||||
@@ -593,39 +591,6 @@ public abstract class AbstractMvcView implements View {
|
|||||||
return (Expression) requestContext.getCurrentState().getAttributes().get("model");
|
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) {
|
private Object getEmptyValue(Class<?> fieldType) {
|
||||||
if (fieldType != null && boolean.class.equals(fieldType) || Boolean.class.equals(fieldType)) {
|
if (fieldType != null && boolean.class.equals(fieldType) || Boolean.class.equals(fieldType)) {
|
||||||
// Special handling of boolean property.
|
// Special handling of boolean property.
|
||||||
@@ -676,9 +641,8 @@ public abstract class AbstractMvcView implements View {
|
|||||||
logger.debug("Validating model");
|
logger.debug("Validating model");
|
||||||
}
|
}
|
||||||
ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, getModelExpression()
|
ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, getModelExpression()
|
||||||
.getExpressionString(), expressionParser, messageCodesResolver, mappingResults);
|
.getExpressionString(), expressionParser, messageCodesResolver, mappingResults, validationHintResolver);
|
||||||
helper.setValidator(this.validator);
|
helper.setValidator(this.validator);
|
||||||
helper.setValidationHints(getValidationHints(model, transition));
|
|
||||||
helper.validate();
|
helper.validate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ import org.apache.commons.logging.Log;
|
|||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.springframework.aop.support.AopUtils;
|
import org.springframework.aop.support.AopUtils;
|
||||||
import org.springframework.beans.factory.BeanFactory;
|
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.expression.ExpressionParser;
|
||||||
import org.springframework.binding.mapping.MappingResults;
|
import org.springframework.binding.mapping.MappingResults;
|
||||||
import org.springframework.binding.message.MessageContext;
|
import org.springframework.binding.message.MessageContext;
|
||||||
@@ -37,6 +39,8 @@ import org.springframework.validation.Errors;
|
|||||||
import org.springframework.validation.MessageCodesResolver;
|
import org.springframework.validation.MessageCodesResolver;
|
||||||
import org.springframework.validation.SmartValidator;
|
import org.springframework.validation.SmartValidator;
|
||||||
import org.springframework.validation.Validator;
|
import org.springframework.validation.Validator;
|
||||||
|
import org.springframework.webflow.definition.TransitionDefinition;
|
||||||
|
import org.springframework.webflow.execution.FlowExecutionException;
|
||||||
import org.springframework.webflow.execution.RequestContext;
|
import org.springframework.webflow.execution.RequestContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -86,8 +90,10 @@ public class ValidationHelper {
|
|||||||
* @param expressionParser the expression parser
|
* @param expressionParser the expression parser
|
||||||
* @param mappingResults object mapping results
|
* @param mappingResults object mapping results
|
||||||
*/
|
*/
|
||||||
public ValidationHelper(Object model, RequestContext requestContext, String eventId, String modelName,
|
public ValidationHelper(Object model, RequestContext requestContext, String eventId,
|
||||||
ExpressionParser expressionParser, MessageCodesResolver messageCodesResolver, MappingResults mappingResults) {
|
String modelName, ExpressionParser expressionParser, MessageCodesResolver messageCodesResolver,
|
||||||
|
MappingResults mappingResults, ValidationHintResolver hintResolver) {
|
||||||
|
|
||||||
Assert.notNull(model, "The model to validate is required");
|
Assert.notNull(model, "The model to validate is required");
|
||||||
Assert.notNull(requestContext, "The request context for the validator is required");
|
Assert.notNull(requestContext, "The request context for the validator is required");
|
||||||
this.model = model;
|
this.model = model;
|
||||||
@@ -97,6 +103,55 @@ public class ValidationHelper {
|
|||||||
this.expressionParser = expressionParser;
|
this.expressionParser = expressionParser;
|
||||||
this.messageCodesResolver = messageCodesResolver;
|
this.messageCodesResolver = messageCodesResolver;
|
||||||
this.mappingResults = mappingResults;
|
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) {
|
public void setValidationHints(Object[] validationHints) {
|
||||||
this.validationHints = validationHints;
|
this.validationHints = validationHints;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ package org.springframework.webflow.validation;
|
|||||||
|
|
||||||
import junit.framework.TestCase;
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
import org.springframework.binding.expression.support.StaticExpression;
|
||||||
import org.springframework.binding.message.MessageContext;
|
import org.springframework.binding.message.MessageContext;
|
||||||
import org.springframework.binding.validation.ValidationContext;
|
import org.springframework.binding.validation.ValidationContext;
|
||||||
import org.springframework.context.support.StaticApplicationContext;
|
import org.springframework.context.support.StaticApplicationContext;
|
||||||
@@ -25,7 +26,9 @@ import org.springframework.validation.Errors;
|
|||||||
import org.springframework.validation.SmartValidator;
|
import org.springframework.validation.SmartValidator;
|
||||||
import org.springframework.webflow.engine.Flow;
|
import org.springframework.webflow.engine.Flow;
|
||||||
import org.springframework.webflow.engine.StubViewFactory;
|
import org.springframework.webflow.engine.StubViewFactory;
|
||||||
|
import org.springframework.webflow.engine.Transition;
|
||||||
import org.springframework.webflow.engine.ViewState;
|
import org.springframework.webflow.engine.ViewState;
|
||||||
|
import org.springframework.webflow.engine.support.DefaultTransitionCriteria;
|
||||||
import org.springframework.webflow.test.MockRequestControlContext;
|
import org.springframework.webflow.test.MockRequestControlContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -337,16 +340,54 @@ public class ValidationHelperTests extends TestCase {
|
|||||||
assertTrue(validator.fallbackInvoked);
|
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();
|
LegacyModelValidator validator = new LegacyModelValidator();
|
||||||
ExtendedModel model = new ExtendedModel();
|
ExtendedModel model = new ExtendedModel();
|
||||||
ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null, codesResolver, null);
|
ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null, codesResolver, null);
|
||||||
helper.setValidator(validator);
|
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());
|
ViewState state = new ViewState(requestContext.getRootFlow(), "state2", new StubViewFactory());
|
||||||
|
state.getTransitionSet().add(transition);
|
||||||
requestContext.setCurrentState(state);
|
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();
|
helper.validate();
|
||||||
|
|
||||||
assertTrue(validator.fallbackInvoked);
|
assertTrue(validator.fallbackInvoked);
|
||||||
@@ -367,7 +408,7 @@ public class ValidationHelperTests extends TestCase {
|
|||||||
fallbackInvoked = true;
|
fallbackInvoked = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class State1 {}
|
private static interface State1 {}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ExtendedModel extends Model {
|
public static class ExtendedModel extends Model {
|
||||||
|
|||||||
Reference in New Issue
Block a user