diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowViewStateManager.java b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowViewStateManager.java index 74037264..a0490807 100644 --- a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowViewStateManager.java +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowViewStateManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2004-2008 the original author or authors. + * Copyright 2004-2011 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. @@ -30,6 +30,7 @@ import org.springframework.webflow.execution.RequestContextHolder; * Custom {@link StateManager} that manages the JSF component state in web flow's view scope. * * @author Jeremy Grelle + * @author Rossen Stoyanchev */ public class FlowViewStateManager extends StateManager { @@ -150,31 +151,35 @@ public class FlowViewStateManager extends StateManager { /** *

* JSF 1.2 (or higher) version of state saving. - *

* *

- * In JSF 2 where a partial state saving algorithm is used, this method merely delegates to the next - * ViewStateManager. Thus partial state saving is handled by the JSF 2 runtime. However, a - * {@link FlowViewResponseStateManager} plugged in via {@link FlowRenderKit} will ensure the state is saved in a Web - * Flow view-scoped variable. - *

+ * In JSF 2, if partial state saving is enabled this method delegates in order to obtain the serialized view state. + * During rendering JSF calls this method to prepare the state and then calls {@link FlowViewResponseStateManager} + * which writes it to Web Flow's view scope. + * + *

+ * Nevertheless this method always writes the serialized state to Web Flow's view scope to ensure it is up-to-date + * for cases outside of rendering (e.g. ViewState.updateHistory()) or when the render phase doesn't call + * {@link FlowViewResponseStateManager} such as when processing a partial request. */ public Object saveView(FacesContext context) { if (context.getViewRoot().isTransient()) { return null; } + FlowSerializedView view = null; if ((!JsfUtils.isFlowRequest()) || JsfRuntimeInformation.isPartialStateSavingSupported()) { - return delegate.saveView(context); + Object[] state = (Object[]) delegate.saveView(context); + view = new FlowSerializedView(context.getViewRoot().getViewId(), state[0], state[1]); } else { - RequestContext requestContext = RequestContextHolder.getRequestContext(); - if (logger.isDebugEnabled()) { - logger.debug("Saving view root '" + context.getViewRoot().getViewId() + "' in view scope"); - } - FlowSerializedView view = new FlowSerializedView(context.getViewRoot().getViewId(), - getTreeStructureToSave(context), getComponentStateToSave(context)); - requestContext.getViewScope().put(SERIALIZED_VIEW_STATE, view); - return view; + view = new FlowSerializedView(context.getViewRoot().getViewId(), getTreeStructureToSave(context), + getComponentStateToSave(context)); } + RequestContext requestContext = RequestContextHolder.getRequestContext(); + if (logger.isDebugEnabled()) { + logger.debug("Saving view root '" + context.getViewRoot().getViewId() + "' in view scope"); + } + requestContext.getViewScope().put(SERIALIZED_VIEW_STATE, view); + return view; } /** diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/JsfView.java b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfView.java index ed7cfb63..d270b2c0 100644 --- a/spring-faces/src/main/java/org/springframework/faces/webflow/JsfView.java +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfView.java @@ -1,5 +1,5 @@ /* - * Copyright 2004-2008 the original author or authors. + * Copyright 2004-2011 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. @@ -88,6 +88,13 @@ public class JsfView implements View { try { logger.debug("Asking faces lifecycle to render"); facesLifecycle.render(facesContext); + + /* Ensure serialized view state is always updated even if JSF didn't call StateManager.writeState(). */ + if (JsfRuntimeInformation.isAtLeastJsf20()) { + if (requestContext.getExternalContext().isAjaxRequest()) { + saveState(); + } + } } finally { logger.debug("View rendering complete"); facesContext.responseComplete(); @@ -134,9 +141,10 @@ public class JsfView implements View { // Set the temporary UIViewRoot state so that it will be available across the redirect return new ViewRootHolder(getViewRoot()); } else { - // In JSF 2 the partial state saving algorithm attaches a system event listener to the UIViewRoot with - // a reference to the FacesContext instance. The FacesContext instance is released at end of each request. - // Hence, keeping the UIViewRoot across the redirect is not feasible. + // In JSF 2 the partial state saving algorithm attaches a system event listener to the UIViewRoot which + // holds on to a reference to the FacesContext instance. The FacesContext instance is released at end of + // each request. Hence, keeping the UIViewRoot across the redirect is not feasible. + // @see com.sun.faces.context.StateContext$AddRemoveListener logger.debug("User event state requested but not saved."); return null; } diff --git a/spring-webflow-samples/booking-mvc/.springBeans b/spring-webflow-samples/booking-mvc/.springBeans index 79d41891..3cc7edfd 100755 --- a/spring-webflow-samples/booking-mvc/.springBeans +++ b/spring-webflow-samples/booking-mvc/.springBeans @@ -1,12 +1,16 @@ 1 - + + src/main/webapp/WEB-INF/config/data-access-config.xml + src/main/webapp/WEB-INF/config/security-config.xml + src/main/webapp/WEB-INF/config/webflow-config.xml + src/main/webapp/WEB-INF/config/webmvc-config.xml diff --git a/spring-webflow-samples/booking-mvc/pom.xml b/spring-webflow-samples/booking-mvc/pom.xml index aaf7ff42..e142911d 100644 --- a/spring-webflow-samples/booking-mvc/pom.xml +++ b/spring-webflow-samples/booking-mvc/pom.xml @@ -17,7 +17,7 @@ org.springframework - spring-context + spring-webmvc ${org.springframework-version} @@ -37,11 +37,6 @@ spring-orm ${org.springframework-version} - - org.springframework - spring-webmvc - ${org.springframework-version} - org.springframework.webflow spring-webflow diff --git a/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/hotels/booking/booking-flow.xml b/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/hotels/booking/booking-flow.xml index d746efef..82841d80 100644 --- a/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/hotels/booking/booking-flow.xml +++ b/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/hotels/booking/booking-flow.xml @@ -1,12 +1,14 @@ - - - + xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow.xsd"> + + + + + - + @@ -23,11 +25,17 @@ + + + - + + + + @@ -36,7 +44,7 @@ - + \ No newline at end of file diff --git a/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/hotels/booking/enterBookingDetails.jsp b/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/hotels/booking/enterBookingDetails.jsp index 4416b2b0..1f95919d 100644 --- a/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/hotels/booking/enterBookingDetails.jsp +++ b/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/hotels/booking/enterBookingDetails.jsp @@ -27,7 +27,7 @@ - +

Book Hotel
@@ -187,7 +187,7 @@

diff --git a/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/hotels/booking/reviewBooking.jsp b/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/hotels/booking/reviewBooking.jsp index f7ecf680..9efe4faa 100644 --- a/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/hotels/booking/reviewBooking.jsp +++ b/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/hotels/booking/reviewBooking.jsp @@ -15,7 +15,7 @@
- +
Confirm Booking Details
@@ -49,9 +49,14 @@
- - - +

+ + + +

+
diff --git a/spring-webflow/src/main/java/org/springframework/webflow/config/FlowExecutorBeanDefinitionParser.java b/spring-webflow/src/main/java/org/springframework/webflow/config/FlowExecutorBeanDefinitionParser.java index 55918341..bd108a86 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/config/FlowExecutorBeanDefinitionParser.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/config/FlowExecutorBeanDefinitionParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2004-2008 the original author or authors. + * Copyright 2004-2011 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. @@ -89,16 +89,22 @@ class FlowExecutorBeanDefinitionParser extends AbstractSingleBeanDefinitionParse } } - private Set parseFlowExecutionAttributes(Element element) { + private Set parseFlowExecutionAttributes(Element element) { Element executionAttributesElement = DomUtils.getChildElementByTagName(element, "flow-execution-attributes"); if (executionAttributesElement != null) { - HashSet attributes = new HashSet(); + HashSet attributes = new HashSet(); Element redirectElement = DomUtils.getChildElementByTagName(executionAttributesElement, "always-redirect-on-pause"); if (redirectElement != null) { String value = redirectElement.getAttribute("value"); attributes.add(new FlowElementAttribute("alwaysRedirectOnPause", value, "boolean")); } + Element redirectInSameStateElement = DomUtils.getChildElementByTagName(executionAttributesElement, + "redirect-in-same-state"); + if (redirectInSameStateElement != null) { + String value = redirectInSameStateElement.getAttribute("value"); + attributes.add(new FlowElementAttribute("redirectInSameState", value, "boolean")); + } List attributeElements = DomUtils.getChildElementsByTagName(executionAttributesElement, "attribute"); for (Iterator it = attributeElements.iterator(); it.hasNext();) { Element attributeElement = (Element) it.next(); diff --git a/spring-webflow/src/main/java/org/springframework/webflow/config/FlowExecutorFactoryBean.java b/spring-webflow/src/main/java/org/springframework/webflow/config/FlowExecutorFactoryBean.java index a4f7a7b5..43aee271 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/config/FlowExecutorFactoryBean.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/config/FlowExecutorFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2004-2008 the original author or authors. + * Copyright 2004-2011 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. @@ -62,6 +62,8 @@ class FlowExecutorFactoryBean implements FactoryBean, ApplicationContextAware, B private static final String ALWAYS_REDIRECT_ON_PAUSE = "alwaysRedirectOnPause"; + private static final String REDIRECT_IN_SAME_STATE = "redirectInSameState"; + private FlowDefinitionLocator flowDefinitionLocator; private Integer maxFlowExecutions; @@ -185,11 +187,12 @@ class FlowExecutorFactoryBean implements FactoryBean, ApplicationContextAware, B private void putDefaultFlowExecutionAttributes(LocalAttributeMap executionAttributes) { if (!executionAttributes.contains(ALWAYS_REDIRECT_ON_PAUSE)) { - if (environment == MvcEnvironment.PORTLET) { - executionAttributes.put(ALWAYS_REDIRECT_ON_PAUSE, Boolean.FALSE); - } else { - executionAttributes.put(ALWAYS_REDIRECT_ON_PAUSE, Boolean.TRUE); - } + boolean redirect = (environment == MvcEnvironment.PORTLET) ? Boolean.FALSE : Boolean.TRUE; + executionAttributes.put(ALWAYS_REDIRECT_ON_PAUSE, redirect); + } + if (!executionAttributes.contains(REDIRECT_IN_SAME_STATE)) { + boolean redirect = (environment == MvcEnvironment.PORTLET) ? Boolean.FALSE : Boolean.TRUE; + executionAttributes.put(REDIRECT_IN_SAME_STATE, redirect); } } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/config/spring-webflow-config-2.3.xsd b/spring-webflow/src/main/java/org/springframework/webflow/config/spring-webflow-config-2.3.xsd index f187db5c..a9640aed 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/config/spring-webflow-config-2.3.xsd +++ b/spring-webflow/src/main/java/org/springframework/webflow/config/spring-webflow-config-2.3.xsd @@ -392,6 +392,16 @@ Example: 'flow1,flow2,flow3'. + + + + + + + @@ -420,6 +430,19 @@ true = always redirect on pause; false = do not, only redirect when explicitly i + + + + + + + + + + diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/RequestControlContext.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/RequestControlContext.java index f12f0194..2382f4dd 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/RequestControlContext.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/RequestControlContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2004-2008 the original author or authors. + * Copyright 2004-2011 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. @@ -150,4 +150,11 @@ public interface RequestControlContext extends RequestContext { */ public boolean getRedirectOnPause(); + /** + * Returns the value of the 'redirect in same state' flow execution attribute if set or otherwise it falls back on + * the value returned by {@link #getRedirectOnPause()}. + * @return true or false + */ + public boolean getRedirectInSameState(); + } \ No newline at end of file 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 27586b5c..ca2bd931 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 @@ -1,5 +1,5 @@ /* - * Copyright 2004-2008 the original author or authors. + * Copyright 2004-2011 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. @@ -41,6 +41,11 @@ import org.springframework.webflow.execution.ViewFactory; */ public class ViewState extends TransitionableState { + /** + * The name of the attribute indicating an Ajax-driven Flow Definition. + */ + private static final String AJAX_DRIVEN_ATTRIBUTE_NAME = "ajaxDriven"; + /** * The list of actions to be executed before the view is rendered. */ @@ -61,12 +66,6 @@ public class ViewState extends TransitionableState { */ private Boolean redirect; - /** - * Whether or not a redirect should occur when the state is not exited (e.g. invalid form submission, a transition - * without a "to" attribute). - */ - private Boolean redirectInSameState = Boolean.FALSE; - /** * Whether or not the view should render as a popup. */ @@ -139,22 +138,6 @@ public class ViewState extends TransitionableState { this.redirect = redirect; } - /** - * Returns whether this view state should request a flow execution redirect when the state hasn't been exited. - */ - public boolean getRedirectInSameState() { - return (redirectInSameState != null) ? redirectInSameState.booleanValue() : false; - } - - /** - * Sets whether this view state should requests a flow execution redirect when entered when processing is done but - * the state hasn't been exited (e.g. invalid form submissions). - * @param redirectInSameState the redirect flag - */ - public void setRedirectInSameState(Boolean redirectInSameState) { - this.redirectInSameState = redirectInSameState; - } - /** * Returns whether this view state should render as a popup. */ @@ -289,17 +272,29 @@ public class ViewState extends TransitionableState { private boolean shouldRedirect(RequestControlContext context) { if (redirect != null) { return redirect.booleanValue(); - } else { - return context.getRedirectOnPause(); } + if (getAjaxDriven(context) != null) { + if (context.getExternalContext().isAjaxRequest()) { + return false; + } + } + return context.getRedirectOnPause(); } private boolean shouldRedirectInSameState(RequestControlContext context) { - if (redirectInSameState != null) { - return redirectInSameState.booleanValue(); - } else { - return shouldRedirect(context); + if (redirect != null) { + return redirect.booleanValue(); } + if (getAjaxDriven(context) != null) { + if (context.getExternalContext().isAjaxRequest()) { + return false; + } + } + return context.getRedirectInSameState(); + } + + private Boolean getAjaxDriven(RequestControlContext context) { + return context.getActiveFlow().getAttributes().getBoolean(AJAX_DRIVEN_ATTRIBUTE_NAME); } private void render(RequestControlContext context, View view) throws ViewRenderingException { @@ -367,8 +362,8 @@ public class ViewState extends TransitionableState { protected void appendToString(ToStringCreator creator) { super.appendToString(creator); - creator.append("viewFactory", viewFactory).append("variables", variables).append("redirect", redirect).append( - "popup", popup); + creator.append("viewFactory", viewFactory).append("variables", variables).append("redirect", redirect) + .append("popup", popup); } } \ No newline at end of file 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 06e5fa56..988e263d 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 @@ -1,5 +1,5 @@ /* - * Copyright 2004-2008 the original author or authors. + * Copyright 2004-2011 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. @@ -86,7 +86,6 @@ public class FlowArtifactFactory { ViewState viewState = new ViewState(flow, id, viewFactory); viewState.addVariables(variables); viewState.setRedirect(redirect); - viewState.setRedirectInSameState(Boolean.FALSE); viewState.setPopup(popup); viewState.getRenderActionList().addAll(renderActions); configureCommonProperties(viewState, entryActions, transitions, exceptionHandlers, exitActions, attributes); 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 1609d946..8eb6d253 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 @@ -1,5 +1,5 @@ /* - * Copyright 2004-2008 the original author or authors. + * Copyright 2004-2011 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. @@ -76,6 +76,7 @@ import org.springframework.webflow.engine.model.AbstractActionModel; import org.springframework.webflow.engine.model.AbstractMappingModel; import org.springframework.webflow.engine.model.AbstractStateModel; import org.springframework.webflow.engine.model.ActionStateModel; +import org.springframework.webflow.engine.model.AjaxDrivenModel; import org.springframework.webflow.engine.model.AttributeModel; import org.springframework.webflow.engine.model.BeanImportModel; import org.springframework.webflow.engine.model.BinderModel; @@ -382,6 +383,7 @@ public class FlowModelFlowBuilder extends AbstractFlowBuilder { MutableAttributeMap flowAttributes = parseMetaAttributes(flow.getAttributes()); parseAndPutPersistenceContext(flow.getPersistenceContext(), flowAttributes); parseAndPutSecured(flow.getSecured(), flowAttributes); + parseAndPutAjaxDriven(flow.getAjaxDriven(), flowAttributes); return flowAttributes; } @@ -927,6 +929,12 @@ public class FlowModelFlowBuilder extends AbstractFlowBuilder { } } + private void parseAndPutAjaxDriven(AjaxDrivenModel ajaxDrivenModel, MutableAttributeMap attributes) { + if (ajaxDrivenModel != null) { + attributes.put("ajaxDriven", Boolean.TRUE); + } + } + private void parseAndPutSecured(SecuredModel secured, MutableAttributeMap attributes) { if (secured != null) { SecurityRule rule = new SecurityRule(); 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 260612f7..0db51f2a 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 @@ -1,5 +1,5 @@ /* - * Copyright 2004-2008 the original author or authors. + * Copyright 2004-2011 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. @@ -246,10 +246,22 @@ class RequestControlContextImpl implements RequestControlContext { return redirectOnPause != null ? redirectOnPause.booleanValue() : false; } + public boolean getRedirectInSameState() { + if (!getExternalContext().isResponseAllowed()) { + return true; + } + Boolean redirectInSameState = flowExecution.getAttributes().getBoolean("redirectInSameState"); + if (redirectInSameState != null) { + return redirectInSameState.booleanValue(); + } else { + return getRedirectOnPause(); + } + } + public String toString() { return new ToStringCreator(this).append("externalContext", externalContext) - .append("currentEvent", currentEvent).append("requestScope", requestScope).append("attributes", - attributes).append("messageContext", messageContext).append("flowExecution", flowExecution) - .toString(); + .append("currentEvent", currentEvent).append("requestScope", requestScope) + .append("attributes", attributes).append("messageContext", messageContext) + .append("flowExecution", flowExecution).toString(); } } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/AjaxDrivenModel.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/AjaxDrivenModel.java new file mode 100644 index 00000000..dea32826 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/AjaxDrivenModel.java @@ -0,0 +1,41 @@ +/* + * Copyright 2004-2011 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.webflow.engine.model; + +/** + * Model support for the ajax-driven element. + * + * @author Rossen Stoyanchev + * @since 2.3 + */ +public class AjaxDrivenModel extends AbstractModel { + + public AjaxDrivenModel() { + } + + public boolean isMergeableWith(Model model) { + return false; + } + + public void merge(Model model) { + + } + + public Model createCopy() { + return new AjaxDrivenModel(); + } + +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/FlowModel.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/FlowModel.java index 4607a06b..e7ff0e3b 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/FlowModel.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/FlowModel.java @@ -1,5 +1,5 @@ /* - * Copyright 2004-2008 the original author or authors. + * Copyright 2004-2011 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. @@ -58,6 +58,8 @@ public class FlowModel extends AbstractModel { private PersistenceContextModel persistenceContext; + private AjaxDrivenModel ajaxDriven; + private LinkedList vars; private LinkedList inputs; @@ -224,6 +226,20 @@ public class FlowModel extends AbstractModel { this.persistenceContext = persistenceContext; } + /** + * @return the ajaxDriven model + */ + public AjaxDrivenModel getAjaxDriven() { + return ajaxDriven; + } + + /** + * @param ajaxDriven the ajaxDriven model to set + */ + public void setAjaxDriven(AjaxDrivenModel ajaxDriven) { + this.ajaxDriven = ajaxDriven; + } + /** * @return the vars */ diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/WebFlowEntityResolver.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/WebFlowEntityResolver.java index 6425c168..8eb89788 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/WebFlowEntityResolver.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/WebFlowEntityResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2004-2008 the original author or authors. + * Copyright 2004-2011 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. @@ -24,7 +24,7 @@ import org.xml.sax.InputSource; import org.xml.sax.SAXException; /** - * EntityResolver implementation for the Spring Web Flow 2.0 XML Schema. This will load the XSD from the classpath. + * EntityResolver implementation for the Spring Web Flow XML Schema. This will load the XSD from the classpath. *

* The xmlns of the XSD expected to be resolved: * @@ -33,7 +33,7 @@ import org.xml.sax.SAXException; * <flow xmlns="http://www.springframework.org/schema/webflow" * 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.0.xsd"> + * http://www.springframework.org/schema/webflow/spring-webflow.xsd"> * * * @author Erwin Vervaet @@ -41,22 +41,32 @@ import org.xml.sax.SAXException; */ class WebFlowEntityResolver implements EntityResolver { - private static final String WEBFLOW_ELEMENT = "spring-webflow-2.0"; + private static final String[] WEBFLOW_VERSIONS = new String[] { "spring-webflow-2.3", "spring-webflow-2.0" }; public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { - if (systemId != null && systemId.indexOf(WEBFLOW_ELEMENT) > systemId.lastIndexOf("/")) { - String filename = systemId.substring(systemId.indexOf(WEBFLOW_ELEMENT)); - try { - Resource resource = new ClassPathResource(filename, getClass()); - InputSource source = new InputSource(resource.getInputStream()); - source.setPublicId(publicId); - source.setSystemId(systemId); - return source; - } catch (IOException ex) { - // fall through below + if (systemId != null && systemId.indexOf("spring-webflow.xsd") > -1) { + return createInputSource(publicId, systemId, WEBFLOW_VERSIONS[0] + ".xsd"); + } + for (int i = 0; i < WEBFLOW_VERSIONS.length; i++) { + if (systemId != null && systemId.indexOf(WEBFLOW_VERSIONS[i]) > systemId.lastIndexOf("/")) { + String fileName = systemId.substring(systemId.indexOf(WEBFLOW_VERSIONS[i])); + return createInputSource(publicId, systemId, fileName); } } // let the parser handle it return null; } -} \ No newline at end of file + + private InputSource createInputSource(String publicId, String systemId, String fileName) { + try { + Resource resource = new ClassPathResource(fileName, getClass()); + InputSource source = new InputSource(resource.getInputStream()); + source.setPublicId(publicId); + source.setSystemId(systemId); + return source; + } catch (IOException ex) { + // fall through below + } + return null; + } +} 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 b0d0128b..acedabfb 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 @@ -1,5 +1,5 @@ /* - * Copyright 2004-2008 the original author or authors. + * Copyright 2004-2011 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. @@ -31,6 +31,7 @@ import org.springframework.util.xml.DomUtils; import org.springframework.webflow.engine.model.AbstractActionModel; import org.springframework.webflow.engine.model.AbstractStateModel; import org.springframework.webflow.engine.model.ActionStateModel; +import org.springframework.webflow.engine.model.AjaxDrivenModel; import org.springframework.webflow.engine.model.AttributeModel; import org.springframework.webflow.engine.model.BeanImportModel; import org.springframework.webflow.engine.model.BinderModel; @@ -201,6 +202,7 @@ public class XmlFlowModelBuilder implements FlowModelBuilder { flow.setAttributes(parseAttributes(element)); flow.setSecured(parseSecured(element)); flow.setPersistenceContext(parsePersistenceContext(element)); + flow.setAjaxDriven(parseAjaxDriven(element)); flow.setVars(parseVars(element)); flow.setInputs(parseInputs(element)); flow.setOnStartActions(parseOnStartActions(element)); @@ -410,6 +412,15 @@ public class XmlFlowModelBuilder implements FlowModelBuilder { } } + private AjaxDrivenModel parseAjaxDriven(Element element) { + element = DomUtils.getChildElementByTagName(element, "ajax-driven"); + if (element == null) { + return null; + } else { + return new AjaxDrivenModel(); + } + } + private VarModel parseVar(Element element) { return new VarModel(element.getAttribute("name"), element.getAttribute("class")); } @@ -507,8 +518,8 @@ public class XmlFlowModelBuilder implements FlowModelBuilder { } private BindingModel parseBinding(Element element) { - return new BindingModel(element.getAttribute("property"), element.getAttribute("converter"), element - .getAttribute("required")); + return new BindingModel(element.getAttribute("property"), element.getAttribute("converter"), + element.getAttribute("required")); } private LinkedList parseOnExitActions(Element element) { diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/spring-webflow-2.3.xsd b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/spring-webflow-2.3.xsd new file mode 100644 index 00000000..ed1894dc --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/spring-webflow-2.3.xsd @@ -0,0 +1,1596 @@ + + + + + + +This schema defines Spring Web Flow's XML-based flow definition language. +
+The root "flow" element in this document defines exactly one flow definition. +A flow definition is a blueprint for a carrying out a conversation with a single user. +
+A flow is composed of one or more states that form the steps of the flow. +Each state executes a behavior when entered. What behavior is executed is a +function of the state's type. Core state types include view states, +action states, subflow states, decision states, and end states. +
+A flow definition has exactly one start state. +Events that occur within states drive state transitions. +]]> +
+
+ + + + + + + + +A flow may also exhibit the following characteristics: +

    +
  • Be annotated with attributes that define descriptive properties that may affect flow execution. +(See the <attribute/> element) + +
  • Be secured +(See the <secured/> element) + +
  • Be a persistence context for managing persistent objects during the course of flow execution. +(See the <persistence-context/> element) + +
  • Instantiate a set of instance variables when started. +(See the <var/> element) + +
  • Map input provided by callers that start it +(See the <input/> element) + +
  • Return output to callers that end it. +(See the <output/> element) + +
  • Execute actions at start time and end time. +(See the <on-start/> and <on-end/> elements) + +
  • Define transitions shared by all states. +(See the <global-transitions/> element) + +
  • Handle exceptions thrown by during flow execution. +(See the <exception-handler/> element) + +
  • Import one or more local bean definition files defining custom flow artifacts +(such as actions, exception handlers, view factories, transition criteria, etc). +(See the <bean-import/> element) +
+]]> + + + + + + + + + + + + + + + + + + + + + + +The persistence context can be referenced from within this flow by the "entityManager" variable. +]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + . +If inheriting from a state defined in another flow, the flowId prefix is required. +If inheriting from a state defined in this flow, the flowId should not be specified. +For example,
<action-state id="state" parent="myParentFlow#myParentState">
or
<action-state id="state" parent="#myLocalParentState">
+]]> +
+
+
+
+
+ + + + +Once paused, a view-state may be 'refreshed' by the user. +A refresh causes the response to be reissued and then returns control back to the user. +
+A view state may be configured with one or more render-actions using the 'on-render' element. +Render actions are executed immediately before the view is rendered. +
+A view state is a transitionable state. +A view state transition is triggered by a user event. +]]> +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + . +If inheriting from a state defined in another flow, the flowId prefix is required. +If inheriting from a state defined in this flow, the flowId should not be specified. +For example,
<view-state id="state" parent="myParentFlow#myParentState">
or
<view-state id="state" parent="#myLocalParentState">
+]]> +
+
+
+ + + + + priceForm.jsp + +Can also be an evaluatable expression: +
+	${flowScope.myViewExpression}
+
+The externalRedirect: prefix may be used to redirect to an external location, typically to interface with an external system or controller. +External redirect query parameters may be specified using ${expressions} that evaluate against the request context. +The supported formats for an encoded externalRedirect expression are: +
+	externalRedirect:
+	externalRedirect:contextRelative:
+	externalRedirect:serverRelative:
+	externalRedirect:
+
+For example: +
+	externalRedirect:/hotels/index
+	externalRedirect:http://someOtherSystem?orderId=${order.id}&callbackUrl=${flowExecutionUrl}
+
+The flowRedirect: prefix may be used to redirect to another flow: +
+	flowRedirect:myOtherFlow?someData=${flowScope.data}
+
+For exotic usages, you may plug in a custom ViewFactory bean you define: +
+	${myCustomViewFactory}
+
+When this attribute is not specified, 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. +]]> +
+
+
+ + + + + + + + + + + + + + + + + + + + + +
+
+ + + + +A decision state is a transitionable state. +A decision state transition can be triggered by evaluating a boolean expression against the flow execution request context. +To define transition expressions, use the 'if' element. +
+Examples: +
+A simple boolean expression test, using the convenient 'if' element: +
+    <decision-state id="requiresShipping">
+	    <if test="sale.requiresShipping" then="enterShippingDetails" else="processSale"/>
+    </decision-state>
+
+]]> +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +The form is: +
+	<if test="criteriaExpression" then="trueStateId" else="falseStateId"/>
+
+]]> +
+
+ + + + + + <if test="sale.requiresShipping" then="enterShippingDetails"/> + +]]> + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + . +If inheriting from a state defined in another flow, the flowId prefix is required. +If inheriting from a state defined in this flow, the flowId should not be specified. +For example,
<decision-state id="state" parent="myParentFlow#myParentState">
or
<decision-state id="state" parent="#myLocalParentState">
+]]> +
+
+
+
+
+ + + + +A subflow state is a transitionable state. +A transition is triggered by the subflow outcome that was reached. +]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + . +If inheriting from a state defined in another flow, the flowId prefix is required. +If inheriting from a state defined in this flow, the flowId should not be specified. +For example,
<subflow-state id="state" parent="myParentFlow#myParentState">
or
<subflow-state id="state" parent="#myLocalParentState">
+]]> +
+
+
+ + + + + + + + + + + + + + +
+
+ + + + +An end state is not transitionable; there are never transitions out of an end state. +When an end-state is entered, an instance of this flow is terminated. +
+When this flow terminates, if it was the "root" flow the entire execution is terminated. +If this flow was a subflow, its parent flow resumes. +]]> +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + . +If inheriting from a state defined in another flow, the flowId prefix is required. +If inheriting from a state defined in this flow, the flowId should not be specified. +For example,
<end-state id="state" parent="myParentFlow#myParentState">
or
<end-state id="state" parent="#myLocalParentState">
+]]> +
+
+
+ + + + + priceForm.jsp + +It can also be an evaluatable expression: +
+	${flowScope.myViewExpression}
+
+The externalRedirect: prefix may be used to request a redirect to an external location, typically to interface with an external system or controller. +External redirect query parameters may be specified using ${expressions} that evaluate against the request context. +The supported formats for an encoded externalRedirect expression are: +
+	externalRedirect:
+	externalRedirect:contextRelative:
+	externalRedirect:serverRelative:
+	externalRedirect:
+
+For example: +
+	externalRedirect:/hotels/index
+	externalRedirect:http://someOtherSystem?orderId=${order.id}
+
+The flowRedirect: prefix may be used to redirect to another flow: +
+	flowRedirect:myOtherFlow?someData=${flowScope.data}
+
+For exotic usages, you may plug in a custom ViewFactory bean you define: +
+	${myCustomViewFactory}
+
+
+When this attribute is not specified, no final response will be issued. +In this case, the caller is expected to handle this flow outcome. +]]> +
+
+
+ + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +For example: +
+    <bean-import resource="orderitem-flow-beans.xml"/>
+
+... would look for 'orderitem-flow-beans.xml' in the same directory as this document. +]]> +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Note: Cannot be used in conjunction with an exception based transition. +]]> + + + + + + + + + + <transition on="submit" to="state"/> + +... which reads "on the occurrence of the 'submit' event, transition to 'state'" +
+Sophisticated transitional expressions are also supported when enclosed in a delimited expression: +
+	<transition on="${currentEvent.id == 'submit' &;amp;& flowScope.attribute == 'foo'}" to="state"/>
+
+]]> +
+
+
+ + + + +The value of this attribute must be a fully-qualified java.lang.Exception class name (e.g. example.booking.ItineraryExpiredException). +Superclasses of the configured exception class match by default. Use this attribute or the 'on' attribute, not both. +
+Note: Cannot be used in conjunction with a secured element. +]]> +
+
+
+ + + + +The value of this attribute may be a static state identifier (e.g. to="displayForm") or a dynamic expression (e.g. to="${flowScope.previousViewState}"). +If no value is specified, this transition acts as a simple event handler and will not change the state of the flow. +]]> + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Note: This element configures a meta-attribute. +For the attribute to be enforced, the flow execution must be observed by a SecurityFlowExecutionListener. +]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-webflow/src/main/java/org/springframework/webflow/test/MockRequestControlContext.java b/spring-webflow/src/main/java/org/springframework/webflow/test/MockRequestControlContext.java index 35c1c7ef..9ccfe3e7 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/test/MockRequestControlContext.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/test/MockRequestControlContext.java @@ -125,6 +125,18 @@ public class MockRequestControlContext extends MockRequestContext implements Req return redirectOnPause != null ? redirectOnPause.booleanValue() : false; } + public boolean getRedirectInSameState() { + if (!getExternalContext().isResponseAllowed()) { + return true; + } + Boolean redirectInSameState = getMockFlowExecutionContext().getAttributes().getBoolean("redirectInSameState"); + if (redirectInSameState != null) { + return redirectInSameState.booleanValue(); + } else { + return getRedirectOnPause(); + } + } + // implementation specific accessors for testing public void setAlwaysRedirectOnPause(boolean alwaysRedirectOnPause) { @@ -132,4 +144,9 @@ public class MockRequestControlContext extends MockRequestContext implements Req Boolean.valueOf(alwaysRedirectOnPause)); } + public void setRedirectInSameState(boolean redirectInSameState) { + getMockFlowExecutionContext().getAttributeMap() + .put("redirectInSameState", Boolean.valueOf(redirectInSameState)); + } + } \ No newline at end of file diff --git a/spring-webflow/src/test/java/org/springframework/webflow/config/FlowExecutorBeanDefinitionParserTests.java b/spring-webflow/src/test/java/org/springframework/webflow/config/FlowExecutorBeanDefinitionParserTests.java index 7942af48..c8c0e1d3 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/config/FlowExecutorBeanDefinitionParserTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/config/FlowExecutorBeanDefinitionParserTests.java @@ -40,9 +40,11 @@ public class FlowExecutorBeanDefinitionParserTests extends TestCase { public static class ConfigurationListener extends FlowExecutionListenerAdapter { public void sessionCreating(RequestContext context, FlowDefinition definition) { - assertEquals(3, context.getFlowExecutionContext().getAttributes().size()); + assertEquals(4, context.getFlowExecutionContext().getAttributes().size()); assertEquals(Boolean.FALSE, context.getFlowExecutionContext().getAttributes().getBoolean("alwaysRedirectOnPause")); + assertEquals(Boolean.TRUE, + context.getFlowExecutionContext().getAttributes().getBoolean("redirectInSameState")); assertEquals("bar", context.getFlowExecutionContext().getAttributes().get("foo")); assertEquals(new Integer(2), context.getFlowExecutionContext().getAttributes().get("bar")); } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/config/flow-executor.xml b/spring-webflow/src/test/java/org/springframework/webflow/config/flow-executor.xml index e66f344e..a398e386 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/config/flow-executor.xml +++ b/spring-webflow/src/test/java/org/springframework/webflow/config/flow-executor.xml @@ -12,6 +12,7 @@ + diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/ViewStateTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/ViewStateTests.java index fc442dc8..bd78fbd1 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/engine/ViewStateTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/ViewStateTests.java @@ -297,7 +297,6 @@ public class ViewStateTests extends TestCase { Flow flow = new Flow("myFlow"); StubViewFactory viewFactory = new StubViewFactory(); ViewState state = new ViewState(flow, "viewState", viewFactory); - state.setRedirectInSameState(Boolean.TRUE); Transition t = new Transition(on("submit"), null); state.getTransitionSet().add(t); MockRequestControlContext context = new MockRequestControlContext(flow); @@ -320,7 +319,6 @@ public class ViewStateTests extends TestCase { Flow flow = new Flow("myFlow"); StubViewFactory viewFactory = new StubViewFactory(); ViewState state = new ViewState(flow, "viewState", viewFactory); - state.setRedirectInSameState(Boolean.TRUE); Transition t = new Transition(on("submit"), null); TestAction action = new TestAction(); t.setExecutionCriteria(new ActionTransitionCriteria(action)); @@ -428,6 +426,62 @@ public class ViewStateTests extends TestCase { assertEquals(StubViewFactory.USER_EVENT_STATE, context.getFlashScope().get(View.USER_EVENT_STATE_ATTRIBUTE)); } + public void testRedirectInSameStateOverridesAlwaysRedirectOnPause() { + Flow flow = new Flow("myFlow"); + StubViewFactory viewFactory = new StubViewFactory(); + ViewState state = new ViewState(flow, "viewState", viewFactory); + Transition t = new Transition(on("submit"), null); + state.getTransitionSet().add(t); + MockRequestControlContext context = new MockRequestControlContext(flow); + state.enter(context); + context = new MockRequestControlContext(context.getFlowExecutionContext()); + context.setAlwaysRedirectOnPause(false); + context.setRedirectInSameState(true); + context.getFlowScope().remove("renderCalled"); + context.putRequestParameter("_eventId", "submit"); + state.resume(context); + assertTrue(context.getMockExternalContext().getFlowExecutionRedirectRequested()); + } + + public void testAjaxDrivenAttributeOverridesRedirectInSameState() { + Flow flow = new Flow("myFlow"); + flow.getAttributes().put("ajaxDriven", Boolean.TRUE); + StubViewFactory viewFactory = new StubViewFactory(); + ViewState state = new ViewState(flow, "viewState", viewFactory); + Transition t = new Transition(on("submit"), null); + state.getTransitionSet().add(t); + MockRequestControlContext context = new MockRequestControlContext(flow); + state.enter(context); + context = new MockRequestControlContext(context.getFlowExecutionContext()); + context.getMockExternalContext().setAjaxRequest(true); + context.setAlwaysRedirectOnPause(true); + context.setRedirectInSameState(true); + context.getFlowScope().remove("renderCalled"); + context.putRequestParameter("_eventId", "submit"); + state.resume(context); + assertFalse(context.getMockExternalContext().getFlowExecutionRedirectRequested()); + } + + public void testViewStateRedirectOverridesAjaxDrivenAttribute() { + Flow flow = new Flow("myFlow"); + flow.getAttributes().put("ajaxDriven", Boolean.TRUE); + StubViewFactory viewFactory = new StubViewFactory(); + ViewState state = new ViewState(flow, "viewState", viewFactory); + state.setRedirect(false); + Transition t = new Transition(on("submit"), null); + state.getTransitionSet().add(t); + MockRequestControlContext context = new MockRequestControlContext(flow); + state.enter(context); + context = new MockRequestControlContext(context.getFlowExecutionContext()); + context.getMockExternalContext().setAjaxRequest(true); + context.setAlwaysRedirectOnPause(true); + context.setRedirectInSameState(true); + context.getFlowScope().remove("renderCalled"); + context.putRequestParameter("_eventId", "submit"); + state.resume(context); + assertFalse(context.getMockExternalContext().getFlowExecutionRedirectRequested()); + } + public void testResumeViewStateForEventDestroyVariables() { Flow flow = new Flow("myFlow"); StubViewFactory viewFactory = new StubViewFactory(); diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/model/FlowModelFlowBuilderTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/model/FlowModelFlowBuilderTests.java index f9f63cc0..d211a18b 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/model/FlowModelFlowBuilderTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/model/FlowModelFlowBuilderTests.java @@ -18,6 +18,7 @@ import org.springframework.webflow.engine.ViewState; import org.springframework.webflow.engine.builder.FlowAssembler; import org.springframework.webflow.engine.builder.FlowBuilderException; import org.springframework.webflow.engine.impl.FlowExecutionImplFactory; +import org.springframework.webflow.engine.model.AjaxDrivenModel; import org.springframework.webflow.engine.model.AttributeModel; import org.springframework.webflow.engine.model.EndStateModel; import org.springframework.webflow.engine.model.EvaluateModel; @@ -129,6 +130,14 @@ public class FlowModelFlowBuilderTests extends TestCase { assertTrue(((Boolean) flow.getAttributes().get("persistenceContext")).booleanValue()); } + public void testAjaxDrivenFlow() { + model.setAjaxDriven(new AjaxDrivenModel()); + model.setStates(singleList(new EndStateModel("end"))); + Flow flow = getFlow(model); + assertNotNull(flow.getAttributes().get("ajaxDriven")); + assertTrue(((Boolean) flow.getAttributes().get("ajaxDriven")).booleanValue()); + } + public void testFlowInputOutputMapping() { InputModel input1 = new InputModel("foo", "flowScope.foo"); InputModel input2 = new InputModel("foo", "flowScope.bar"); @@ -190,8 +199,8 @@ public class FlowModelFlowBuilderTests extends TestCase { end.setSecured(new SecuredModel("ROLE_USER")); model.setStates(singleList(end)); Flow flow = getFlow(model); - SecurityRule rule = (SecurityRule) flow.getState("end").getAttributes().get( - SecurityRule.SECURITY_ATTRIBUTE_NAME); + SecurityRule rule = (SecurityRule) flow.getState("end").getAttributes() + .get(SecurityRule.SECURITY_ATTRIBUTE_NAME); assertNotNull(rule); assertEquals(SecurityRule.COMPARISON_ANY, rule.getComparisonType()); assertEquals(1, rule.getAttributes().size()); diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/WebFlowEntityResolverTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/WebFlowEntityResolverTests.java new file mode 100644 index 00000000..e2c49dd4 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/WebFlowEntityResolverTests.java @@ -0,0 +1,32 @@ +package org.springframework.webflow.engine.model.builder.xml; + +import junit.framework.TestCase; + +import org.xml.sax.InputSource; + +public class WebFlowEntityResolverTests extends TestCase { + + private static final String PUBLIC_ID = "http://www.springframework.org/schema/webflow"; + + public void testResolve23() throws Exception { + WebFlowEntityResolver resolver = new WebFlowEntityResolver(); + InputSource source = resolver.resolveEntity(PUBLIC_ID, + "http://www.springframework.org/schema/webflow/spring-webflow-2.3.xsd"); + assertNotNull(source); + } + + public void testResolve20() throws Exception { + WebFlowEntityResolver resolver = new WebFlowEntityResolver(); + InputSource source = resolver.resolveEntity(PUBLIC_ID, + "http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd"); + assertNotNull(source); + } + + public void testResolveLatest() throws Exception { + WebFlowEntityResolver resolver = new WebFlowEntityResolver(); + InputSource source = resolver.resolveEntity(PUBLIC_ID, + "http://www.springframework.org/schema/webflow/spring-webflow.xsd"); + assertNotNull(source); + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/XmlFlowModelBuilderTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/XmlFlowModelBuilderTests.java index f43497da..967b46fb 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/XmlFlowModelBuilderTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/XmlFlowModelBuilderTests.java @@ -13,6 +13,7 @@ import org.springframework.webflow.engine.builder.FlowAssembler; import org.springframework.webflow.engine.builder.model.FlowModelFlowBuilder; import org.springframework.webflow.engine.impl.FlowExecutionImplFactory; import org.springframework.webflow.engine.model.AbstractStateModel; +import org.springframework.webflow.engine.model.AjaxDrivenModel; import org.springframework.webflow.engine.model.AttributeModel; import org.springframework.webflow.engine.model.BindingModel; import org.springframework.webflow.engine.model.ExceptionHandlerModel; @@ -103,6 +104,16 @@ public class XmlFlowModelBuilderTests extends TestCase { assertEquals("ROLE_USER", secured.getAttributes()); } + public void testFlowAjaxDriven() { + ClassPathResource resource = new ClassPathResource("flow-ajax-driven.xml", getClass()); + FlowModelBuilder builder = new XmlFlowModelBuilder(resource, registry); + builder.init(); + builder.build(); + FlowModel flow = builder.getFlowModel(); + AjaxDrivenModel ajaxDriven = flow.getAjaxDriven(); + assertNotNull(ajaxDriven); + } + public void testFlowSecuredState() { ClassPathResource resource = new ClassPathResource("flow-secured-state.xml", getClass()); FlowModelBuilder builder = new XmlFlowModelBuilder(resource, registry); @@ -290,16 +301,16 @@ public class XmlFlowModelBuilderTests extends TestCase { DefaultFlowModelHolder holder = new DefaultFlowModelHolder(builder); FlowModel model = holder.getFlowModel(); assertEquals("foo1", ((ExceptionHandlerModel) model.getExceptionHandlers().get(0)).getBean()); - assertEquals("foo2", ((ExceptionHandlerModel) model.getStateById("state1").getExceptionHandlers().get(0)) - .getBean()); - assertEquals("foo3", ((ExceptionHandlerModel) model.getStateById("state2").getExceptionHandlers().get(0)) - .getBean()); - assertEquals("foo4", ((ExceptionHandlerModel) model.getStateById("state3").getExceptionHandlers().get(0)) - .getBean()); - assertEquals("foo5", ((ExceptionHandlerModel) model.getStateById("state4").getExceptionHandlers().get(0)) - .getBean()); - assertEquals("foo6", ((ExceptionHandlerModel) model.getStateById("state5").getExceptionHandlers().get(0)) - .getBean()); + assertEquals("foo2", + ((ExceptionHandlerModel) model.getStateById("state1").getExceptionHandlers().get(0)).getBean()); + assertEquals("foo3", + ((ExceptionHandlerModel) model.getStateById("state2").getExceptionHandlers().get(0)).getBean()); + assertEquals("foo4", + ((ExceptionHandlerModel) model.getStateById("state3").getExceptionHandlers().get(0)).getBean()); + assertEquals("foo5", + ((ExceptionHandlerModel) model.getStateById("state4").getExceptionHandlers().get(0)).getBean()); + assertEquals("foo6", + ((ExceptionHandlerModel) model.getStateById("state5").getExceptionHandlers().get(0)).getBean()); } public void testFormActionValidatorMethod() { diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-ajax-driven.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-ajax-driven.xml new file mode 100644 index 00000000..85ce29a6 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-ajax-driven.xml @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file