delegating flow resolver

This commit is contained in:
Keith Donald
2007-04-02 23:47:46 +00:00
parent 120095c27e
commit 4eeb09618f
10 changed files with 137 additions and 74 deletions

View File

@@ -1,33 +1,22 @@
<?xml version="1.0"?>
<!DOCTYPE faces-config PUBLIC
"-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"
"http://java.sun.com/dtd/web-facesconfig_1_0.dtd">
<faces-config>
<application>
<!-- Navigation handler proxy for a Spring-managed bean that is the Web Flow Navigation Handler -->
<navigation-handler>
org.springframework.web.jsf.DelegatingNavigationHandlerProxy
org.springframework.webflow.executor.jsf.FlowNavigationHandler
</navigation-handler>
<variable-resolver>
org.springframework.webflow.executor.jsf.FlowExecutionVariableResolver
</variable-resolver>
<property-resolver>
org.springframework.webflow.executor.jsf.FlowPropertyResolver
org.springframework.webflow.executor.jsf.FlowExecutionPropertyResolver
</property-resolver>
<variable-resolver>
org.springframework.webflow.executor.jsf.FlowVariableResolver
</variable-resolver>
<variable-resolver>
org.springframework.web.jsf.DelegatingVariableResolver
</variable-resolver>
<!-- Extended "webApplicationContext" resolver -->
<variable-resolver>
org.springframework.web.jsf.WebApplicationContextVariableResolver
</variable-resolver>
</application>
<lifecycle>
<!-- Multi-caster that broadcasts phase events to all PhaseListeners managed by Spring -->
<phase-listener>org.springframework.web.jsf.DelegatingPhaseListenerMulticaster</phase-listener>
</lifecycle>
<lifecycle>
<phase-listener>org.springframework.webflow.executor.jsf.FlowPhaseListener</phase-listener>
</lifecycle>
</faces-config>

View File

@@ -3,7 +3,9 @@
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-1.0.xsd">
<var name="sale" class="org.springframework.webflow.samples.sellitem.Sale"/>
<start-state idref="enterPriceAndItemCount" />
<view-state id="enterPriceAndItemCount" view="/priceAndItemCountForm.jsp">

View File

@@ -16,25 +16,4 @@
<flow:location path="/WEB-INF/flows/sellitem-flow.xml" />
</flow:registry>
<!-- A "Sale" managed bean created by JSF for each 'sellitem' flow execution -->
<bean name="sale" class="org.springframework.webflow.samples.sellitem.Sale" scope="prototype" />
<!--
Spring configured flow navigation handler delegate, allowing for custom configuration
using standard dependency injection techniques.
Note: this definition is optional; you may choose to simply specify your FlowNavigationHandler
in your faces-config.xml if its defaults meet your needs.
-->
<bean id="jsfNavigationHandler" class="org.springframework.webflow.executor.jsf.FlowNavigationHandler" />
<!--
Spring configured flow phase listener delegate, allowing for custom configuration using
standard dependency injection techniques.
Note: this definition is optional; you may choose to simply specify your FlowPhaseListener
in your faces-config.xml if its defaults meet your needs.
-->
<bean id="flowPhaseListener" class="org.springframework.webflow.executor.jsf.FlowPhaseListener" />
</beans>

View File

@@ -21,17 +21,17 @@
<h2>Select category</h2>
<table>
<tr class="readOnly">
<td>Price:</td><td><h:outputText value="#{flowScope.sale.price}"/></td>
<td>Price:</td><td><h:outputText value="#{sale.price} "/></td>
</tr>
<tr class="readOnly">
<td>Item count:</td><td><h:outputText value="#{flowScope.sale.itemCount}"/></td>
<td>Item count:</td><td><h:outputText value="#{sale.itemCount}"/></td>
</tr>
<h:form id="categoryForm">
<tr>
<td>Category:</td>
<td>
<h:selectOneMenu value="#{flowScope.sale.category}">
<h:selectOneMenu value="#{sale.category}">
<f:selectItem itemLabel="None (0.02 discount rate)" itemValue=""/>
<f:selectItem itemLabel="Cat. A (0.1 discount rate when more than 100 items)" itemValue="A"/>
<f:selectItem itemLabel="Cat. B (0.2 discount rate when more than 200 items)" itemValue="B"/>
@@ -41,12 +41,11 @@
<tr>
<td>Is shipping required?:</td>
<td>
<h:selectBooleanCheckbox value="#{flowScope.sale.shipping}"/>
<h:selectBooleanCheckbox value="#{sale.shipping}"/>
</td>
</tr>
<tr>
<td colspan="2" class="buttonBar">
<input type="hidden" name="_flowExecutionKey" value="${flowExecutionKey}"/>
<h:commandButton type="submit" value="Next" action="submit" immediate="false" />
</td>
</tr>

View File

@@ -18,7 +18,6 @@
<f:view>
<div id="content">
<div id="insert"><img src="images/webflow-logo.jpg" /></div>
<h2>Enter price and item count</h2>
<hr>
<table>
@@ -26,7 +25,7 @@
<tr>
<td>Price:</td>
<td>
<h:inputText id="price" value="#{flowScope.sale.price}" required="true">
<h:inputText id="price" value="#{sale.price}" required="true">
<f:validateDoubleRange minimum="0.01"/>
</h:inputText>
</td>
@@ -37,7 +36,7 @@
<tr>
<td>Item count:</td>
<td>
<h:inputText id="itemCount" value="#{flowScope.sale.itemCount}" required="true">
<h:inputText id="itemCount" value="#{sale.itemCount}" required="true">
<f:validateLongRange minimum="1"/>
</h:inputText>
</td>
@@ -47,7 +46,6 @@
</tr>
<tr>
<td colspan="2" class="buttonBar">
<input type="hidden" name="_flowExecutionKey" value="${flowExecutionKey}"/>
<h:commandButton type="submit" value="Next" action="submit" immediate="false" />
</td>
<td></td>

View File

@@ -22,22 +22,22 @@
<hr>
<table>
<tr class="readOnly">
<td>Price:</td><td><h:outputText value="#{flowScope.sale.price}"/></td>
<td>Price:</td><td><h:outputText value="#{sale.price}"/></td>
</tr>
<tr class="readOnly">
<td>Item count:</td><td><h:outputText value="#{flowScope.sale.itemCount}"/></td>
<td>Item count:</td><td><h:outputText value="#{sale.itemCount}"/></td>
</tr>
<tr class="readOnly">
<td>Category:</td><td><h:outputText value="#{flowScope.sale.category}"/></td>
<td>Category:</td><td><h:outputText value="#{sale.category}"/></td>
<tr class="readOnly">
<td>Shipping:</td><td><h:outputText value="#{flowScope.sale.shipping}"/></td>
<td>Shipping:</td><td><h:outputText value="#{sale.shipping}"/></td>
</tr>
<h:form id="shippingForm">
<tr>
<td>Shipping type:</td>
<td>
<h:selectOneMenu value="#{flowScope.sale.shippingType}">
<h:selectOneMenu value="#{sale.shippingType}">
<f:selectItem itemLabel="Standard (10 extra cost)" itemValue="S"/>
<f:selectItem itemLabel="Express (20 extra cost)" itemValue="E"/>
</h:selectOneMenu>
@@ -45,7 +45,6 @@
</tr>
<tr>
<td colspan="2" class="buttonBar">
<input type="hidden" name="_flowExecutionKey" value="${flowExecutionKey}"/>
<h:commandButton type="submit" value="Next" action="submit" immediate="false" />
</td>
</tr>

View File

@@ -0,0 +1,81 @@
/*
* Copyright 2004-2007 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.executor.jsf;
import javax.faces.context.FacesContext;
import javax.faces.el.EvaluationException;
import javax.faces.el.VariableResolver;
import org.springframework.webflow.execution.FlowExecution;
/**
* Custom variable resolver that searches the current flow execution for variables to resolve.
* The search algorithm looks in flash scope first, then flow scope, then conversation scope.
* If no variable is found, this resolver delegates to the next resolver in the chain.
*
* Suitable for use along side other variable resolvers to support EL binding expressions like
* {#bean.property} where "bean" could be a property in any supported scope.
*
* @author Keith Donald
*/
public class DelegatingFlowVariableResolver extends VariableResolver {
/**
* The standard variable resolver to delegate to if this one doesn't apply.
*/
private VariableResolver resolverDelegate;
/**
* Create a new FlowExecutionVariableResolver, using the given original VariableResolver.
* <p>
* A JSF implementation will automatically pass its original resolver into the constructor of a configured resolver,
* provided that there is a corresponding constructor argument.
*
* @param resolverDelegate the original VariableResolver
*/
public DelegatingFlowVariableResolver(VariableResolver resolverDelegate) {
this.resolverDelegate = resolverDelegate;
}
/**
* Return the original VariableResolver that this resolver delegates to.
*/
protected final VariableResolver getResolverDelegate() {
return resolverDelegate;
}
/**
* Check flash/flow/conversation scope. If the variable is not found, delegate to next variable resolver in the
* chain.
*/
public Object resolveVariable(FacesContext context, String name) throws EvaluationException {
FlowExecution execution = FlowExecutionHolderUtils.getRequiredCurrentFlowExecution(context);
// try flash/flow/conversation
if (execution.getActiveSession().getFlashMap().contains(name)) {
return execution.getActiveSession().getFlashMap().get(name);
}
else if (execution.getActiveSession().getScope().contains(name)) {
return execution.getActiveSession().getScope().get(name);
}
else if (execution.getConversationScope().contains(name)) {
return execution.getConversationScope().get(name);
}
else {
// neither found a value, delegate to next resolver in the chain
return resolverDelegate.resolveVariable(context, name);
}
}
}

View File

@@ -17,13 +17,15 @@ package org.springframework.webflow.executor.jsf;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.el.EvaluationException;
import org.springframework.webflow.execution.FlowExecution;
/**
* A static utility class for accessing the current flow execution holder.
* <p>
* By default, the current flow execution holder is stored associated with the
* current thread in the {@link FacesContext}'s
* {@link ExternalContext#getRequestMap()}.
* By default, the current flow execution holder is stored associated with the current thread in the
* {@link FacesContext}'s {@link ExternalContext#getRequestMap()}.
*
* @author Keith Donald
* @author Craig McClanahan
@@ -36,7 +38,24 @@ public class FlowExecutionHolderUtils {
* @return the flow execution holder, or <code>null</code> if none set.
*/
public static FlowExecutionHolder getFlowExecutionHolder(FacesContext context) {
return (FlowExecutionHolder)context.getExternalContext().getRequestMap().get(getFlowExecutionHolderKey());
return (FlowExecutionHolder) context.getExternalContext().getRequestMap().get(getFlowExecutionHolderKey());
}
/**
* Returns the current flow execution holder for the given faces context.
* @param context faces context
* @return the flow execution holder or <code>null</code> if none set
* @throws EvaluationException if no flow execution was bound
*/
public static FlowExecution getRequiredCurrentFlowExecution(FacesContext context) throws EvaluationException {
FlowExecutionHolder holder = getFlowExecutionHolder(context);
if (holder != null) {
return holder.getFlowExecution();
}
else {
throw new EvaluationException(
"FlowExecution is not bound to current thread context - has the flow ended or expired?");
}
}
/**
@@ -48,11 +67,17 @@ public class FlowExecutionHolderUtils {
context.getExternalContext().getRequestMap().put(getFlowExecutionHolderKey(), holder);
}
/**
* Returns true if the flow execution has been restored in the current thread.
* @param context the faces context
* @return true if restored, false otherwise
*/
public static boolean isFlowExecutionRestored(FacesContext context) {
return getFlowExecutionHolder(context) != null;
}
private static String getFlowExecutionHolderKey() {
return FlowExecutionHolder.class.getName();
}
public static boolean isFlowExecutionRestored(FacesContext context) {
return getFlowExecutionHolder(context) != null;
}
}

View File

@@ -72,12 +72,7 @@ public class FlowExecutionVariableResolver extends VariableResolver {
return resolverDelegate.resolveVariable(context, name);
}
else {
FlowExecutionHolder holder = FlowExecutionHolderUtils.getFlowExecutionHolder(context);
if (holder == null)
throw new EvaluationException("'" + FLOW_EXECUTION_VARIABLE_NAME
+ "' variable prefix specified but a FlowExecution is not bound to current thread context: "
+ "has the flow ended or expired?");
return holder.getFlowExecution();
return FlowExecutionHolderUtils.getRequiredCurrentFlowExecution(context);
}
}
}

View File

@@ -68,11 +68,7 @@ public class FlowVariableResolver extends VariableResolver {
return resolverDelegate.resolveVariable(context, name);
}
else {
FlowExecutionHolder holder = FlowExecutionHolderUtils.getFlowExecutionHolder(context);
if (holder == null)
throw new EvaluationException(
"'flowScope' variable prefix specified but a FlowExecution is not bound to current thread context - has the flow ended or expired?");
return holder.getFlowExecution();
return FlowExecutionHolderUtils.getRequiredCurrentFlowExecution(context);
}
}
}