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");
* 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 {
/**
* <p>
* JSF 1.2 (or higher) version of state saving.
* </p>
*
* <p>
* 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.
* </p>
* 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.
*
* <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) {
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;
}
/**

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

View File

@@ -1,12 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<beansProjectDescription>
<version>1</version>
<pluginVersion><![CDATA[2.0.5.v200805032123]]></pluginVersion>
<pluginVersion><![CDATA[2.5.2.201101081000-RELEASE]]></pluginVersion>
<configSuffixes>
<configSuffix><![CDATA[xml]]></configSuffix>
</configSuffixes>
<enableImports><![CDATA[true]]></enableImports>
<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>
<configSets>
</configSets>

View File

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

View File

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

View File

@@ -27,7 +27,7 @@
</spring:bind>
</div>
</spring:hasBindErrors>
<form:form modelAttribute="booking">
<form:form modelAttribute="booking" action="${flowExecutionUrl}">
<fieldset>
<legend>Book Hotel</legend>
<div>
@@ -187,7 +187,7 @@
</p>
<script type="text/javascript">
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>
</div>
</fieldset>

View File

@@ -15,7 +15,7 @@
</address>
</div>
<div class="span-12 last">
<form:form id="confirm" modelAttribute="booking">
<form:form id="confirm" modelAttribute="booking" action="${flowExecutionUrl}">
<fieldset>
<legend>Confirm Booking Details</legend>
<div>
@@ -49,9 +49,14 @@
</div>
</div>
<div>
<button type="submit" name="_eventId_confirm">Confirm</button>
<button type="submit" name="_eventId_revise">Revise</button>
<button type="submit" name="_eventId_cancel">Cancel</button>
<p>
<button type="submit" name="_eventId_confirm">Confirm</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>
</fieldset>
</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");
* 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");
if (executionAttributesElement != null) {
HashSet attributes = new HashSet();
HashSet<Object> attributes = new HashSet<Object>();
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();

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

View File

@@ -392,6 +392,16 @@ Example: 'flow1,flow2,flow3'.
<xsd:documentation>
<![CDATA[
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:annotation>
@@ -420,6 +430,19 @@ true = always redirect on pause; false = do not, only redirect when explicitly i
</xsd:attribute>
</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:sequence>
<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");
* 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();
}

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

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");
* 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);

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");
* 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();

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

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");
* 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
*/

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");
* 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.
* <p>
* 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;
* xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
* 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>
*
* @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;
}
}
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");
* 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) {

View File

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

View File

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

View File

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

View File

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

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.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());

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

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>