SWF-991, SWF-1413 - Add ajax-driven flow attribute and redirect-in-same-view-state flow execution attribute

This commit is contained in:
Rossen Stoyanchev
2011-01-31 00:32:00 +00:00
parent b0e64468ae
commit 5cb3ef0067
28 changed files with 2009 additions and 122 deletions

View File

@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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. * Custom {@link StateManager} that manages the JSF component state in web flow's view scope.
* *
* @author Jeremy Grelle * @author Jeremy Grelle
* @author Rossen Stoyanchev
*/ */
public class FlowViewStateManager extends StateManager { public class FlowViewStateManager extends StateManager {
@@ -150,31 +151,35 @@ public class FlowViewStateManager extends StateManager {
/** /**
* <p> * <p>
* JSF 1.2 (or higher) version of state saving. * JSF 1.2 (or higher) version of state saving.
* </p>
* *
* <p> * <p>
* In JSF 2 where a partial state saving algorithm is used, this method merely delegates to the next * In JSF 2, if partial state saving is enabled this method delegates in order to obtain the serialized view state.
* ViewStateManager. Thus partial state saving is handled by the JSF 2 runtime. However, a * During rendering JSF calls this method to prepare the state and then calls {@link FlowViewResponseStateManager}
* {@link FlowViewResponseStateManager} plugged in via {@link FlowRenderKit} will ensure the state is saved in a Web * which writes it to Web Flow's view scope.
* Flow view-scoped variable. *
* </p> * <p>
* 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) { public Object saveView(FacesContext context) {
if (context.getViewRoot().isTransient()) { if (context.getViewRoot().isTransient()) {
return null; return null;
} }
FlowSerializedView view = null;
if ((!JsfUtils.isFlowRequest()) || JsfRuntimeInformation.isPartialStateSavingSupported()) { 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 { } else {
RequestContext requestContext = RequestContextHolder.getRequestContext(); view = new FlowSerializedView(context.getViewRoot().getViewId(), getTreeStructureToSave(context),
if (logger.isDebugEnabled()) { getComponentStateToSave(context));
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;
} }
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;
} }
/** /**

View File

@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -88,6 +88,13 @@ public class JsfView implements View {
try { try {
logger.debug("Asking faces lifecycle to render"); logger.debug("Asking faces lifecycle to render");
facesLifecycle.render(facesContext); 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 { } finally {
logger.debug("View rendering complete"); logger.debug("View rendering complete");
facesContext.responseComplete(); 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 // Set the temporary UIViewRoot state so that it will be available across the redirect
return new ViewRootHolder(getViewRoot()); return new ViewRootHolder(getViewRoot());
} else { } else {
// In JSF 2 the partial state saving algorithm attaches a system event listener to the UIViewRoot with // In JSF 2 the partial state saving algorithm attaches a system event listener to the UIViewRoot which
// a reference to the FacesContext instance. The FacesContext instance is released at end of each request. // holds on to a reference to the FacesContext instance. The FacesContext instance is released at end of
// Hence, keeping the UIViewRoot across the redirect is not feasible. // 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."); logger.debug("User event state requested but not saved.");
return null; return null;
} }

View File

@@ -1,12 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<beansProjectDescription> <beansProjectDescription>
<version>1</version> <version>1</version>
<pluginVersion><![CDATA[2.0.5.v200805032123]]></pluginVersion> <pluginVersion><![CDATA[2.5.2.201101081000-RELEASE]]></pluginVersion>
<configSuffixes> <configSuffixes>
<configSuffix><![CDATA[xml]]></configSuffix> <configSuffix><![CDATA[xml]]></configSuffix>
</configSuffixes> </configSuffixes>
<enableImports><![CDATA[true]]></enableImports> <enableImports><![CDATA[true]]></enableImports>
<configs> <configs>
<config>src/main/webapp/WEB-INF/config/data-access-config.xml</config>
<config>src/main/webapp/WEB-INF/config/security-config.xml</config>
<config>src/main/webapp/WEB-INF/config/webflow-config.xml</config>
<config>src/main/webapp/WEB-INF/config/webmvc-config.xml</config>
</configs> </configs>
<configSets> <configSets>
</configSets> </configSets>

View File

@@ -17,7 +17,7 @@
<!-- Spring --> <!-- Spring -->
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId> <artifactId>spring-webmvc</artifactId>
<version>${org.springframework-version}</version> <version>${org.springframework-version}</version>
<exclusions> <exclusions>
<!-- Exclude Commons Logging in favor of SLF4j --> <!-- Exclude Commons Logging in favor of SLF4j -->
@@ -37,11 +37,6 @@
<artifactId>spring-orm</artifactId> <artifactId>spring-orm</artifactId>
<version>${org.springframework-version}</version> <version>${org.springframework-version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.webflow</groupId> <groupId>org.springframework.webflow</groupId>
<artifactId>spring-webflow</artifactId> <artifactId>spring-webflow</artifactId>

View File

@@ -1,12 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow" <flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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"> xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow.xsd">
<secured attributes="ROLE_USER" /> <secured attributes="ROLE_USER" />
<ajax-driven />
<input name="hotelId" required="true" /> <input name="hotelId" required="true" />
<on-start> <on-start>
<evaluate expression="bookingService.createBooking(hotelId, currentUser.name)" result="flowScope.booking" /> <evaluate expression="bookingService.createBooking(hotelId, currentUser.name)" result="flowScope.booking" />
</on-start> </on-start>
@@ -23,11 +25,17 @@
<binding property="creditCardExpiryYear" /> <binding property="creditCardExpiryYear" />
<binding property="amenities" /> <binding property="amenities" />
</binder> </binder>
<on-render>
<render fragments="body" />
</on-render>
<transition on="proceed" to="reviewBooking" /> <transition on="proceed" to="reviewBooking" />
<transition on="cancel" to="cancel" bind="false" /> <transition on="cancel" to="cancel" bind="false" />
</view-state> </view-state>
<view-state id="reviewBooking" model="booking"> <view-state id="reviewBooking" model="booking">
<on-render>
<render fragments="body" />
</on-render>
<transition on="confirm" to="bookingConfirmed"> <transition on="confirm" to="bookingConfirmed">
<evaluate expression="bookingService.persistBooking(booking)" /> <evaluate expression="bookingService.persistBooking(booking)" />
</transition> </transition>
@@ -36,7 +44,7 @@
</view-state> </view-state>
<end-state id="bookingConfirmed"/> <end-state id="bookingConfirmed"/>
<end-state id="cancel" /> <end-state id="cancel" />
</flow> </flow>

View File

@@ -27,7 +27,7 @@
</spring:bind> </spring:bind>
</div> </div>
</spring:hasBindErrors> </spring:hasBindErrors>
<form:form modelAttribute="booking"> <form:form modelAttribute="booking" action="${flowExecutionUrl}">
<fieldset> <fieldset>
<legend>Book Hotel</legend> <legend>Book Hotel</legend>
<div> <div>
@@ -187,7 +187,7 @@
</p> </p>
<script type="text/javascript"> <script type="text/javascript">
Spring.addDecoration(new Spring.ValidateAllDecoration({elementId:'proceed', event:'onclick'})); Spring.addDecoration(new Spring.ValidateAllDecoration({elementId:'proceed', event:'onclick'}));
Spring.addDecoration(new Spring.AjaxEventDecoration({elementId:'proceed',event:'onclick',formId:'booking',params:{fragments:'body'}})); Spring.addDecoration(new Spring.AjaxEventDecoration({elementId:'proceed',event:'onclick',formId:'booking'}));
</script> </script>
</div> </div>
</fieldset> </fieldset>

View File

@@ -15,7 +15,7 @@
</address> </address>
</div> </div>
<div class="span-12 last"> <div class="span-12 last">
<form:form id="confirm" modelAttribute="booking"> <form:form id="confirm" modelAttribute="booking" action="${flowExecutionUrl}">
<fieldset> <fieldset>
<legend>Confirm Booking Details</legend> <legend>Confirm Booking Details</legend>
<div> <div>
@@ -49,9 +49,14 @@
</div> </div>
</div> </div>
<div> <div>
<button type="submit" name="_eventId_confirm">Confirm</button> <p>
<button type="submit" name="_eventId_revise">Revise</button> <button type="submit" name="_eventId_confirm">Confirm</button>
<button type="submit" name="_eventId_cancel">Cancel</button> <button type="submit" name="_eventId_revise" id="revise">Revise</button>
<button type="submit" name="_eventId_cancel">Cancel</button>
</p>
<script type="text/javascript">
Spring.addDecoration(new Spring.AjaxEventDecoration({elementId:'revise',event:'onclick',formId:'confirm'}));
</script>
</div> </div>
</fieldset> </fieldset>
</form:form> </form:form>

View File

@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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<Object> parseFlowExecutionAttributes(Element element) {
Element executionAttributesElement = DomUtils.getChildElementByTagName(element, "flow-execution-attributes"); Element executionAttributesElement = DomUtils.getChildElementByTagName(element, "flow-execution-attributes");
if (executionAttributesElement != null) { if (executionAttributesElement != null) {
HashSet attributes = new HashSet(); HashSet<Object> attributes = new HashSet<Object>();
Element redirectElement = DomUtils.getChildElementByTagName(executionAttributesElement, Element redirectElement = DomUtils.getChildElementByTagName(executionAttributesElement,
"always-redirect-on-pause"); "always-redirect-on-pause");
if (redirectElement != null) { if (redirectElement != null) {
String value = redirectElement.getAttribute("value"); String value = redirectElement.getAttribute("value");
attributes.add(new FlowElementAttribute("alwaysRedirectOnPause", value, "boolean")); 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"); List attributeElements = DomUtils.getChildElementsByTagName(executionAttributesElement, "attribute");
for (Iterator it = attributeElements.iterator(); it.hasNext();) { for (Iterator it = attributeElements.iterator(); it.hasNext();) {
Element attributeElement = (Element) it.next(); Element attributeElement = (Element) it.next();

View File

@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 ALWAYS_REDIRECT_ON_PAUSE = "alwaysRedirectOnPause";
private static final String REDIRECT_IN_SAME_STATE = "redirectInSameState";
private FlowDefinitionLocator flowDefinitionLocator; private FlowDefinitionLocator flowDefinitionLocator;
private Integer maxFlowExecutions; private Integer maxFlowExecutions;
@@ -185,11 +187,12 @@ class FlowExecutorFactoryBean implements FactoryBean, ApplicationContextAware, B
private void putDefaultFlowExecutionAttributes(LocalAttributeMap executionAttributes) { private void putDefaultFlowExecutionAttributes(LocalAttributeMap executionAttributes) {
if (!executionAttributes.contains(ALWAYS_REDIRECT_ON_PAUSE)) { if (!executionAttributes.contains(ALWAYS_REDIRECT_ON_PAUSE)) {
if (environment == MvcEnvironment.PORTLET) { boolean redirect = (environment == MvcEnvironment.PORTLET) ? Boolean.FALSE : Boolean.TRUE;
executionAttributes.put(ALWAYS_REDIRECT_ON_PAUSE, Boolean.FALSE); executionAttributes.put(ALWAYS_REDIRECT_ON_PAUSE, redirect);
} else { }
executionAttributes.put(ALWAYS_REDIRECT_ON_PAUSE, Boolean.TRUE); if (!executionAttributes.contains(REDIRECT_IN_SAME_STATE)) {
} boolean redirect = (environment == MvcEnvironment.PORTLET) ? Boolean.FALSE : Boolean.TRUE;
executionAttributes.put(REDIRECT_IN_SAME_STATE, redirect);
} }
} }

View File

@@ -392,6 +392,16 @@ Example: 'flow1,flow2,flow3'.
<xsd:documentation> <xsd:documentation>
<![CDATA[ <![CDATA[
Determines if flow executions always redirect after they pause. Determines if flow executions always redirect after they pause.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="redirect-in-same-state" type="redirectInSameStateType" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Determines if flow executions redirect after they pause for transitions that remain in the same view state.
This attribute effectively overrides the value of the "always-redirect-on-pause" attribute in same state tranisitions.
]]> ]]>
</xsd:documentation> </xsd:documentation>
</xsd:annotation> </xsd:annotation>
@@ -420,6 +430,19 @@ true = always redirect on pause; false = do not, only redirect when explicitly i
</xsd:attribute> </xsd:attribute>
</xsd:complexType> </xsd:complexType>
<xsd:complexType name="redirectInSameStateType">
<xsd:attribute name="value" type="xsd:boolean" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
true = redirect on pause for transitions that remain in the same view state;
false = do not redirect for transitions that remain in the same view state.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="flowDefinitionAttributesType"> <xsd:complexType name="flowDefinitionAttributesType">
<xsd:sequence> <xsd:sequence>
<xsd:element name="attribute" type="attributeType" minOccurs="0" maxOccurs="unbounded"> <xsd:element name="attribute" type="attributeType" minOccurs="0" maxOccurs="unbounded">

View File

@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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(); 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();
} }

View File

@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 { 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. * The list of actions to be executed before the view is rendered.
*/ */
@@ -61,12 +66,6 @@ public class ViewState extends TransitionableState {
*/ */
private Boolean redirect; 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. * Whether or not the view should render as a popup.
*/ */
@@ -139,22 +138,6 @@ public class ViewState extends TransitionableState {
this.redirect = redirect; 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. * Returns whether this view state should render as a popup.
*/ */
@@ -289,17 +272,29 @@ public class ViewState extends TransitionableState {
private boolean shouldRedirect(RequestControlContext context) { private boolean shouldRedirect(RequestControlContext context) {
if (redirect != null) { if (redirect != null) {
return redirect.booleanValue(); 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) { private boolean shouldRedirectInSameState(RequestControlContext context) {
if (redirectInSameState != null) { if (redirect != null) {
return redirectInSameState.booleanValue(); return redirect.booleanValue();
} else {
return shouldRedirect(context);
} }
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 { private void render(RequestControlContext context, View view) throws ViewRenderingException {
@@ -367,8 +362,8 @@ public class ViewState extends TransitionableState {
protected void appendToString(ToStringCreator creator) { protected void appendToString(ToStringCreator creator) {
super.appendToString(creator); super.appendToString(creator);
creator.append("viewFactory", viewFactory).append("variables", variables).append("redirect", redirect).append( creator.append("viewFactory", viewFactory).append("variables", variables).append("redirect", redirect)
"popup", popup); .append("popup", popup);
} }
} }

View File

@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 viewState = new ViewState(flow, id, viewFactory);
viewState.addVariables(variables); viewState.addVariables(variables);
viewState.setRedirect(redirect); viewState.setRedirect(redirect);
viewState.setRedirectInSameState(Boolean.FALSE);
viewState.setPopup(popup); viewState.setPopup(popup);
viewState.getRenderActionList().addAll(renderActions); viewState.getRenderActionList().addAll(renderActions);
configureCommonProperties(viewState, entryActions, transitions, exceptionHandlers, exitActions, attributes); configureCommonProperties(viewState, entryActions, transitions, exceptionHandlers, exitActions, attributes);

View File

@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.AbstractMappingModel;
import org.springframework.webflow.engine.model.AbstractStateModel; import org.springframework.webflow.engine.model.AbstractStateModel;
import org.springframework.webflow.engine.model.ActionStateModel; 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.AttributeModel;
import org.springframework.webflow.engine.model.BeanImportModel; import org.springframework.webflow.engine.model.BeanImportModel;
import org.springframework.webflow.engine.model.BinderModel; import org.springframework.webflow.engine.model.BinderModel;
@@ -382,6 +383,7 @@ public class FlowModelFlowBuilder extends AbstractFlowBuilder {
MutableAttributeMap flowAttributes = parseMetaAttributes(flow.getAttributes()); MutableAttributeMap flowAttributes = parseMetaAttributes(flow.getAttributes());
parseAndPutPersistenceContext(flow.getPersistenceContext(), flowAttributes); parseAndPutPersistenceContext(flow.getPersistenceContext(), flowAttributes);
parseAndPutSecured(flow.getSecured(), flowAttributes); parseAndPutSecured(flow.getSecured(), flowAttributes);
parseAndPutAjaxDriven(flow.getAjaxDriven(), flowAttributes);
return 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) { private void parseAndPutSecured(SecuredModel secured, MutableAttributeMap attributes) {
if (secured != null) { if (secured != null) {
SecurityRule rule = new SecurityRule(); SecurityRule rule = new SecurityRule();

View File

@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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; 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() { public String toString() {
return new ToStringCreator(this).append("externalContext", externalContext) return new ToStringCreator(this).append("externalContext", externalContext)
.append("currentEvent", currentEvent).append("requestScope", requestScope).append("attributes", .append("currentEvent", currentEvent).append("requestScope", requestScope)
attributes).append("messageContext", messageContext).append("flowExecution", flowExecution) .append("attributes", attributes).append("messageContext", messageContext)
.toString(); .append("flowExecution", flowExecution).toString();
} }
} }

View File

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

View File

@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 PersistenceContextModel persistenceContext;
private AjaxDrivenModel ajaxDriven;
private LinkedList vars; private LinkedList vars;
private LinkedList inputs; private LinkedList inputs;
@@ -224,6 +226,20 @@ public class FlowModel extends AbstractModel {
this.persistenceContext = persistenceContext; 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 * @return the vars
*/ */

View File

@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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; 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.
* <p> * <p>
* The xmlns of the XSD expected to be resolved: * The xmlns of the XSD expected to be resolved:
* *
@@ -33,7 +33,7 @@ import org.xml.sax.SAXException;
* &lt;flow xmlns=&quot;http://www.springframework.org/schema/webflow&quot; * &lt;flow xmlns=&quot;http://www.springframework.org/schema/webflow&quot;
* xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; * xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
* xsi:schemaLocation=&quot;http://www.springframework.org/schema/webflow * xsi:schemaLocation=&quot;http://www.springframework.org/schema/webflow
* http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd&quot;&gt; * http://www.springframework.org/schema/webflow/spring-webflow.xsd&quot;&gt;
* </pre> * </pre>
* *
* @author Erwin Vervaet * @author Erwin Vervaet
@@ -41,22 +41,32 @@ import org.xml.sax.SAXException;
*/ */
class WebFlowEntityResolver implements EntityResolver { 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 { public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
if (systemId != null && systemId.indexOf(WEBFLOW_ELEMENT) > systemId.lastIndexOf("/")) { if (systemId != null && systemId.indexOf("spring-webflow.xsd") > -1) {
String filename = systemId.substring(systemId.indexOf(WEBFLOW_ELEMENT)); return createInputSource(publicId, systemId, WEBFLOW_VERSIONS[0] + ".xsd");
try { }
Resource resource = new ClassPathResource(filename, getClass()); for (int i = 0; i < WEBFLOW_VERSIONS.length; i++) {
InputSource source = new InputSource(resource.getInputStream()); if (systemId != null && systemId.indexOf(WEBFLOW_VERSIONS[i]) > systemId.lastIndexOf("/")) {
source.setPublicId(publicId); String fileName = systemId.substring(systemId.indexOf(WEBFLOW_VERSIONS[i]));
source.setSystemId(systemId); return createInputSource(publicId, systemId, fileName);
return source;
} catch (IOException ex) {
// fall through below
} }
} }
// let the parser handle it // let the parser handle it
return null; return null;
} }
}
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;
}
}

View File

@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.AbstractActionModel;
import org.springframework.webflow.engine.model.AbstractStateModel; import org.springframework.webflow.engine.model.AbstractStateModel;
import org.springframework.webflow.engine.model.ActionStateModel; 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.AttributeModel;
import org.springframework.webflow.engine.model.BeanImportModel; import org.springframework.webflow.engine.model.BeanImportModel;
import org.springframework.webflow.engine.model.BinderModel; import org.springframework.webflow.engine.model.BinderModel;
@@ -201,6 +202,7 @@ public class XmlFlowModelBuilder implements FlowModelBuilder {
flow.setAttributes(parseAttributes(element)); flow.setAttributes(parseAttributes(element));
flow.setSecured(parseSecured(element)); flow.setSecured(parseSecured(element));
flow.setPersistenceContext(parsePersistenceContext(element)); flow.setPersistenceContext(parsePersistenceContext(element));
flow.setAjaxDriven(parseAjaxDriven(element));
flow.setVars(parseVars(element)); flow.setVars(parseVars(element));
flow.setInputs(parseInputs(element)); flow.setInputs(parseInputs(element));
flow.setOnStartActions(parseOnStartActions(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) { private VarModel parseVar(Element element) {
return new VarModel(element.getAttribute("name"), element.getAttribute("class")); return new VarModel(element.getAttribute("name"), element.getAttribute("class"));
} }
@@ -507,8 +518,8 @@ public class XmlFlowModelBuilder implements FlowModelBuilder {
} }
private BindingModel parseBinding(Element element) { private BindingModel parseBinding(Element element) {
return new BindingModel(element.getAttribute("property"), element.getAttribute("converter"), element return new BindingModel(element.getAttribute("property"), element.getAttribute("converter"),
.getAttribute("required")); element.getAttribute("required"));
} }
private LinkedList parseOnExitActions(Element element) { private LinkedList parseOnExitActions(Element element) {

View File

@@ -125,6 +125,18 @@ public class MockRequestControlContext extends MockRequestContext implements Req
return redirectOnPause != null ? redirectOnPause.booleanValue() : false; 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 // implementation specific accessors for testing
public void setAlwaysRedirectOnPause(boolean alwaysRedirectOnPause) { public void setAlwaysRedirectOnPause(boolean alwaysRedirectOnPause) {
@@ -132,4 +144,9 @@ public class MockRequestControlContext extends MockRequestContext implements Req
Boolean.valueOf(alwaysRedirectOnPause)); Boolean.valueOf(alwaysRedirectOnPause));
} }
public void setRedirectInSameState(boolean redirectInSameState) {
getMockFlowExecutionContext().getAttributeMap()
.put("redirectInSameState", Boolean.valueOf(redirectInSameState));
}
} }

View File

@@ -40,9 +40,11 @@ public class FlowExecutorBeanDefinitionParserTests extends TestCase {
public static class ConfigurationListener extends FlowExecutionListenerAdapter { public static class ConfigurationListener extends FlowExecutionListenerAdapter {
public void sessionCreating(RequestContext context, FlowDefinition definition) { public void sessionCreating(RequestContext context, FlowDefinition definition) {
assertEquals(3, context.getFlowExecutionContext().getAttributes().size()); assertEquals(4, context.getFlowExecutionContext().getAttributes().size());
assertEquals(Boolean.FALSE, assertEquals(Boolean.FALSE,
context.getFlowExecutionContext().getAttributes().getBoolean("alwaysRedirectOnPause")); context.getFlowExecutionContext().getAttributes().getBoolean("alwaysRedirectOnPause"));
assertEquals(Boolean.TRUE,
context.getFlowExecutionContext().getAttributes().getBoolean("redirectInSameState"));
assertEquals("bar", context.getFlowExecutionContext().getAttributes().get("foo")); assertEquals("bar", context.getFlowExecutionContext().getAttributes().get("foo"));
assertEquals(new Integer(2), context.getFlowExecutionContext().getAttributes().get("bar")); assertEquals(new Integer(2), context.getFlowExecutionContext().getAttributes().get("bar"));
} }

View File

@@ -12,6 +12,7 @@
<webflow:flow-execution-repository max-executions="1" max-execution-snapshots="2" conversation-manager="conversationManager" /> <webflow:flow-execution-repository max-executions="1" max-execution-snapshots="2" conversation-manager="conversationManager" />
<webflow:flow-execution-attributes> <webflow:flow-execution-attributes>
<webflow:always-redirect-on-pause value="false"/> <webflow:always-redirect-on-pause value="false"/>
<webflow:redirect-in-same-state value="true"/>
<webflow:attribute name="foo" value="bar"/> <webflow:attribute name="foo" value="bar"/>
<webflow:attribute name="bar" value="2" type="integer"/> <webflow:attribute name="bar" value="2" type="integer"/>
</webflow:flow-execution-attributes> </webflow:flow-execution-attributes>

View File

@@ -297,7 +297,6 @@ public class ViewStateTests extends TestCase {
Flow flow = new Flow("myFlow"); Flow flow = new Flow("myFlow");
StubViewFactory viewFactory = new StubViewFactory(); StubViewFactory viewFactory = new StubViewFactory();
ViewState state = new ViewState(flow, "viewState", viewFactory); ViewState state = new ViewState(flow, "viewState", viewFactory);
state.setRedirectInSameState(Boolean.TRUE);
Transition t = new Transition(on("submit"), null); Transition t = new Transition(on("submit"), null);
state.getTransitionSet().add(t); state.getTransitionSet().add(t);
MockRequestControlContext context = new MockRequestControlContext(flow); MockRequestControlContext context = new MockRequestControlContext(flow);
@@ -320,7 +319,6 @@ public class ViewStateTests extends TestCase {
Flow flow = new Flow("myFlow"); Flow flow = new Flow("myFlow");
StubViewFactory viewFactory = new StubViewFactory(); StubViewFactory viewFactory = new StubViewFactory();
ViewState state = new ViewState(flow, "viewState", viewFactory); ViewState state = new ViewState(flow, "viewState", viewFactory);
state.setRedirectInSameState(Boolean.TRUE);
Transition t = new Transition(on("submit"), null); Transition t = new Transition(on("submit"), null);
TestAction action = new TestAction(); TestAction action = new TestAction();
t.setExecutionCriteria(new ActionTransitionCriteria(action)); 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)); 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() { public void testResumeViewStateForEventDestroyVariables() {
Flow flow = new Flow("myFlow"); Flow flow = new Flow("myFlow");
StubViewFactory viewFactory = new StubViewFactory(); StubViewFactory viewFactory = new StubViewFactory();

View File

@@ -18,6 +18,7 @@ import org.springframework.webflow.engine.ViewState;
import org.springframework.webflow.engine.builder.FlowAssembler; import org.springframework.webflow.engine.builder.FlowAssembler;
import org.springframework.webflow.engine.builder.FlowBuilderException; import org.springframework.webflow.engine.builder.FlowBuilderException;
import org.springframework.webflow.engine.impl.FlowExecutionImplFactory; 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.AttributeModel;
import org.springframework.webflow.engine.model.EndStateModel; import org.springframework.webflow.engine.model.EndStateModel;
import org.springframework.webflow.engine.model.EvaluateModel; import org.springframework.webflow.engine.model.EvaluateModel;
@@ -129,6 +130,14 @@ public class FlowModelFlowBuilderTests extends TestCase {
assertTrue(((Boolean) flow.getAttributes().get("persistenceContext")).booleanValue()); 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() { public void testFlowInputOutputMapping() {
InputModel input1 = new InputModel("foo", "flowScope.foo"); InputModel input1 = new InputModel("foo", "flowScope.foo");
InputModel input2 = new InputModel("foo", "flowScope.bar"); InputModel input2 = new InputModel("foo", "flowScope.bar");
@@ -190,8 +199,8 @@ public class FlowModelFlowBuilderTests extends TestCase {
end.setSecured(new SecuredModel("ROLE_USER")); end.setSecured(new SecuredModel("ROLE_USER"));
model.setStates(singleList(end)); model.setStates(singleList(end));
Flow flow = getFlow(model); Flow flow = getFlow(model);
SecurityRule rule = (SecurityRule) flow.getState("end").getAttributes().get( SecurityRule rule = (SecurityRule) flow.getState("end").getAttributes()
SecurityRule.SECURITY_ATTRIBUTE_NAME); .get(SecurityRule.SECURITY_ATTRIBUTE_NAME);
assertNotNull(rule); assertNotNull(rule);
assertEquals(SecurityRule.COMPARISON_ANY, rule.getComparisonType()); assertEquals(SecurityRule.COMPARISON_ANY, rule.getComparisonType());
assertEquals(1, rule.getAttributes().size()); assertEquals(1, rule.getAttributes().size());

View File

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

View File

@@ -13,6 +13,7 @@ import org.springframework.webflow.engine.builder.FlowAssembler;
import org.springframework.webflow.engine.builder.model.FlowModelFlowBuilder; import org.springframework.webflow.engine.builder.model.FlowModelFlowBuilder;
import org.springframework.webflow.engine.impl.FlowExecutionImplFactory; import org.springframework.webflow.engine.impl.FlowExecutionImplFactory;
import org.springframework.webflow.engine.model.AbstractStateModel; 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.AttributeModel;
import org.springframework.webflow.engine.model.BindingModel; import org.springframework.webflow.engine.model.BindingModel;
import org.springframework.webflow.engine.model.ExceptionHandlerModel; import org.springframework.webflow.engine.model.ExceptionHandlerModel;
@@ -103,6 +104,16 @@ public class XmlFlowModelBuilderTests extends TestCase {
assertEquals("ROLE_USER", secured.getAttributes()); 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() { public void testFlowSecuredState() {
ClassPathResource resource = new ClassPathResource("flow-secured-state.xml", getClass()); ClassPathResource resource = new ClassPathResource("flow-secured-state.xml", getClass());
FlowModelBuilder builder = new XmlFlowModelBuilder(resource, registry); FlowModelBuilder builder = new XmlFlowModelBuilder(resource, registry);
@@ -290,16 +301,16 @@ public class XmlFlowModelBuilderTests extends TestCase {
DefaultFlowModelHolder holder = new DefaultFlowModelHolder(builder); DefaultFlowModelHolder holder = new DefaultFlowModelHolder(builder);
FlowModel model = holder.getFlowModel(); FlowModel model = holder.getFlowModel();
assertEquals("foo1", ((ExceptionHandlerModel) model.getExceptionHandlers().get(0)).getBean()); assertEquals("foo1", ((ExceptionHandlerModel) model.getExceptionHandlers().get(0)).getBean());
assertEquals("foo2", ((ExceptionHandlerModel) model.getStateById("state1").getExceptionHandlers().get(0)) assertEquals("foo2",
.getBean()); ((ExceptionHandlerModel) model.getStateById("state1").getExceptionHandlers().get(0)).getBean());
assertEquals("foo3", ((ExceptionHandlerModel) model.getStateById("state2").getExceptionHandlers().get(0)) assertEquals("foo3",
.getBean()); ((ExceptionHandlerModel) model.getStateById("state2").getExceptionHandlers().get(0)).getBean());
assertEquals("foo4", ((ExceptionHandlerModel) model.getStateById("state3").getExceptionHandlers().get(0)) assertEquals("foo4",
.getBean()); ((ExceptionHandlerModel) model.getStateById("state3").getExceptionHandlers().get(0)).getBean());
assertEquals("foo5", ((ExceptionHandlerModel) model.getStateById("state4").getExceptionHandlers().get(0)) assertEquals("foo5",
.getBean()); ((ExceptionHandlerModel) model.getStateById("state4").getExceptionHandlers().get(0)).getBean());
assertEquals("foo6", ((ExceptionHandlerModel) model.getStateById("state5").getExceptionHandlers().get(0)) assertEquals("foo6",
.getBean()); ((ExceptionHandlerModel) model.getStateById("state5").getExceptionHandlers().get(0)).getBean());
} }
public void testFormActionValidatorMethod() { public void testFormActionValidatorMethod() {

View File

@@ -0,0 +1,9 @@
<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.3.xsd">
<ajax-driven/>
<end-state id="end"/>
</flow>