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");
|
||||
* 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.
|
||||
* <p>
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user