global transition respecting bind and validate flags

This commit is contained in:
Keith Donald
2008-07-29 15:47:05 +00:00
parent 4a57f3d1ff
commit 209ccc2fbb
14 changed files with 100 additions and 27 deletions

View File

@@ -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"));
}

View File

@@ -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;

View File

@@ -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());
}

View File

@@ -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");
}

View File

@@ -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

View File

@@ -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");
}

View File

@@ -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()));
}

View File

@@ -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() {

View File

@@ -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;
}

View File

@@ -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
*/

View File

@@ -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));

View File

@@ -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 <code>null</code> 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.

View File

@@ -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;
}

View File

@@ -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();
}