Added Spring scoping support (SWF-163). Also updated the sellitem-jsf sample to use the new scoping scheme.

This commit is contained in:
Ben Hale
2007-05-02 20:42:22 +00:00
parent c9ec04eecc
commit 4a947948d2
22 changed files with 1162 additions and 5 deletions

View File

@@ -6,7 +6,7 @@
<faces-config>
<application>
<navigation-handler>org.springframework.webflow.executor.jsf.FlowNavigationHandler</navigation-handler>
<variable-resolver>org.springframework.webflow.executor.jsf.DelegatingFlowVariableResolver</variable-resolver>
<variable-resolver>org.springframework.web.jsf.DelegatingVariableResolver</variable-resolver>
</application>
<lifecycle>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:flow="http://www.springframework.org/schema/webflow-config"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/webflow-config
http://www.springframework.org/schema/webflow-config/spring-webflow-config-1.1.xsd">
<flow:enable-scopes/>
<bean id="sale" class="org.springframework.webflow.samples.sellitem.Sale" scope="conversation"/>
</beans>

View File

@@ -3,8 +3,6 @@
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" scope="conversation"/>
<start-state idref="enterPriceAndItemCount" />
@@ -33,6 +31,6 @@
<transition on="success" to="showCostOverview" />
</action-state>
<end-state id="showCostOverview" view="/costOverview.jsp" />
<end-state id="showCostOverview" view="/costOverview.jsp" />
</flow>

View File

@@ -9,6 +9,7 @@
<param-value>
classpath:org/springframework/webflow/samples/sellitem/services-config.xml
/WEB-INF/webflow-config.xml
/WEB-INF/flows/sellitem-beans.xml
</param-value>
</context-param>

View File

@@ -1 +1,2 @@
http\://www.springframework.org/schema/webflow-config/spring-webflow-config-1.0.xsd=org/springframework/webflow/config/spring-webflow-config-1.0.xsd
http\://www.springframework.org/schema/webflow-config/spring-webflow-config-1.0.xsd=org/springframework/webflow/config/spring-webflow-config-1.0.xsd
http\://www.springframework.org/schema/webflow-config/spring-webflow-config-1.1.xsd=org/springframework/webflow/config/spring-webflow-config-1.1.xsd

View File

@@ -0,0 +1,39 @@
/*
* 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.config;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.webflow.context.scope.ScopeRegistrar;
import org.w3c.dom.Element;
/**
* {@link BeanDefinitionParser} for the <code>&lt;enable-scopes&gt;</code> tag.
*
* @author Ben Hale
* @since 1.1
*/
class EnableScopesBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
protected Class getBeanClass(Element element) {
return ScopeRegistrar.class;
}
protected boolean shouldGenerateId() {
return true;
}
}

View File

@@ -65,5 +65,6 @@ public class WebFlowConfigNamespaceHandler extends NamespaceHandlerSupport {
registerBeanDefinitionParser("execution-listeners", new ExecutionListenersBeanDefinitionParser());
registerBeanDefinitionParser("executor", new ExecutorBeanDefinitionParser());
registerBeanDefinitionParser("registry", new RegistryBeanDefinitionParser());
registerBeanDefinitionParser("enable-scopes", new EnableScopesBeanDefinitionParser());
}
}

View File

@@ -0,0 +1,356 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema
xmlns="http://www.springframework.org/schema/webflow-config"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
targetNamespace="http://www.springframework.org/schema/webflow-config"
elementFormDefault="qualified" attributeFormDefault="unqualified"
version="1.1">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Spring Web Flow Configuration Schema
Authors: Keith Donald, Ben Hale
<br>
Provides an easy way to configure a flow executor and an XML flow definition registry.
]]>
</xsd:documentation>
</xsd:annotation>
<xsd:import namespace="http://www.springframework.org/schema/beans"
schemaLocation="http://www.springframework.org/schema/beans/spring-beans-2.0.xsd" />
<xsd:element name="enable-scopes">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Allows access to the Spring Web Flow scopes (conversation, flow, flash) from a Spring ApplicationContext.
Only one of these elements is required per ApplicationContext.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="registry">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Deploys a flow definition registry. A flow definition registry contains a set of flow definitions.
A flow definition is typically built from externalized resource location such as a .xml file.
<br>
Each flow definition registered in this registry is assigned a unique identifier. By default,
this identifier is the name of the externalized resource minus its file extension. For example,
a registry containing flow definitions built from the files "orderitem-flow.xml" and "shipping-flow.xml"
would index those definitions by "orderitem-flow" and "shipping-flow" by default.
<br>
A flow registry is used by a flow executor at runtime to launch new executions of flow definitions.
]]>
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:sequence>
<xsd:element name="location" type="locationType" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Specifies a path to a externalized flow definition resource. The flow definition built from this
resource will be registered in this registry.
<br>
Individual paths such as:
<pre>
/WEB-INF/flows/orderitem-flow.xml
</pre>
... are supported as well as wildcard paths such as:
<pre>
/WEB-INF/flows/**/*-flow.xml
</pre>
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="locationType">
<xsd:attribute name="path" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The path to the externalized flow definition resource. May be a path to a single resource or
a ANT-style path expression that matches multiple resources.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:element name="executor">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Deploys a flow executor. A flow executor executes flow definitions. More specifically, an executor
is responsible for launching new flow executions as well as resuming paused flow executions.
A flow execution that spans more than one request is said to have 'paused' and is stored in
an execution repository.
]]>
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:sequence>
<xsd:element name="repository" type="repositoryType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Explicit repository configuration for this executor. This element is used if configuration needs
to be more fine grained than the repositoryType attribute on executor.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="execution-attributes" type="execution-attributesType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Execution attributes to associate with new flow executions launched by this executor.
These attributes may influence execution behavior.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="execution-listeners" type="execution-listenersType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The listeners eligible for observing the lifecycle of executions launched by this executor.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="repository-type" type="repositoryTypeAttribute">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The type of execution repository to use. The repository is responsible for managing flow execution
persistence between requests.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="registry-ref" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The idref to the registry this executor will use to locate flow definitions for execution.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:simpleType name="repositoryTypeAttribute">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="continuation">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The continuation repository type. Use this to snapshot flow execution state server-side to support use
of the browser back button.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="simple">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The simple repository type. Use of this strategy incurs minimal storage overhead but explicitly prevents
resubmits using the browser back button.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="client">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The client continuation repository type. Use this to snapshot flow execution state client-side to support use of the browser
back button in a stateless server environment.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="singlekey">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The "single key" repository type. Use of this strategy assigns a single key per flow execution that remains
constant throughout the life of the execution.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="repositoryType">
<xsd:attribute name="type" type="repositoryTypeAttribute" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The type of flow execution repository to use. The repository is responsible for managing flow execution
persistence between requests.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="max-conversations" type="xsd:integer">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The maximum number of concurrent conversations allowed by this repository. It is illegal to set this
attribute if you are also setting the 'conversation-manager-ref' attribute.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="max-continuations" type="xsd:integer">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The maximum number of flow execution continuations (snapshots) allowed by this repository per conversation.
This attribute is only relevant when the repository type is 'continuation'.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="conversation-manager-ref" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The idref of the conversation manager this repository should use. Setting this attribute
allows full control over the conversation manager implementation and configuration.
When used, any value for the 'max-conversations' attribute is ignored.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="execution-listenersType">
<xsd:sequence>
<xsd:element name="listener" type="listenerType" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Deploys a single flow execution listener that will observe the execution lifecycle of one or more
flow definitions. The flow definitions this listener applies to may be restricted by specifying criteria.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="listenerType">
<xsd:attribute name="ref" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The idref to your flow execution listener.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="criteria" type="xsd:string" default="*">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The flow definitions your listener should apply to, delimited by commas or '*' for "all".
Example: 'flow1,flow2,flow3'.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="execution-attributesType">
<xsd:sequence>
<xsd:element name="alwaysRedirectOnPause" type="alwaysRedirectOnPauseType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Sets the 'alwaysRedirectOnPause' execution attribute value. 'alwaysRedirectOnPause' allows
control over whether each time a flow execution pauses a browser redirect is performed. If
not specified the default value is 'true' unless explicitly set otherwise.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="attribute" type="attributeType" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
A single attribute describing an element. Attributes have string keys and object values.
An attribute's type may be specified using the 'type' attribute.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="alwaysRedirectOnPauseType">
<xsd:attribute name="value" type="xsd:boolean" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
true = always redirect on pause; false = do not, only redirect when explicitly instructed by the flow definition.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="attributeType">
<xsd:attribute name="name" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The name of the attribute.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="type" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The attribute's type, used to perform a from-string type conversion if specified.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="value" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The attribute value, subject to type conversion if the 'type' attribute is defined.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:schema>

View File

@@ -0,0 +1,125 @@
/*
* 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.context.scope;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.execution.FlowExecutionContext;
import org.springframework.webflow.execution.FlowExecutionContextHolder;
import org.springframework.webflow.execution.RequestContext;
/**
* Abstract {@link Scope} implementation that reads from a particular scope
* in the current thread-bound {@link FlowExecutionContext} object.
*
* <p>Subclasses simply need to implement {@link #getScope()} to instruct
* this class which {@link FlowExecutionContext} scope to read attributes from.
*
* <p>Relies on a thread-bound @{link FlowExecutionContext} instance wich is located
* through a @{link FlowExecutionContextHolder}.
*
* @author Ben Hale
* @since 1.1
* @see FlowExecutionContext
* @see FlowExecutionContextHolder
*/
public abstract class AbstractWebFlowScope implements Scope {
/**
* Logger, usable by subclasses.
*/
protected final Log logger = LogFactory.getLog(getClass());
/**
* Template method that determines the actual target scope.
* @return the target scope
* @see RequestContext#getConversationScope()
* @see RequestContext#getFlowScope()
* @see RequestContext#getRequestScope()
* @see RequestContext#getFlashScope()
*/
protected abstract MutableAttributeMap getScope();
public Object get(String name, ObjectFactory objectFactory) {
MutableAttributeMap scope;
try {
scope = getScope();
} catch(IllegalStateException e) {
throw new ScopedBeanException("Cannot retrieve scoped bean '"
+ name + "' before the scope has been populated");
}
Object scopedObject = scope.get(name);
if (scopedObject == null) {
if(logger.isDebugEnabled()) {
logger.debug("Could not find existing scoped instance of '" +
name + "'; creating new instace");
}
scopedObject = objectFactory.getObject();
scope.put(name, scopedObject);
} else {
if(logger.isDebugEnabled()) {
logger.debug("Found existing scoped instance of '" +
name + "'");
}
}
return scopedObject;
}
public Object remove(String name) {
try {
return getScope().remove(name);
} catch(IllegalStateException e) {
throw new ScopedBeanException("Cannot remove scoped bean '" +
name + "' before the scope has been populated");
}
}
/**
* Always returns <code>null</code> as most Spring Web Flow scopes do not
* have obvious conversation ids. Subclasses should override this method
* where conversation ids can be intelligently returned.
* @return always returns <code>null</code>
*/
public String getConversationId() {
return null;
}
/**
* Will not register a destruction callback as Spring Web Flow does not
* support destruction of scoped beans. Subclasses should override this
* method where where destruction can adequately be accomplished.
* @param name the name of the bean to register the callback for
* @param callback the callback to execute
*/
public void registerDestructionCallback(String name, Runnable callback) {
logger.warn("Destruction callback for '" + name +
"' was not registered. Spring Web Flow does not " +
"support destruction of scoped beans.");
}
/**
* Returns the current flow execution context. Used by subclasses to easily
* get access to the thread-bound flow execution context.
* @return the current thread-bound flow execution context
*/
protected FlowExecutionContext getFlowExecutionContext() {
return FlowExecutionContextHolder.getFlowExecutionContext();
}
}

View File

@@ -0,0 +1,35 @@
/*
* 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.context.scope;
import org.springframework.beans.factory.config.Scope;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.execution.FlowExecution;
/**
* Conversation-backed {@link Scope} implementation.
*
* @author Ben Hale
* @since 1.1
* @see FlowExecution#getConversationScope()
*/
public class ConversationScope extends AbstractWebFlowScope {
protected MutableAttributeMap getScope() {
return getFlowExecutionContext().getConversationScope();
}
}

View File

@@ -0,0 +1,35 @@
/*
* 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.context.scope;
import org.springframework.beans.factory.config.Scope;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.execution.FlowSession;
/**
* Flash-backed {@link Scope} implementation.
*
* @author Ben Hale
* @since 1.1
* @see FlowSession#getFlashMap()
*/
public class FlashScope extends AbstractWebFlowScope {
protected MutableAttributeMap getScope() {
return getFlowExecutionContext().getActiveSession().getFlashMap();
}
}

View File

@@ -0,0 +1,35 @@
/*
* 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.context.scope;
import org.springframework.beans.factory.config.Scope;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.execution.FlowSession;
/**
* Flow-backed {@link Scope} implementation.
*
* @author Ben Hale
* @since 1.1
* @see FlowSession#getScope()
*/
public class FlowScope extends AbstractWebFlowScope {
protected MutableAttributeMap getScope() {
return getFlowExecutionContext().getActiveSession().getScope();
}
}

View File

@@ -0,0 +1,43 @@
/*
* 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.context.scope;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.core.Ordered;
import org.springframework.webflow.execution.ScopeType;
/**
* Registers the Spring Web Flow bean scopes with a @{link ConfigurableListableBeanFactory}.
*
* @author Ben Hale
* @see Scope
*/
public class ScopeRegistrar implements BeanFactoryPostProcessor, Ordered {
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
beanFactory.registerScope(ScopeType.CONVERSATION.getLabel().toLowerCase(), new ConversationScope());
beanFactory.registerScope(ScopeType.FLASH.getLabel().toLowerCase(), new FlashScope());
beanFactory.registerScope(ScopeType.FLOW.getLabel().toLowerCase(), new FlowScope());
}
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}

View File

@@ -0,0 +1,47 @@
/*
* 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.context.scope;
import org.springframework.webflow.core.FlowException;
/**
* Thrown when a bean cannot be retrieved because the scope it resides in
* has not yet been populated. This might occur if the bean is being referenced
* before the FlowExecution has been retrieved.
*
* @author Ben Hale
* @since 1.1
*/
public class ScopedBeanException extends FlowException {
/**
* Create a new scoped bean exception.
* @param message the message
* @param cause the root cause of the failure
*/
public ScopedBeanException(String message, Throwable cause) {
super(message, cause);
}
/**
* Create a new scoped bean exception.
* @param message the message
*/
public ScopedBeanException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,63 @@
/*
* 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.execution;
import org.springframework.util.Assert;
/**
* Simple holder class that associates a {@link FlowExecutionContext} instance
* with the current thread. The FlowExecutionContext will not be inherited by
* any child threads spawned by the current thread.
* <p>
* Used as a central holder for the current FlowExecutionContext in Spring Web
* Flow, wherever necessary. Often used by artifacts needing to access the
* current active flow execution.
*
* @see FlowExecutionContext
*
* @author Ben Hale
* @since 1.1
*/
public class FlowExecutionContextHolder {
private static final ThreadLocal flowExecutionContextHolder = new ThreadLocal();
/**
* Associate the given FlowExecutionContext with the current thread.
* @param flowExecutionContext the current FlowExecutionContext, or
* <code>null</code> to reset the thread-bound context
*/
public static void setFlowExecutionContext(FlowExecutionContext flowExecutionContext) {
flowExecutionContextHolder.set(flowExecutionContext);
}
/**
* Return the FlowExecutionContext associated with the current thread, if
* any.
* @return the current FlowExecutionContext
* @throws IllegalStateException if no FlowExecutionContext is bound to this
* thread
*/
public static FlowExecutionContext getFlowExecutionContext() {
Assert.state(flowExecutionContextHolder.get() != null,
"No request context is bound to this thread");
return (FlowExecutionContext) flowExecutionContextHolder.get();
}
// not instantiable
private FlowExecutionContextHolder() {
}
}

View File

@@ -20,6 +20,7 @@ import javax.faces.context.FacesContext;
import javax.faces.el.EvaluationException;
import org.springframework.webflow.execution.FlowExecution;
import org.springframework.webflow.execution.FlowExecutionContextHolder;
/**
* A static utility class for accessing the current flow execution holder.
@@ -48,6 +49,7 @@ public class FlowExecutionHolderUtils {
*/
public static void setFlowExecutionHolder(FlowExecutionHolder holder, FacesContext context) {
context.getExternalContext().getRequestMap().put(getFlowExecutionHolderKey(), holder);
FlowExecutionContextHolder.setFlowExecutionContext(holder.getFlowExecution());
}
/**

View File

@@ -30,6 +30,7 @@ import org.springframework.webflow.definition.FlowDefinition;
import org.springframework.webflow.definition.registry.FlowDefinitionLocator;
import org.springframework.webflow.engine.NoMatchingTransitionException;
import org.springframework.webflow.execution.FlowExecution;
import org.springframework.webflow.execution.FlowExecutionContextHolder;
import org.springframework.webflow.execution.FlowExecutionFactory;
import org.springframework.webflow.execution.ViewSelection;
import org.springframework.webflow.executor.RequestParameterInputMapper;
@@ -246,6 +247,7 @@ public class FlowNavigationHandler extends DecoratingNavigationHandler {
if (logger.isDebugEnabled()) {
logger.debug("Cleaning up allocated flow system resources");
}
FlowExecutionContextHolder.setFlowExecutionContext(null);
FlowExecutionHolderUtils.unlockCurrentFlowExecutionIfNecessary(context);
ExternalContextHolder.setExternalContext(null);
}

View File

@@ -36,6 +36,7 @@ import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.definition.FlowDefinition;
import org.springframework.webflow.definition.registry.FlowDefinitionLocator;
import org.springframework.webflow.execution.FlowExecution;
import org.springframework.webflow.execution.FlowExecutionContextHolder;
import org.springframework.webflow.execution.FlowExecutionFactory;
import org.springframework.webflow.execution.ViewSelection;
import org.springframework.webflow.execution.repository.FlowExecutionAccessException;
@@ -481,6 +482,7 @@ public class FlowPhaseListener implements PhaseListener {
if (logger.isDebugEnabled()) {
logger.debug("Cleaning up allocated flow system resources");
}
FlowExecutionContextHolder.setFlowExecutionContext(null);
FlowExecutionHolderUtils.unlockCurrentFlowExecutionIfNecessary(context);
ExternalContextHolder.setExternalContext(null);
}

View File

@@ -0,0 +1,105 @@
/*
* 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.context.scope;
import junit.framework.TestCase;
import org.springframework.webflow.execution.FlowExecutionContextHolder;
import org.springframework.webflow.test.MockFlowExecutionContext;
/**
* Test cases for the @{link ConversationScope} class.
*
* @author Ben Hale
*/
public class ConversationScopeTests extends TestCase {
private MockFlowExecutionContext context;
private ConversationScope scope;
protected void setUp() {
context = new MockFlowExecutionContext();
FlowExecutionContextHolder.setFlowExecutionContext(context);
scope = new ConversationScope();
}
protected void tearDown() {
scope = null;
context = null;
FlowExecutionContextHolder.setFlowExecutionContext(null);
}
public void testGetVarMissing() {
StubObjectFactory factory = new StubObjectFactory();
Object gotten = scope.get("name", factory);
assertNotNull("Should be real object", gotten);
assertTrue("Should have added object to the map", context.getConversationScope().contains("name"));
assertSame("Created object should have been returned", factory.getValue(), gotten);
assertSame("Created object should have been persisted", factory.getValue(), context.getConversationScope().get(
"name"));
}
public void testGetVarExist() {
StubObjectFactory factory = new StubObjectFactory();
Object value = new Object();
context.getConversationScope().put("name", value);
Object gotten = scope.get("name", factory);
assertNotNull("Should be real object", gotten);
assertTrue("Should still be in map", context.getConversationScope().contains("name"));
assertSame("Persisted object should have been returned", value, gotten);
assertNotSame("Created object should not have been returned", factory.getValue(), gotten);
}
public void testGetRequestContextMissing() {
FlowExecutionContextHolder.setFlowExecutionContext(null);
StubObjectFactory factory = new StubObjectFactory();
try {
scope.get("name", factory);
fail("Should have thrown a ScopedBeanException without a request context");
} catch (ScopedBeanException e) {
}
}
public void testGetConversationId() {
String conversationId = scope.getConversationId();
assertNull("Method not implemented yet, should return null", conversationId);
}
public void testRemoveVarMissing() {
Object removed = scope.remove("name");
assertFalse("Should have removed from object from map", context.getConversationScope().contains("name"));
assertNull("Should have returned a null object", removed);
}
public void testRemoveVarExist() {
Object value = new Object();
context.getConversationScope().put("name", value);
Object removed = scope.remove("name");
assertFalse("Should have removed from object from map", context.getConversationScope().contains("name"));
assertSame("Should have returned the previous object", removed, value);
}
public void testRemoveRequestContextMissing() {
FlowExecutionContextHolder.setFlowExecutionContext(null);
try {
scope.remove("name");
fail("Should have thrown a ScopedBeanException without a request context");
} catch (ScopedBeanException e) {
}
}
}

View File

@@ -0,0 +1,108 @@
/*
* 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.context.scope;
import junit.framework.TestCase;
import org.springframework.webflow.execution.FlowExecutionContextHolder;
import org.springframework.webflow.test.MockFlowExecutionContext;
/**
* Test cases for the
* @{link FlashScope} class.
*
* @author Ben Hale
*/
public class FlashScopeTests extends TestCase {
private MockFlowExecutionContext context;
private FlashScope scope;
protected void setUp() {
context = new MockFlowExecutionContext();
FlowExecutionContextHolder.setFlowExecutionContext(context);
scope = new FlashScope();
}
protected void tearDown() {
scope = null;
context = null;
FlowExecutionContextHolder.setFlowExecutionContext(null);
}
public void testGetVarMissing() {
StubObjectFactory factory = new StubObjectFactory();
Object gotten = scope.get("name", factory);
assertNotNull("Should be real object", gotten);
assertTrue("Should have added object to the map", context.getActiveSession().getFlashMap().contains("name"));
assertSame("Created object should have been returned", factory.getValue(), gotten);
assertSame("Created object should have been persisted", factory.getValue(), context.getActiveSession()
.getFlashMap().get("name"));
}
public void testGetVarExist() {
StubObjectFactory factory = new StubObjectFactory();
Object value = new Object();
context.getActiveSession().getFlashMap().put("name", value);
Object gotten = scope.get("name", factory);
assertNotNull("Should be real object", gotten);
assertTrue("Should still be in map", context.getActiveSession().getFlashMap().contains("name"));
assertSame("Persisted object should have been returned", value, gotten);
assertNotSame("Created object should not have been returned", factory.getValue(), gotten);
}
public void testGetRequestContextMissing() {
FlowExecutionContextHolder.setFlowExecutionContext(null);
StubObjectFactory factory = new StubObjectFactory();
try {
scope.get("name", factory);
fail("Should have thrown a ScopedBeanException without a request context");
} catch (ScopedBeanException e) {
}
}
public void testGetConversationId() {
String flashId = scope.getConversationId();
assertNull("Method not implemented yet, should return null", flashId);
}
public void testRemoveVarMissing() {
Object removed = scope.remove("name");
assertFalse("Should have removed from object from map", context.getActiveSession().getFlashMap().contains(
"name"));
assertNull("Should have returned a null object", removed);
}
public void testRemoveVarExist() {
Object value = new Object();
context.getActiveSession().getFlashMap().put("name", value);
Object removed = scope.remove("name");
assertFalse("Should have removed from object from map", context.getActiveSession().getFlashMap().contains(
"name"));
assertSame("Should have returned the previous object", removed, value);
}
public void testRemoveRequestContextMissing() {
FlowExecutionContextHolder.setFlowExecutionContext(null);
try {
scope.remove("name");
fail("Should have thrown a ScopedBeanException without a request context");
} catch (ScopedBeanException e) {
}
}
}

View File

@@ -0,0 +1,106 @@
/*
* 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.context.scope;
import junit.framework.TestCase;
import org.springframework.webflow.execution.FlowExecutionContextHolder;
import org.springframework.webflow.test.MockFlowExecutionContext;
/**
* Test cases for the
* @{link FlowScope} class.
*
* @author Ben Hale
*/
public class FlowScopeTests extends TestCase {
private MockFlowExecutionContext context;
private FlowScope scope;
protected void setUp() {
context = new MockFlowExecutionContext();
FlowExecutionContextHolder.setFlowExecutionContext(context);
scope = new FlowScope();
}
protected void tearDown() {
scope = null;
context = null;
FlowExecutionContextHolder.setFlowExecutionContext(null);
}
public void testGetVarMissing() {
StubObjectFactory factory = new StubObjectFactory();
Object gotten = scope.get("name", factory);
assertNotNull("Should be real object", gotten);
assertTrue("Should have added object to the map", context.getActiveSession().getScope().contains("name"));
assertSame("Created object should have been returned", factory.getValue(), gotten);
assertSame("Created object should have been persisted", factory.getValue(), context.getActiveSession()
.getScope().get("name"));
}
public void testGetVarExist() {
StubObjectFactory factory = new StubObjectFactory();
Object value = new Object();
context.getActiveSession().getScope().put("name", value);
Object gotten = scope.get("name", factory);
assertNotNull("Should be real object", gotten);
assertTrue("Should still be in map", context.getActiveSession().getScope().contains("name"));
assertSame("Persisted object should have been returned", value, gotten);
assertNotSame("Created object should not have been returned", factory.getValue(), gotten);
}
public void testGetRequestContextMissing() {
FlowExecutionContextHolder.setFlowExecutionContext(null);
StubObjectFactory factory = new StubObjectFactory();
try {
scope.get("name", factory);
fail("Should have thrown a ScopedBeanException without a request context");
} catch (ScopedBeanException e) {
}
}
public void testGetConversationId() {
String flowId = scope.getConversationId();
assertNull("Method not implemented yet, should return null", flowId);
}
public void testRemoveVarMissing() {
Object removed = scope.remove("name");
assertFalse("Should have removed from object from map", context.getActiveSession().getScope().contains("name"));
assertNull("Should have returned a null object", removed);
}
public void testRemoveVarExist() {
Object value = new Object();
context.getActiveSession().getScope().put("name", value);
Object removed = scope.remove("name");
assertFalse("Should have removed from object from map", context.getActiveSession().getScope().contains("name"));
assertSame("Should have returned the previous object", removed, value);
}
public void testRemoveRequestContextMissing() {
FlowExecutionContextHolder.setFlowExecutionContext(null);
try {
scope.remove("name");
fail("Should have thrown a ScopedBeanException without a request context");
} catch (ScopedBeanException e) {
}
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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.context.scope;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectFactory;
/**
* Stub implementation for testing the Spring Web Flow scopes.
*
* @author Ben Hale
*/
public class StubObjectFactory implements ObjectFactory {
private Object value = new Object();
public Object getObject() throws BeansException {
return value;
}
public Object getValue() {
return value;
}
}