diff --git a/spring-webflow/src/main/java/org/springframework/webflow/action/FlowDefinitionRedirectAction.java b/spring-webflow/src/main/java/org/springframework/webflow/action/FlowDefinitionRedirectAction.java index 61c825f8..2774031f 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/action/FlowDefinitionRedirectAction.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/action/FlowDefinitionRedirectAction.java @@ -46,7 +46,6 @@ public class FlowDefinitionRedirectAction extends AbstractAction { } public static FlowDefinitionRedirectAction create(String encodedFlowRedirect) { - // TODO - return null; + throw new UnsupportedOperationException("Not yet implemented"); } } 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 b642f8df..2e6738b3 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 @@ -16,7 +16,9 @@ package org.springframework.webflow.engine; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; +import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; @@ -129,7 +131,7 @@ public class Flow extends AnnotatedObject implements FlowDefinition { /** * The set of flow variables created by this flow. */ - private Set variables = new LinkedHashSet(3); + private Map variables = new LinkedHashMap(); /** * The mapper to map flow input attributes. @@ -350,7 +352,7 @@ public class Flow extends AnnotatedObject implements FlowDefinition { * @param variable the variable */ public void addVariable(FlowVariable variable) { - variables.add(variable); + variables.put(variable.getName(), variable); } /** @@ -366,11 +368,19 @@ public class Flow extends AnnotatedObject implements FlowDefinition { } } + /** + * Returns the flow variable with the given name. + * @param name the name of the variable + */ + public FlowVariable getVariable(String name) { + return (FlowVariable) variables.get(name); + } + /** * Returns the flow variables. */ public FlowVariable[] getVariables() { - return (FlowVariable[]) variables.toArray(new FlowVariable[variables.size()]); + return (FlowVariable[]) variables.values().toArray(new FlowVariable[variables.size()]); } /** @@ -513,6 +523,7 @@ public class Flow extends AnnotatedObject implements FlowDefinition { * @throws FlowExecutionException when an exception occurs during the resume operation */ public void resume(RequestControlContext context) throws FlowExecutionException { + restoreVariables(context); getCurrentViewState(context).resume(context); } @@ -575,11 +586,8 @@ public class Flow extends AnnotatedObject implements FlowDefinition { } } - /** - * Create (setup) all known flow variables in flow scope. - */ private void createVariables(RequestContext context) { - Iterator it = variables.iterator(); + Iterator it = variables.values().iterator(); while (it.hasNext()) { FlowVariable variable = (FlowVariable) it.next(); if (logger.isDebugEnabled()) { @@ -589,9 +597,17 @@ public class Flow extends AnnotatedObject implements FlowDefinition { } } - /** - * Returns the current state and makes sure it is a view state. - */ + private void restoreVariables(RequestContext context) { + Iterator it = variables.values().iterator(); + while (it.hasNext()) { + FlowVariable variable = (FlowVariable) it.next(); + if (logger.isDebugEnabled()) { + logger.debug("Restoring " + variable); + } + variable.restore(context); + } + } + private ViewState getCurrentViewState(RequestControlContext context) { State currentState = (State) context.getCurrentState(); if (!(currentState instanceof ViewState)) { @@ -601,9 +617,6 @@ public class Flow extends AnnotatedObject implements FlowDefinition { return (ViewState) currentState; } - /** - * Returns the current state and makes sure it is transitionable. - */ private TransitionableState getCurrentTransitionableState(RequestControlContext context) { State currentState = (State) context.getCurrentState(); if (!(currentState instanceof TransitionableState)) { diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/FlowVariable.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/FlowVariable.java index c73268d8..6ba2bfab 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/FlowVariable.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/FlowVariable.java @@ -17,7 +17,6 @@ package org.springframework.webflow.engine; import org.springframework.core.style.ToStringCreator; import org.springframework.util.Assert; -import org.springframework.webflow.execution.FlowSession; import org.springframework.webflow.execution.RequestContext; /** @@ -85,10 +84,6 @@ public class FlowVariable extends AnnotatedObject { return name.hashCode() + valueFactory.hashCode() + local.hashCode(); } - /** - * Creates a new instance of this flow variable in the configured scope. - * @param context the flow execution request context - */ public final void create(RequestContext context) { Object value = valueFactory.createVariableValue(context); if (local == Boolean.TRUE) { @@ -98,10 +93,10 @@ public class FlowVariable extends AnnotatedObject { } } - public final Object restore(FlowSession session, RequestContext context) { + public final Object restore(RequestContext context) { Object value; if (local == Boolean.TRUE) { - value = session.getScope().get(name); + value = context.getFlowScope().get(name); } else { value = context.getConversationScope().get(name); } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/State.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/State.java index ed2373fb..b9d7f305 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/State.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/State.java @@ -184,10 +184,21 @@ public abstract class State extends AnnotatedObject implements StateDefinition { logger.debug("Entering state '" + getId() + "' of flow '" + getFlow().getId() + "'"); } context.setCurrentState(this); + doPreEntryActions(context); entryActionList.execute(context); doEnter(context); } + /** + * Hook method to execute before running state entry actions upon state entry. Does nothing by default. Subclasses + * may override. + * @param context the request control context + * @throws FlowExecutionException if an exception occurs + */ + protected void doPreEntryActions(RequestControlContext context) throws FlowExecutionException { + + } + /** * Hook method to execute custom behavior as a result of entering this state. By implementing this method subclasses * specialize the behavior of the state. diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/ViewState.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/ViewState.java index e2be964b..4cf7751b 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/ViewState.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/ViewState.java @@ -15,10 +15,15 @@ */ package org.springframework.webflow.engine; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; + import org.springframework.core.style.ToStringCreator; import org.springframework.util.Assert; import org.springframework.webflow.execution.Event; import org.springframework.webflow.execution.FlowExecutionException; +import org.springframework.webflow.execution.RequestContext; import org.springframework.webflow.execution.View; import org.springframework.webflow.execution.ViewFactory; @@ -33,25 +38,33 @@ import org.springframework.webflow.execution.ViewFactory; */ public class ViewState extends TransitionableState { - private static final String FLOW_MODAL_VIEW_HEADER = "Flow-Modal-View"; - - private static final String MODAL_ATTR = "modal"; + private static final String POPUP_VIEW_HEADER = "Flow-Modal-View"; /** * The list of actions to be executed when this state is entered. */ private ActionList renderActionList = new ActionList(); - /** - * Whether or not a redirect should occur before the view is rendered. - */ - private boolean redirect; - /** * A factory for creating and restoring the view rendered by this view state. */ private ViewFactory viewFactory; + /** + * The set of view variables created by this view state. + */ + private Map variables = new LinkedHashMap(); + + /** + * Whether or not a redirect should occur before the view is rendered. + */ + private Boolean redirect; + + /** + * Whether or not the view should render as a popup. + */ + private boolean popup; + /** * Create a new view state. * @param flow the owning flow @@ -65,12 +78,53 @@ public class ViewState extends TransitionableState { this.viewFactory = viewFactory; } + /** + * Adds a view variable. + * @param variable the variable + */ + public void addVariable(ViewVariable variable) { + variables.put(variable.getName(), variable); + } + + /** + * Adds a set of view variables. + * @param variables the variables + */ + public void addVariables(ViewVariable[] variables) { + for (int i = 0; i < variables.length; i++) { + addVariable(variables[i]); + } + } + + /** + * Returns the view variable with the given name. + * @param name the name of the variable + */ + public ViewVariable getVariable(String name) { + return (ViewVariable) variables.get(name); + } + + /** + * Returns the flow variables. + */ + public ViewVariable[] getVariables() { + return (ViewVariable[]) variables.values().toArray(new ViewVariable[variables.size()]); + } + /** * Sets whether this view state should send a flow execution redirect when entered. * @param redirect the redirect flag */ public void setRedirect(boolean redirect) { - this.redirect = redirect; + this.redirect = Boolean.valueOf(redirect); + } + + /** + * Sets whether this view state should render as a popup. + * @param popup the popup flag + */ + public void setPopup(boolean popup) { + this.popup = popup; } /** @@ -81,13 +135,16 @@ public class ViewState extends TransitionableState { return renderActionList; } + protected void doPreEntryActions(RequestControlContext context) throws FlowExecutionException { + createVariables(context); + } + protected void doEnter(RequestControlContext context) throws FlowExecutionException { context.assignFlowExecutionKey(); - if (context.getExternalContext().isAjaxRequest() - && Boolean.TRUE.equals(context.getCurrentState().getAttributes().getBoolean(MODAL_ATTR))) { - context.getExternalContext().setResponseHeader(FLOW_MODAL_VIEW_HEADER, "true"); - } if (shouldRedirect(context)) { + if (context.getExternalContext().isAjaxRequest() && popup) { + context.getExternalContext().setResponseHeader(POPUP_VIEW_HEADER, "true"); + } context.sendFlowExecutionRedirect(); } else { View view = viewFactory.getView(context); @@ -103,6 +160,7 @@ public class ViewState extends TransitionableState { public void resume(RequestControlContext context) { View view = viewFactory.getView(context); + restoreVariables(context); if (view.eventSignaled()) { Event event = view.getEvent(); if (logger.isDebugEnabled()) { @@ -120,12 +178,58 @@ public class ViewState extends TransitionableState { } } + public void exit(RequestControlContext context) { + destroyVariables(context); + super.exit(context); + } + + // internal helpers + private boolean shouldRedirect(RequestControlContext context) { - return redirect || context.getAlwaysRedirectOnPause(); + if (redirect != null) { + return redirect.booleanValue(); + } else { + return context.getAlwaysRedirectOnPause(); + } + } + + private void createVariables(RequestContext context) { + Iterator it = variables.values().iterator(); + while (it.hasNext()) { + ViewVariable variable = (ViewVariable) it.next(); + if (logger.isDebugEnabled()) { + logger.debug("Creating " + variable); + } + variable.create(context); + } + } + + private void restoreVariables(RequestContext context) { + Iterator it = variables.values().iterator(); + while (it.hasNext()) { + ViewVariable variable = (ViewVariable) it.next(); + if (logger.isDebugEnabled()) { + logger.debug("Destroying " + variable); + + } + variable.restore(context); + } + } + + private void destroyVariables(RequestContext context) { + Iterator it = variables.values().iterator(); + while (it.hasNext()) { + ViewVariable variable = (ViewVariable) it.next(); + if (logger.isDebugEnabled()) { + logger.debug("Destroying " + variable); + + } + variable.destroy(context); + } } protected void appendToString(ToStringCreator creator) { super.appendToString(creator); - creator.append("viewManager", viewFactory); + creator.append("viewFactory", viewFactory).append("variables", variables); } } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/ViewVariable.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/ViewVariable.java new file mode 100644 index 00000000..3f7dbdd7 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/ViewVariable.java @@ -0,0 +1,50 @@ +package org.springframework.webflow.engine; + +import org.springframework.webflow.execution.RequestContext; + +public class ViewVariable extends AnnotatedObject { + private String name; + + private VariableValueFactory valueFactory; + + public ViewVariable(String name, VariableValueFactory valueFactory) { + this.name = name; + this.valueFactory = valueFactory; + } + + public String getName() { + return name; + } + + public VariableValueFactory getValueFactory() { + return valueFactory; + } + + // name and scope based equality + + public boolean equals(Object o) { + if (!(o instanceof ViewVariable)) { + return false; + } + ViewVariable other = (ViewVariable) o; + return name.equals(other.name) && valueFactory.equals(other.valueFactory); + } + + public int hashCode() { + return name.hashCode() + valueFactory.hashCode(); + } + + public final void create(RequestContext context) { + Object value = valueFactory.createVariableValue(context); + context.getFlowScope().put(name, value); + } + + public final Object restore(RequestContext context) { + Object value = context.getFlowScope().get(name); + return valueFactory.restoreReferences(value, context); + } + + public final Object destroy(RequestContext context) { + return context.getFlowScope().remove(name); + } +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/FlowArtifactFactory.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/FlowArtifactFactory.java index 42477416..d7af8e78 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/FlowArtifactFactory.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/FlowArtifactFactory.java @@ -30,6 +30,7 @@ import org.springframework.webflow.engine.Transition; import org.springframework.webflow.engine.TransitionCriteria; import org.springframework.webflow.engine.TransitionableState; import org.springframework.webflow.engine.ViewState; +import org.springframework.webflow.engine.ViewVariable; import org.springframework.webflow.execution.Action; import org.springframework.webflow.execution.ViewFactory; @@ -38,7 +39,7 @@ import org.springframework.webflow.execution.ViewFactory; * {@link Transition transitions}. *

* This factory encapsulates the construction of each Flow implementation as well as each core artifact type. Subclasses - * may customize how the core elements are created, useful for plugging in custom implementations. + * may customize how the core elements are created. * * @author Keith Donald * @author Erwin Vervaet @@ -69,6 +70,7 @@ public class FlowArtifactFactory { * @param entryActions any state entry actions; may be null * @param viewFactory the state view factory strategy * @param redirect whether to send a flow execution redirect before rendering + * @param popup whether to display the view in a popup window * @param renderActions any 'render actions' to execute on entry and refresh; may be null * @param transitions any transitions (paths) out of this state; may be null * @param exceptionHandlers any exception handlers; may be null @@ -77,11 +79,13 @@ public class FlowArtifactFactory { * null * @return the fully initialized view state instance */ - public State createViewState(String id, Flow flow, Action[] entryActions, ViewFactory viewFactory, - boolean redirect, Action[] renderActions, Transition[] transitions, + public State createViewState(String id, Flow flow, ViewVariable[] variables, Action[] entryActions, + ViewFactory viewFactory, boolean redirect, boolean popup, Action[] renderActions, Transition[] transitions, FlowExecutionExceptionHandler[] exceptionHandlers, Action[] exitActions, AttributeMap attributes) { ViewState viewState = new ViewState(flow, id, viewFactory); + viewState.addVariables(variables); viewState.setRedirect(redirect); + viewState.setPopup(popup); viewState.getRenderActionList().addAll(renderActions); configureCommonProperties(viewState, entryActions, transitions, exceptionHandlers, exitActions, attributes); return viewState; diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/support/ActionExecutingViewFactory.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/support/ActionExecutingViewFactory.java index 21a1ad47..588ea513 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/support/ActionExecutingViewFactory.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/support/ActionExecutingViewFactory.java @@ -65,7 +65,9 @@ public class ActionExecutingViewFactory implements ViewFactory { } public void render() { - ActionExecutor.execute(action, context); + if (action != null) { + ActionExecutor.execute(action, context); + } } } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/XmlFlowBuilder.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/XmlFlowBuilder.java index e0885283..188ead31 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/XmlFlowBuilder.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/XmlFlowBuilder.java @@ -16,6 +16,7 @@ package org.springframework.webflow.engine.builder.xml; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; @@ -43,6 +44,7 @@ import org.springframework.binding.method.MethodSignature; import org.springframework.binding.method.Parameter; import org.springframework.binding.method.Parameters; import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigUtils; import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; @@ -70,6 +72,7 @@ import org.springframework.webflow.engine.TargetStateResolver; 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.FlowArtifactFactory; import org.springframework.webflow.engine.builder.FlowBuilderException; import org.springframework.webflow.engine.builder.support.AbstractFlowBuilder; @@ -435,7 +438,8 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde + importElement.getAttribute(RESOURCE_ATTRIBUTE) + "'", e); } } - this.localFlowBuilderContext = new LocalFlowBuilderContext(getContext(), createFlowApplicationContext(resources)); + this.localFlowBuilderContext = new LocalFlowBuilderContext(getContext(), + createFlowApplicationContext(resources)); } private GenericApplicationContext createFlowApplicationContext(Resource[] resources) { @@ -459,6 +463,7 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde } } flowContext.setResourceLoader(new FlowRelativeResourceLoader(resource)); + AnnotationConfigUtils.registerAnnotationConfigProcessors(flowContext); new XmlBeanDefinitionReader(flowContext).loadBeanDefinitions(resources); registerFlowBeans(flowContext.getDefaultListableBeanFactory()); flowContext.refresh(); @@ -561,14 +566,35 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde } private void parseAndAddViewState(Element element, Flow flow) { - ViewInfo viewInfo = parseViewInfo(element); + ViewFactory viewFactory = parseViewFactory(element); boolean redirect = false; - if (viewInfo.redirect != null) { - redirect = viewInfo.redirect.booleanValue(); + if (element.hasAttribute("redirect")) { + redirect = ((Boolean) fromStringTo(Boolean.class).execute(element.getAttribute("redirect"))).booleanValue(); } - getFlowArtifactFactory().createViewState(parseId(element), flow, parseEntryActions(element), - viewInfo.viewFactory, redirect, parseRenderActions(element), parseTransitions(element), - parseExceptionHandlers(element), parseExitActions(element), parseAttributes(element)); + boolean popup = false; + if (element.hasAttribute("popup")) { + popup = ((Boolean) fromStringTo(Boolean.class).execute(element.getAttribute("popup"))).booleanValue(); + } + getFlowArtifactFactory().createViewState(parseId(element), flow, parseViewVariables(element), + parseEntryActions(element), viewFactory, redirect, popup, parseRenderActions(element), + parseTransitions(element), parseExceptionHandlers(element), parseExitActions(element), + parseAttributes(element)); + } + + private ViewVariable[] parseViewVariables(Element viewStateElement) { + List varElements = DomUtils.getChildElementsByTagName(viewStateElement, VAR_ELEMENT); + List variables = new ArrayList(varElements.size()); + for (Iterator it = varElements.iterator(); it.hasNext();) { + variables.add(parseViewVariable((Element) it.next())); + } + return (ViewVariable[]) variables.toArray(new ViewVariable[variables.size()]); + } + + private ViewVariable parseViewVariable(Element element) { + Class clazz = (Class) fromStringTo(Class.class).execute(element.getAttribute(CLASS_ATTRIBUTE)); + VariableValueFactory valueFactory = new BeanFactoryVariableValueFactory(clazz, + (AutowireCapableBeanFactory) getFlow().getBeanFactory()); + return new ViewVariable(element.getAttribute("name"), valueFactory); } private void parseAndAddDecisionState(Element element, Flow flow) { @@ -602,41 +628,38 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde } } - private ViewInfo parseViewInfo(Element element) { + private ViewFactory parseViewFactory(Element element) { String encodedView = element.getAttribute(VIEW_ATTRIBUTE); - if (encodedView == null || encodedView.length() == 0) { - // TODO what to do here? - return null; - } else if (encodedView.startsWith(REDIRECT_PREFIX)) { - String encodedViewName = encodedView.substring(REDIRECT_PREFIX.length()); - Expression viewName = getExpressionParser().parseExpression(encodedViewName, + if (!StringUtils.hasText(encodedView)) { + encodedView = createViewId(element.getAttribute(ID_ATTRIBUTE)); + Expression viewName = getExpressionParser().parseExpression(encodedView, new ParserContextImpl().eval(RequestContext.class).expect(String.class)); - ViewFactory viewFactory = getLocalContext().getViewFactoryCreator().createViewFactory(viewName, + return getLocalContext().getViewFactoryCreator().createViewFactory(viewName, getLocalContext().getResourceLoader()); - return new ViewInfo(viewFactory, Boolean.TRUE); } else if (encodedView.startsWith(EXTERNAL_REDIRECT_PREFIX)) { String encodedUrl = encodedView.substring(EXTERNAL_REDIRECT_PREFIX.length()); Expression externalUrl = getExpressionParser().parseExpression(encodedUrl, new ParserContextImpl().eval(RequestContext.class).expect(String.class)); - ViewFactory viewFactory = new ActionExecutingViewFactory(new ExternalRedirectAction(externalUrl)); - return new ViewInfo(viewFactory, Boolean.FALSE); + return new ActionExecutingViewFactory(new ExternalRedirectAction(externalUrl)); } else if (encodedView.startsWith(FLOW_DEFINITION_REDIRECT_PREFIX)) { String flowRedirect = encodedView.substring(FLOW_DEFINITION_REDIRECT_PREFIX.length()); - ViewFactory viewFactory = new ActionExecutingViewFactory(FlowDefinitionRedirectAction.create(flowRedirect)); - return new ViewInfo(viewFactory, Boolean.FALSE); + return new ActionExecutingViewFactory(FlowDefinitionRedirectAction.create(flowRedirect)); } else if (encodedView.startsWith(BEAN_PREFIX)) { - ViewFactory viewFactory = (ViewFactory) getLocalContext().getBeanFactory().getBean( + return (ViewFactory) getLocalContext().getBeanFactory().getBean( encodedView.substring(BEAN_PREFIX.length()), ViewFactory.class); - return new ViewInfo(viewFactory, Boolean.FALSE); } else { Expression viewName = getExpressionParser().parseExpression(encodedView, new ParserContextImpl().eval(RequestContext.class).expect(String.class)); - ViewFactory viewFactory = getLocalContext().getViewFactoryCreator().createViewFactory(viewName, + return getLocalContext().getViewFactoryCreator().createViewFactory(viewName, getLocalContext().getResourceLoader()); - return new ViewInfo(viewFactory, null); } } + // TODO - make configurable + private String createViewId(String viewStateId) { + return viewStateId + ".html"; + } + private Action parseFinalResponseAction(Element element) { String encodedView = element.getAttribute(VIEW_ATTRIBUTE); if (encodedView == null || encodedView.length() == 0) { @@ -1123,12 +1146,14 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde private ViewFactory viewFactory; - private Boolean redirect; - - public ViewInfo(ViewFactory viewFactory, Boolean redirect) { + public ViewInfo(ViewFactory viewFactory) { this.viewFactory = viewFactory; - this.redirect = redirect; } + + public ViewFactory getViewFactory() { + return viewFactory; + } + } private static class FlowRelativeResourceLoader implements ResourceLoader { diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/spring-webflow-2.0.xsd b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/spring-webflow-2.0.xsd index 7e40f5f2..df534a93 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/spring-webflow-2.0.xsd +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/spring-webflow-2.0.xsd @@ -1294,7 +1294,17 @@ An attribute describing this state. ]]> - + + + + + + + + @@ -1365,13 +1375,6 @@ This value may also be a view name expression evaluated against the request cont

 	${flowScope.view}
 
-Use of the "redirect:" prefix indicates this view state should trigger a redirect to a -unique "flow execution URL". This causes the application view to render on the -redirected request to that URL. This allows browsers to refresh a specific -state of the conversation while it remains active: -
-	redirect:priceForm
-
Use of the "externalRedirect:" prefix indicates this view state should trigger a redirect to an absolute external URL, typically to interface with an external system. External redirect query parameters may be specified using ${expressions} that evaluate @@ -1379,27 +1382,43 @@ against the request context:
 	externalRedirect:/someOtherSystem.htm?orderId=${flowScope.order.id}
 
-Use of the "flowRedirect:" prefix has this view state generate a redirect to a URL -that launches a new flow execution of an identified flow: +Use of the "flowRedirect:" prefix requests a redirect to a new execution of another flow:
-	flowRedirect:editOrderFlow?orderId=${flowScope.order.id}
+	flowRedirect:myOtherFlow?someData=${flowScope.data}
 
-Use of the "bean:" prefix references a custom ViewSelector implementation you define, +Use of the "bean:" prefix references a custom ViewFactory implementation you define, exposed by id in either a flow-local context using the "import" element or in the parent context.
-	bean:myCustomViewSelector
+	bean:myCustomViewFactory
 
-The exact semantics regarding the interpretation of this value are determined by the -installed TextToViewSelector converter.
-Note when no view name is provided, this view state will make a "null" view selection. A null -view does not request the rendering of a view, it only pauses the flow and returns control -the client. Use a null view when another state is expected to generate the response. +Note when no view name is provided, the view to render will be determined by convention. +The default convention is to treat the id of this view state as the view identifier. ]]> + + + + + + + + + + + + + + diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/FlowVariableTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/FlowVariableTests.java new file mode 100644 index 00000000..ecefcbf7 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/FlowVariableTests.java @@ -0,0 +1,61 @@ +package org.springframework.webflow.engine; + +import junit.framework.TestCase; + +import org.springframework.webflow.execution.RequestContext; +import org.springframework.webflow.test.MockRequestContext; + +public class FlowVariableTests extends TestCase { + + private boolean restoreCalled; + + public void testCreateVariable() { + FlowVariable var = new FlowVariable("foo", new VariableValueFactory() { + public Object createVariableValue(RequestContext context) { + return "bar"; + } + + public Object restoreReferences(Object value, RequestContext context) { + return value; + } + + }, true); + MockRequestContext context = new MockRequestContext(); + var.create(context); + assertEquals("bar", context.getFlowScope().get("foo")); + } + + public void testCreateConversationVariable() { + FlowVariable var = new FlowVariable("foo", new VariableValueFactory() { + public Object createVariableValue(RequestContext context) { + return "bar"; + } + + public Object restoreReferences(Object value, RequestContext context) { + return value; + } + }, false); + MockRequestContext context = new MockRequestContext(); + var.create(context); + assertEquals("bar", context.getConversationScope().get("foo")); + } + + public void testCreateRestoreVariable() { + FlowVariable var = new FlowVariable("foo", new VariableValueFactory() { + public Object createVariableValue(RequestContext context) { + return "bar"; + } + + public Object restoreReferences(Object value, RequestContext context) { + restoreCalled = true; + assertEquals("bar", value); + return value; + } + }, false); + MockRequestContext context = new MockRequestContext(); + var.create(context); + var.restore(context); + assertEquals("bar", context.getConversationScope().get("foo")); + assertTrue(restoreCalled); + } +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/XmlFlowBuilderTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/XmlFlowBuilderTests.java index fffa6458..dc5a9757 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/XmlFlowBuilderTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/XmlFlowBuilderTests.java @@ -5,6 +5,7 @@ import junit.framework.TestCase; import org.springframework.beans.factory.support.StaticListableBeanFactory; import org.springframework.core.io.ClassPathResource; import org.springframework.webflow.engine.Flow; +import org.springframework.webflow.engine.ViewState; import org.springframework.webflow.engine.builder.FlowAssembler; import org.springframework.webflow.engine.builder.FlowBuilderException; import org.springframework.webflow.test.MockFlowBuilderContext; @@ -79,9 +80,17 @@ public class XmlFlowBuilderTests extends TestCase { builder = new XmlFlowBuilder(resource); FlowAssembler assembler = new FlowAssembler(builder, new MockFlowBuilderContext("flow")); Flow flow = assembler.assembleFlow(); - assertEquals("flow-foo", flow.getVariables()[0].getName()); - assertEquals(true, flow.getVariables()[0].isLocal()); + assertEquals("flow-foo", flow.getVariable("flow-foo").getName()); + assertEquals(true, flow.getVariable("flow-foo").isLocal()); assertEquals("conversation-foo", flow.getVariables()[1].getName()); assertEquals(false, flow.getVariables()[1].isLocal()); } + + public void testViewStateVariable() { + ClassPathResource resource = new ClassPathResource("flow-viewstate-var.xml", getClass()); + builder = new XmlFlowBuilder(resource); + FlowAssembler assembler = new FlowAssembler(builder, new MockFlowBuilderContext("flow")); + Flow flow = assembler.assembleFlow(); + assertNotNull(((ViewState) flow.getStateInstance("view")).getVariable("foo")); + } } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-viewstate-externalredirect.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-viewstate-externalredirect.xml new file mode 100644 index 00000000..2f697ef1 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-viewstate-externalredirect.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-viewstate-flowredirect.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-viewstate-flowredirect.xml new file mode 100644 index 00000000..0f310383 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-viewstate-flowredirect.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-viewstate-popup.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-viewstate-popup.xml new file mode 100644 index 00000000..20af01de --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-viewstate-popup.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-viewstate-redirect.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-viewstate-redirect.xml new file mode 100644 index 00000000..fac265e1 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-viewstate-redirect.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-viewstate-var.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-viewstate-var.xml new file mode 100644 index 00000000..5b86843f --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-viewstate-var.xml @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-viewstate-view.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-viewstate-view.xml new file mode 100644 index 00000000..352406a5 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-viewstate-view.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/support/BeanFactoryVariableValueFactoryTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/support/BeanFactoryVariableValueFactoryTests.java new file mode 100644 index 00000000..275ded80 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/support/BeanFactoryVariableValueFactoryTests.java @@ -0,0 +1,18 @@ +package org.springframework.webflow.engine.support; + +import junit.framework.TestCase; + +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.webflow.TestBean; +import org.springframework.webflow.test.MockRequestContext; + +public class BeanFactoryVariableValueFactoryTests extends TestCase { + private BeanFactoryVariableValueFactory factory; + + public void testCreateValue() { + factory = new BeanFactoryVariableValueFactory(TestBean.class, new DefaultListableBeanFactory()); + MockRequestContext context = new MockRequestContext(); + Object value = factory.createVariableValue(context); + assertTrue(value instanceof TestBean); + } +}