From 209ccc2fbbc0f1080a0c5a7c376de9b258633621 Mon Sep 17 00:00:00 2001 From: Keith Donald Date: Tue, 29 Jul 2008 15:47:05 +0000 Subject: [PATCH] global transition respecting bind and validate flags --- .../faces/ui/ProgressiveUICommand.java | 4 ++-- .../faces/webflow/FlowActionListener.java | 8 +++---- .../faces/webflow/JsfViewFactoryCreator.java | 4 ++-- ...lderServicesBeanDefinitionParserTests.java | 4 ++-- .../springframework/webflow/engine/Flow.java | 16 ++++++++++++++ .../webflow/engine/TransitionableState.java | 4 ++-- .../builder/model/FlowModelFlowBuilder.java | 13 +++++++++--- .../engine/impl/FlowExecutionImpl.java | 15 +++++++++++-- .../impl/RequestControlContextImpl.java | 4 ++++ .../webflow/engine/model/TransitionModel.java | 21 +++++++++++++++++++ .../builder/xml/XmlFlowModelBuilder.java | 1 + .../webflow/execution/RequestContext.java | 9 ++++++++ .../webflow/mvc/view/AbstractMvcView.java | 14 +++++-------- .../webflow/test/MockRequestContext.java | 10 +++++++++ 14 files changed, 100 insertions(+), 27 deletions(-) diff --git a/spring-faces/src/main/java/org/springframework/faces/ui/ProgressiveUICommand.java b/spring-faces/src/main/java/org/springframework/faces/ui/ProgressiveUICommand.java index 2388e0e1..32ad39a6 100644 --- a/spring-faces/src/main/java/org/springframework/faces/ui/ProgressiveUICommand.java +++ b/spring-faces/src/main/java/org/springframework/faces/ui/ProgressiveUICommand.java @@ -73,8 +73,8 @@ public class ProgressiveUICommand extends UICommand { RequestContext context = RequestContextHolder.getRequestContext(); if (context != null && getActionExpression().isLiteralText() && context.getCurrentState() instanceof TransitionableState) { - TransitionDefinition transition = ((TransitionableState) context.getCurrentState()) - .getTransition(getActionExpression().getExpressionString()); + TransitionDefinition transition = context + .getMatchingTransition(getActionExpression().getExpressionString()); if (transition != null && transition.getAttributes().contains("bind")) { return Boolean.FALSE.equals(transition.getAttributes().getBoolean("bind")); } 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 a137c083..6967214a 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 @@ -36,7 +36,6 @@ import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; import org.springframework.validation.Errors; import org.springframework.webflow.definition.TransitionDefinition; -import org.springframework.webflow.definition.TransitionableStateDefinition; import org.springframework.webflow.execution.RequestContext; import org.springframework.webflow.execution.RequestContextHolder; import org.springframework.webflow.execution.View; @@ -147,11 +146,10 @@ public class FlowActionListener implements ActionListener { if (model == null) { return false; } - TransitionableStateDefinition currentState = (TransitionableStateDefinition) requestContext.getCurrentState(); - TransitionDefinition transition = currentState.getTransition(eventId); + TransitionDefinition transition = requestContext.getMatchingTransition(eventId); if (transition != null) { - if (transition.getAttributes().contains("bind")) { - return transition.getAttributes().getBoolean("bind").booleanValue(); + if (transition.getAttributes().contains("validate")) { + return transition.getAttributes().getBoolean("validate").booleanValue(); } } return true; diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/JsfViewFactoryCreator.java b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfViewFactoryCreator.java index 89f1eb72..74c75961 100644 --- a/spring-faces/src/main/java/org/springframework/faces/webflow/JsfViewFactoryCreator.java +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfViewFactoryCreator.java @@ -20,8 +20,8 @@ import javax.faces.lifecycle.Lifecycle; import org.springframework.binding.convert.ConversionService; import org.springframework.binding.expression.Expression; import org.springframework.binding.expression.ExpressionParser; +import org.springframework.webflow.engine.builder.BinderConfiguration; import org.springframework.webflow.engine.builder.ViewFactoryCreator; -import org.springframework.webflow.engine.model.BinderModel; import org.springframework.webflow.execution.ViewFactory; /** @@ -36,7 +36,7 @@ public class JsfViewFactoryCreator implements ViewFactoryCreator { private Lifecycle lifecycle; public ViewFactory createViewFactory(Expression viewIdExpression, ExpressionParser expressionParser, - ConversionService conversionService, BinderModel binderModel) { + ConversionService conversionService, BinderConfiguration binderConfiguration) { return new JsfViewFactory(viewIdExpression, getLifecycle()); } diff --git a/spring-faces/src/test/java/org/springframework/faces/config/FacesFlowBuilderServicesBeanDefinitionParserTests.java b/spring-faces/src/test/java/org/springframework/faces/config/FacesFlowBuilderServicesBeanDefinitionParserTests.java index 55294ac2..972de9ea 100644 --- a/spring-faces/src/test/java/org/springframework/faces/config/FacesFlowBuilderServicesBeanDefinitionParserTests.java +++ b/spring-faces/src/test/java/org/springframework/faces/config/FacesFlowBuilderServicesBeanDefinitionParserTests.java @@ -16,9 +16,9 @@ import org.springframework.faces.model.converter.FacesConversionService; import org.springframework.faces.webflow.JSFMockHelper; import org.springframework.faces.webflow.JsfManagedBeanAwareELExpressionParser; import org.springframework.faces.webflow.JsfViewFactoryCreator; +import org.springframework.webflow.engine.builder.BinderConfiguration; import org.springframework.webflow.engine.builder.ViewFactoryCreator; import org.springframework.webflow.engine.builder.support.FlowBuilderServices; -import org.springframework.webflow.engine.model.BinderModel; import org.springframework.webflow.execution.ViewFactory; import org.springframework.webflow.expression.el.WebFlowELExpressionParser; @@ -64,7 +64,7 @@ public class FacesFlowBuilderServicesBeanDefinitionParserTests extends TestCase public static class TestViewFactoryCreator implements ViewFactoryCreator { public ViewFactory createViewFactory(Expression viewIdExpression, ExpressionParser expressionParser, - ConversionService conversionService, BinderModel binderModel) { + ConversionService conversionService, BinderConfiguration binderConfiguration) { throw new UnsupportedOperationException("Auto-generated method stub"); } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/Flow.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/Flow.java index eb2931a2..ccf410d5 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/Flow.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/Flow.java @@ -40,6 +40,7 @@ import org.springframework.webflow.core.collection.LocalAttributeMap; import org.springframework.webflow.core.collection.MutableAttributeMap; import org.springframework.webflow.definition.FlowDefinition; import org.springframework.webflow.definition.StateDefinition; +import org.springframework.webflow.definition.TransitionDefinition; import org.springframework.webflow.execution.FlowExecutionException; import org.springframework.webflow.execution.RequestContext; @@ -466,6 +467,21 @@ public class Flow extends AnnotatedObject implements FlowDefinition { return globalTransitionSet; } + /** + * Returns the transition that matches the event with the provided id. + * @param eventId the event id + * @return the transition that matches, or null if no match is found. + */ + public TransitionDefinition getGlobalTransition(String eventId) { + for (Iterator it = globalTransitionSet.iterator(); it.hasNext();) { + Transition transition = (Transition) it.next(); + if (transition.getId().equals(eventId)) { + return transition; + } + } + return null; + } + /** * Sets a reference to the application context hosting application objects needed by this flow. * @param applicationContext the application context diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/TransitionableState.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/TransitionableState.java index 0dfae6fb..44bdbcdf 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/TransitionableState.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/TransitionableState.java @@ -91,8 +91,8 @@ public abstract class TransitionableState extends State implements Transitionabl Transition transition = getTransitionSet().getTransition(context); if (transition == null) { throw new NoMatchingTransitionException(getFlow().getId(), getId(), context.getCurrentEvent(), - "No transition found on occurence of event '" + context.getCurrentEvent() + "' in state '" + getId() - + "' of flow '" + getFlow().getId() + "' -- valid transitional criteria are " + "No transition found on occurence of event '" + context.getCurrentEvent() + "' in state '" + + getId() + "' of flow '" + getFlow().getId() + "' -- valid transitional criteria are " + StylerUtils.style(getTransitionSet().getTransitionCriterias()) + " -- likely programmer error, check the set of TransitionCriteria for this state"); } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/model/FlowModelFlowBuilder.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/model/FlowModelFlowBuilder.java index 30e7f815..6dd2eac9 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/model/FlowModelFlowBuilder.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/model/FlowModelFlowBuilder.java @@ -66,9 +66,9 @@ import org.springframework.webflow.engine.Transition; import org.springframework.webflow.engine.TransitionCriteria; import org.springframework.webflow.engine.VariableValueFactory; import org.springframework.webflow.engine.ViewVariable; +import org.springframework.webflow.engine.builder.BinderConfiguration; import org.springframework.webflow.engine.builder.FlowBuilderContext; import org.springframework.webflow.engine.builder.FlowBuilderException; -import org.springframework.webflow.engine.builder.BinderConfiguration; import org.springframework.webflow.engine.builder.BinderConfiguration.Binding; import org.springframework.webflow.engine.builder.support.AbstractFlowBuilder; import org.springframework.webflow.engine.model.AbstractActionModel; @@ -628,8 +628,12 @@ public class FlowModelFlowBuilder extends AbstractFlowBuilder { List bindings = binderModel.getBindings(); for (Iterator it = bindings.iterator(); it.hasNext();) { BindingModel bindingModel = (BindingModel) it.next(); - boolean required = ((Boolean) fromStringTo(Boolean.class).execute(bindingModel.getRequired())) - .booleanValue(); + boolean required; + if (StringUtils.hasText(bindingModel.getRequired())) { + required = ((Boolean) fromStringTo(Boolean.class).execute(bindingModel.getRequired())).booleanValue(); + } else { + required = false; + } Binding binding = new Binding(bindingModel.getProperty(), bindingModel.getConverter(), required); binderConfiguration.addBinding(binding); } @@ -797,6 +801,9 @@ public class FlowModelFlowBuilder extends AbstractFlowBuilder { if (StringUtils.hasText(transition.getBind())) { attributes.put("bind", fromStringTo(Boolean.class).execute(transition.getBind())); } + if (StringUtils.hasText(transition.getValidate())) { + attributes.put("validate", fromStringTo(Boolean.class).execute(transition.getValidate())); + } if (StringUtils.hasText(transition.getHistory())) { attributes.put("history", fromStringTo(History.class).execute(transition.getHistory())); } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImpl.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImpl.java index 9c90d44d..074f3440 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImpl.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImpl.java @@ -37,10 +37,12 @@ import org.springframework.webflow.core.collection.CollectionUtils; import org.springframework.webflow.core.collection.LocalAttributeMap; import org.springframework.webflow.core.collection.MutableAttributeMap; import org.springframework.webflow.definition.FlowDefinition; +import org.springframework.webflow.definition.TransitionDefinition; import org.springframework.webflow.engine.Flow; import org.springframework.webflow.engine.RequestControlContext; import org.springframework.webflow.engine.State; import org.springframework.webflow.engine.Transition; +import org.springframework.webflow.engine.TransitionableState; import org.springframework.webflow.execution.Event; import org.springframework.webflow.execution.FlowExecution; import org.springframework.webflow.execution.FlowExecutionException; @@ -321,8 +323,7 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable { * Create a flow execution control context. * @param externalContext the external context triggering this request */ - protected RequestControlContext createRequestContext(ExternalContext externalContext, - MessageContext messageContext) { + protected RequestControlContext createRequestContext(ExternalContext externalContext, MessageContext messageContext) { return new RequestControlContextImpl(this, externalContext, messageContext); } @@ -416,6 +417,16 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable { keyFactory.removeAllFlowExecutionSnapshots(this); } + TransitionDefinition getMatchingTransition(String eventId) { + FlowSessionImpl session = getActiveSessionInternal(); + TransitionableState currentState = (TransitionableState) session.getState(); + TransitionDefinition transition = currentState.getTransition(eventId); + if (transition == null) { + transition = session.getFlow().getGlobalTransition(eventId); + } + return transition; + } + // package private setters for restoring transient state used by FlowExecutionImplServicesConfigurer FlowExecutionListener[] getListeners() { diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/RequestControlContextImpl.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/RequestControlContextImpl.java index 72f4feb4..6d5605d9 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/RequestControlContextImpl.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/RequestControlContextImpl.java @@ -105,6 +105,10 @@ class RequestControlContextImpl implements RequestControlContext { return flowExecution.getActiveSession().getState(); } + public TransitionDefinition getMatchingTransition(String eventId) throws IllegalStateException { + return flowExecution.getMatchingTransition(eventId); + } + public MutableAttributeMap getRequestScope() { return requestScope; } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/TransitionModel.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/TransitionModel.java index 85be869c..6b6c0c7c 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/TransitionModel.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/TransitionModel.java @@ -39,6 +39,8 @@ public class TransitionModel extends AbstractModel { private String bind; + private String validate; + private String history; private LinkedList attributes; @@ -66,6 +68,7 @@ public class TransitionModel extends AbstractModel { setOnException(merge(getOnException(), transition.getOnException())); setTo(merge(getTo(), transition.getTo())); setBind(merge(getBind(), transition.getBind())); + setBind(merge(getValidate(), transition.getValidate())); setHistory(merge(getHistory(), transition.getHistory())); setAttributes(merge(getAttributes(), transition.getAttributes())); setSecured((SecuredModel) merge(getSecured(), transition.getSecured())); @@ -144,6 +147,24 @@ public class TransitionModel extends AbstractModel { } } + /** + * @return the validate + */ + public String getValidate() { + return validate; + } + + /** + * @param validate the validate to set + */ + public void setValidate(String validate) { + if (StringUtils.hasText(validate)) { + this.validate = validate; + } else { + this.validate = null; + } + } + /** * @return the history */ diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/XmlFlowModelBuilder.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/XmlFlowModelBuilder.java index a3c14a3c..1ba45d21 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/XmlFlowModelBuilder.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/XmlFlowModelBuilder.java @@ -471,6 +471,7 @@ public class XmlFlowModelBuilder implements FlowModelBuilder { transition.setTo(element.getAttribute("to")); transition.setOnException(element.getAttribute("on-exception")); transition.setBind(element.getAttribute("bind")); + transition.setValidate(element.getAttribute("validate")); transition.setHistory(element.getAttribute("history")); transition.setAttributes(parseAttributes(element)); transition.setSecured(parseSecured(element)); diff --git a/spring-webflow/src/main/java/org/springframework/webflow/execution/RequestContext.java b/spring-webflow/src/main/java/org/springframework/webflow/execution/RequestContext.java index f298d09b..1af9d610 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/execution/RequestContext.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/execution/RequestContext.java @@ -74,6 +74,15 @@ public interface RequestContext { */ public StateDefinition getCurrentState() throws IllegalStateException; + /** + * Returns the transition that would execute on the occurrence of the given event. + * @param eventId the id of the user event + * @return the transition that would trigger, or null if no transition matches + * @throws IllegalStateException if this flow execution has not been started at all, or if this execution has ended + * and is no longer actively executing + */ + public TransitionDefinition getMatchingTransition(String eventId) throws IllegalStateException; + /** * Returns true if the flow is currently active and in a view state. When in a view state {@link #getViewScope()}, * can be safely called. 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 0582b4ce..c9fb9d0d 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 @@ -51,7 +51,6 @@ import org.springframework.validation.Errors; import org.springframework.web.util.WebUtils; import org.springframework.webflow.core.collection.ParameterMap; import org.springframework.webflow.definition.TransitionDefinition; -import org.springframework.webflow.definition.TransitionableStateDefinition; import org.springframework.webflow.engine.builder.BinderConfiguration; import org.springframework.webflow.engine.builder.BinderConfiguration.Binding; import org.springframework.webflow.execution.Event; @@ -186,13 +185,14 @@ public abstract class AbstractMvcView implements View { if (model == null) { return; } - if (shouldBind(model)) { + TransitionDefinition transition = requestContext.getMatchingTransition(eventId); + if (shouldBind(model, transition)) { mappingResults = bind(model); if (hasMappingErrors(mappingResults)) { viewErrors = true; addErrorMessages(mappingResults); } else { - if (shouldValidate(model)) { + if (shouldValidate(model, transition)) { validate(model); if (requestContext.getMessageContext().hasErrorMessages()) { viewErrors = true; @@ -275,9 +275,7 @@ public abstract class AbstractMvcView implements View { return (Expression) requestContext.getCurrentState().getAttributes().get("model"); } - private boolean shouldBind(Object model) { - TransitionableStateDefinition currentState = (TransitionableStateDefinition) requestContext.getCurrentState(); - TransitionDefinition transition = currentState.getTransition(eventId); + private boolean shouldBind(Object model, TransitionDefinition transition) { if (transition == null) { return true; } @@ -409,9 +407,7 @@ public abstract class AbstractMvcView implements View { .defaultText(errorCode + " on " + field).build(); } - private boolean shouldValidate(Object model) { - TransitionableStateDefinition currentState = (TransitionableStateDefinition) requestContext.getCurrentState(); - TransitionDefinition transition = currentState.getTransition(eventId); + private boolean shouldValidate(Object model, TransitionDefinition transition) { if (transition == null) { return true; } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/test/MockRequestContext.java b/spring-webflow/src/main/java/org/springframework/webflow/test/MockRequestContext.java index 574ed5a0..2ae48dec 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/test/MockRequestContext.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/test/MockRequestContext.java @@ -28,6 +28,7 @@ import org.springframework.webflow.definition.StateDefinition; import org.springframework.webflow.definition.TransitionDefinition; import org.springframework.webflow.engine.Flow; import org.springframework.webflow.engine.Transition; +import org.springframework.webflow.engine.TransitionableState; import org.springframework.webflow.execution.Event; import org.springframework.webflow.execution.FlowExecutionContext; import org.springframework.webflow.execution.FlowSession; @@ -118,6 +119,15 @@ public class MockRequestContext implements RequestContext { return getFlowExecutionContext().getActiveSession().getState(); } + public TransitionDefinition getMatchingTransition(String eventId) throws IllegalStateException { + TransitionableState state = (TransitionableState) getFlowExecutionContext().getActiveSession().getState(); + TransitionDefinition transition = state.getTransition(eventId); + if (transition == null) { + transition = getRootFlow().getGlobalTransition(eventId); + } + return transition; + } + public boolean inViewState() { return getFlowExecutionContext().isActive() && getCurrentState() != null && getCurrentState().isViewState(); }