Support validation-hints on transitions

Issue SWF-1626
This commit is contained in:
Rossen Stoyanchev
2014-03-30 18:06:59 -04:00
parent 9dffe65dee
commit 76430e3e9f
7 changed files with 82 additions and 26 deletions

View File

@@ -812,6 +812,11 @@ public class FlowModelFlowBuilder extends AbstractFlowBuilder {
if (StringUtils.hasText(transition.getValidate())) {
attributes.put("validate", fromStringTo(Boolean.class).execute(transition.getValidate()));
}
if (StringUtils.hasText(transition.getValidationHints())) {
attributes.put("validationHints",
getLocalContext().getExpressionParser().parseExpression(transition.getValidationHints(),
new FluentParserContext().evaluate(RequestContext.class)));
}
if (StringUtils.hasText(transition.getHistory())) {
attributes.put("history", fromStringTo(History.class).execute(transition.getHistory().toUpperCase()));
}

View File

@@ -26,7 +26,7 @@ import org.springframework.util.StringUtils;
* A path from this state to another state triggered by an event. Transitions may execute one or more actions. All
* transition actions must execute successfully for the transition itself to complete. If no transition target is
* specified, the transition acts as a simple event handler and does not change the state of the flow.
*
*
* @author Scott Andrews
*/
public class TransitionModel extends AbstractModel {
@@ -41,6 +41,8 @@ public class TransitionModel extends AbstractModel {
private String validate;
private String validationHints;
private String history;
private LinkedList<AttributeModel> attributes;
@@ -70,6 +72,7 @@ public class TransitionModel extends AbstractModel {
setTo(merge(getTo(), transition.getTo()));
setBind(merge(getBind(), transition.getBind()));
setValidate(merge(getValidate(), transition.getValidate()));
setValidationHints(merge(getValidationHints(), transition.getValidationHints()));
setHistory(merge(getHistory(), transition.getHistory()));
setAttributes(merge(getAttributes(), transition.getAttributes()));
setSecured((SecuredModel) merge(getSecured(), transition.getSecured()));
@@ -83,6 +86,7 @@ public class TransitionModel extends AbstractModel {
copy.setTo(to);
copy.setBind(bind);
copy.setValidate(validate);
copy.setValidationHints(validationHints);
copy.setHistory(history);
copy.setAttributes(copyList(attributes));
copy.setSecured((SecuredModel) copy(secured));
@@ -180,6 +184,25 @@ public class TransitionModel extends AbstractModel {
}
}
/**
* @return the validation hints
*/
public String getValidationHints() {
return this.validationHints;
}
/**
* @param validationHints the validation hints expression to set
*/
public void setValidationHints(String validationHints) {
if (StringUtils.hasText(validationHints)) {
this.validationHints = validationHints;
} else {
this.validationHints = null;
}
}
/**
* @return the history
*/

View File

@@ -435,6 +435,7 @@ public class XmlFlowModelBuilder implements FlowModelBuilder {
transition.setOnException(element.getAttribute("on-exception"));
transition.setBind(element.getAttribute("bind"));
transition.setValidate(element.getAttribute("validate"));
transition.setValidationHints(element.getAttribute("validation-hints"));
transition.setHistory(element.getAttribute("history"));
transition.setAttributes(parseAttributes(element));
transition.setSecured(parseSecured(element));

View File

@@ -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.
@@ -232,7 +232,7 @@ public abstract class AbstractMvcView implements View {
addErrorMessages(mappingResults);
}
if (shouldValidate(model, transition)) {
validate(model);
validate(model, transition);
}
}
} else {
@@ -593,31 +593,37 @@ public abstract class AbstractMvcView implements View {
return (Expression) requestContext.getCurrentState().getAttributes().get("model");
}
private Object[] getValidationHints(Object model) {
Expression expr = (Expression) requestContext.getCurrentState().getAttributes().get("validationHints");
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();
if (expr != null) {
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 + "]");
}
try {
Object hintsValue = expr.getValue(requestContext);
if (hintsValue instanceof String) {
String[] hints = StringUtils.commaDelimitedListToStringArray((String) hintsValue);
return validationHintResolver.resolveValidationHints(model, flowId, stateId, hints);
}
catch (EvaluationException e) {
else if (hintsValue instanceof Object[]) {
return (Object[]) hintsValue;
}
else {
throw new FlowExecutionException(flowId, stateId,
"Failed to resolve validation hints expression [" + expr + "]", e);
"Failed to resolve validation hints [" + hintsValue + "]");
}
}
return null;
catch (EvaluationException e) {
throw new FlowExecutionException(flowId, stateId,
"Failed to resolve validation hints expression [" + expr + "]", e);
}
}
private Object getEmptyValue(Class<?> fieldType) {
@@ -665,14 +671,14 @@ public abstract class AbstractMvcView implements View {
}
}
private void validate(Object model) {
private void validate(Object model, TransitionDefinition transition) {
if (logger.isDebugEnabled()) {
logger.debug("Validating model");
}
ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, getModelExpression()
.getExpressionString(), expressionParser, messageCodesResolver, mappingResults);
helper.setValidator(this.validator);
helper.setValidationHints(getValidationHints(model));
helper.setValidationHints(getValidationHints(model, transition));
helper.validate();
}

View File

@@ -1485,6 +1485,21 @@ Indicates whether model validation should occur before this transition executes.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="validation-hints" type="expression">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
A comma-separated list of validation hints such as validation groups against a JSR-303 provider.
Each hint is used to find an inner Class either in the Class of the model or its parent classes.
For example, given org.example.MyModel with inner type MyGroup, the hint "MyGroup" can be used.
The hint "default" is reserved as the default validation group, i.e. "javax.validation.groups.Default".
A hint can also be a fully qualified class name.
The validation hints string is evaluated as an expression before hints are resolved.
The result of the expression can be a String or an Object[] with Class instances.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="history">
<xsd:annotation>
<xsd:documentation>

View File

@@ -14,6 +14,7 @@ import org.springframework.webflow.engine.builder.model.FlowModelFlowBuilder;
import org.springframework.webflow.engine.impl.FlowExecutionImplFactory;
import org.springframework.webflow.engine.model.FlowModel;
import org.springframework.webflow.engine.model.SecuredModel;
import org.springframework.webflow.engine.model.TransitionModel;
import org.springframework.webflow.engine.model.ViewStateModel;
import org.springframework.webflow.engine.model.builder.DefaultFlowModelHolder;
import org.springframework.webflow.engine.model.builder.FlowModelBuilder;
@@ -327,7 +328,7 @@ public class XmlFlowModelBuilderTests extends TestCase {
assertTrue(((TestBeanValidator) action.getValidator()).getInvoked());
}
public void testParseFlowValidationHints() {
public void testParsedFlowValidationHints() {
ClassPathResource res = new ClassPathResource("flow-validation-hints.xml", getClass());
XmlFlowModelBuilder builder = new XmlFlowModelBuilder(res);
DefaultFlowModelHolder holder = new DefaultFlowModelHolder(builder);
@@ -336,6 +337,9 @@ public class XmlFlowModelBuilderTests extends TestCase {
ViewStateModel state = (ViewStateModel) model.getStateById("state1");
assertEquals("foo,bar", state.getValidationHints());
TransitionModel transition = state.getTransitions().get(0);
assertEquals("baz", transition.getValidationHints());
state = (ViewStateModel) model.getStateById("state2");
assertNull(state.getValidationHints());
}

View File

@@ -2,7 +2,9 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow-2.4.xsd">
<view-state id="state1" validation-hints="foo,bar" />
<view-state id="state1" validation-hints="foo,bar">
<transition on="submit" to="state2" validation-hints="baz" />
</view-state>
<view-state id="state2" />