diff --git a/spring-webflow/ivy.xml b/spring-webflow/ivy.xml index 64ad8c16..c6a1f3ec 100644 --- a/spring-webflow/ivy.xml +++ b/spring-webflow/ivy.xml @@ -33,6 +33,8 @@ + + @@ -45,7 +47,7 @@ - + diff --git a/spring-webflow/src/main/java/org/springframework/webflow/config/EnableFlowScopesBeanDefinitionParser.java b/spring-webflow/src/main/java/org/springframework/webflow/config/EnableFlowScopesBeanDefinitionParser.java new file mode 100644 index 00000000..189ff831 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/config/EnableFlowScopesBeanDefinitionParser.java @@ -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.config; + +import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.webflow.config.scope.ScopeRegistrar; +import org.w3c.dom.Element; + +/** + * {@link BeanDefinitionParser} for the <enable-flow-scopes> tag. + * @author Ben Hale + */ +class EnableFlowScopesBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { + protected Class getBeanClass(Element element) { + return ScopeRegistrar.class; + } + + protected boolean shouldGenerateId() { + return true; + } +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/config/WebFlowConfigNamespaceHandler.java b/spring-webflow/src/main/java/org/springframework/webflow/config/WebFlowConfigNamespaceHandler.java index b29f34ed..232118c8 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/config/WebFlowConfigNamespaceHandler.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/config/WebFlowConfigNamespaceHandler.java @@ -28,5 +28,6 @@ public class WebFlowConfigNamespaceHandler extends NamespaceHandlerSupport { registerBeanDefinitionParser("flow-executor", new FlowExecutorBeanDefinitionParser()); registerBeanDefinitionParser("flow-execution-listeners", new FlowExecutionListenerLoaderBeanDefinitionParser()); registerBeanDefinitionParser("flow-registry", new FlowRegistryBeanDefinitionParser()); + registerBeanDefinitionParser("enable-flow-scopes", new EnableFlowScopesBeanDefinitionParser()); } } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/config/scope/AbstractWebFlowScope.java b/spring-webflow/src/main/java/org/springframework/webflow/config/scope/AbstractWebFlowScope.java new file mode 100644 index 00000000..8024e056 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/config/scope/AbstractWebFlowScope.java @@ -0,0 +1,78 @@ +package org.springframework.webflow.config.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.RequestContext; +import org.springframework.webflow.execution.RequestContextHolder; + +/** + * Base class for {@link Scope} implementations that access a Web Flow scope from the current request. + * + * @author Keith Donald + */ +public abstract class AbstractWebFlowScope implements Scope { + + /** + * Logger, usable by subclasses. + */ + protected final Log logger = LogFactory.getLog(getClass()); + + public Object get(String name, ObjectFactory objectFactory) { + MutableAttributeMap scope = getScope(); + Object scopedObject = scope.get(name); + if (scopedObject == null) { + if (logger.isDebugEnabled()) { + logger.debug("No scoped instance '" + name + "' found; creating new instance"); + } + scopedObject = objectFactory.getObject(); + scope.put(name, scopedObject); + } else { + if (logger.isDebugEnabled()) { + logger.debug("Returning scoped instance '" + name + "'"); + } + } + return scopedObject; + } + + public Object remove(String name) { + return getScope().remove(name); + } + + /** + * Template method that returns the target scope map. + * @throws IllegalStateException if the scope could not be accessed + */ + protected abstract MutableAttributeMap getScope() throws IllegalStateException; + + /** + * Always returns null 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 null + */ + 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."); + } + + protected RequestContext getRequiredRequestContext() { + RequestContext context = RequestContextHolder.getRequestContext(); + if (context == null) { + throw new IllegalStateException( + "No request context bound to this thread; to access flow-scoped beans you must be running in a flow execution request"); + } + return context; + } +} \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/config/scope/ConversationScope.java b/spring-webflow/src/main/java/org/springframework/webflow/config/scope/ConversationScope.java new file mode 100644 index 00000000..37b5887e --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/config/scope/ConversationScope.java @@ -0,0 +1,29 @@ +/* + * 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.scope; + +import org.springframework.beans.factory.config.Scope; +import org.springframework.webflow.core.collection.MutableAttributeMap; + +/** + * Conversation {@link Scope scope} implementation. + * @author Ben Hale + */ +class ConversationScope extends AbstractWebFlowScope { + protected MutableAttributeMap getScope() { + return getRequiredRequestContext().getConversationScope(); + } +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/config/scope/FlashScope.java b/spring-webflow/src/main/java/org/springframework/webflow/config/scope/FlashScope.java new file mode 100644 index 00000000..96d4a052 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/config/scope/FlashScope.java @@ -0,0 +1,29 @@ +/* + * 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.scope; + +import org.springframework.beans.factory.config.Scope; +import org.springframework.webflow.core.collection.MutableAttributeMap; + +/** + * Flash {@link Scope scope} implementation. + * @author Ben Hale + */ +class FlashScope extends AbstractWebFlowScope { + protected MutableAttributeMap getScope() { + return getRequiredRequestContext().getFlashScope(); + } +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/config/scope/FlowScope.java b/spring-webflow/src/main/java/org/springframework/webflow/config/scope/FlowScope.java new file mode 100644 index 00000000..6f67cba7 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/config/scope/FlowScope.java @@ -0,0 +1,29 @@ +/* + * 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.scope; + +import org.springframework.beans.factory.config.Scope; +import org.springframework.webflow.core.collection.MutableAttributeMap; + +/** + * Flow {@link Scope scope} implementation. + * @author Ben Hale + */ +class FlowScope extends AbstractWebFlowScope { + protected MutableAttributeMap getScope() { + return getRequiredRequestContext().getFlowScope(); + } +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/config/scope/RequestScope.java b/spring-webflow/src/main/java/org/springframework/webflow/config/scope/RequestScope.java new file mode 100644 index 00000000..e8c15625 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/config/scope/RequestScope.java @@ -0,0 +1,29 @@ +/* + * 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.scope; + +import org.springframework.beans.factory.config.Scope; +import org.springframework.webflow.core.collection.MutableAttributeMap; + +/** + * Request {@link Scope scope} implementation. + * @author Ben Hale + */ +class RequestScope extends AbstractWebFlowScope { + protected MutableAttributeMap getScope() { + return getRequiredRequestContext().getRequestScope(); + } +} \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/config/scope/ScopeRegistrar.java b/spring-webflow/src/main/java/org/springframework/webflow/config/scope/ScopeRegistrar.java new file mode 100644 index 00000000..7411eafb --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/config/scope/ScopeRegistrar.java @@ -0,0 +1,45 @@ +/* + * 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.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.REQUEST.getLabel().toLowerCase(), new RequestScope()); + beanFactory.registerScope(ScopeType.FLASH.getLabel().toLowerCase(), new FlashScope()); + beanFactory.registerScope(ScopeType.FLOW.getLabel().toLowerCase(), new FlowScope()); + beanFactory.registerScope(ScopeType.CONVERSATION.getLabel().toLowerCase(), new ConversationScope()); + } + + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE; + } + +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/config/scope/package.html b/spring-webflow/src/main/java/org/springframework/webflow/config/scope/package.html new file mode 100644 index 00000000..dd51d58d --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/config/scope/package.html @@ -0,0 +1,5 @@ + + +Support code to allow access to the Spring Web Flow scopes (request, flash, flow conversation) from a Spring ApplicationContext. + + diff --git a/spring-webflow/src/main/java/org/springframework/webflow/config/spring-webflow-config-2.0.xsd b/spring-webflow/src/main/java/org/springframework/webflow/config/spring-webflow-config-2.0.xsd index 096e2e67..7f4b09e9 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/config/spring-webflow-config-2.0.xsd +++ b/spring-webflow/src/main/java/org/springframework/webflow/config/spring-webflow-config-2.0.xsd @@ -208,6 +208,16 @@ The idref to the registry this executor will use to locate flow definitions for + + + + + + + + diff --git a/spring-webflow/src/test/java/org/springframework/webflow/config/EnableScopesBeanDefinitionParserTests.java b/spring-webflow/src/test/java/org/springframework/webflow/config/EnableScopesBeanDefinitionParserTests.java new file mode 100644 index 00000000..55e46723 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/config/EnableScopesBeanDefinitionParserTests.java @@ -0,0 +1,36 @@ +package org.springframework.webflow.config; + +import junit.framework.TestCase; + +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.webflow.core.collection.AttributeMap; +import org.springframework.webflow.execution.FlowExecutionListenerAdapter; +import org.springframework.webflow.execution.FlowSession; +import org.springframework.webflow.execution.RequestContext; +import org.springframework.webflow.executor.FlowExecutor; +import org.springframework.webflow.test.MockExternalContext; + +public class EnableScopesBeanDefinitionParserTests extends TestCase { + + private ClassPathXmlApplicationContext context; + + private FlowExecutor executor; + + public void setUp() { + context = new ClassPathXmlApplicationContext("org/springframework/webflow/config/enable-flow-scopes.xml"); + executor = (FlowExecutor) context.getBean("flowExecutor"); + } + + public void testExecute() { + MockExternalContext context = new MockExternalContext(); + context.setFlowId("flow"); + executor.execute(context); + } + + public static class ConfigurationListener extends FlowExecutionListenerAdapter { + public void sessionEnded(RequestContext context, FlowSession session, AttributeMap output) { + assertNotNull(session.getScope().get("user")); + } + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/config/EnableScopesService.java b/spring-webflow/src/test/java/org/springframework/webflow/config/EnableScopesService.java new file mode 100644 index 00000000..c76bc6b6 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/config/EnableScopesService.java @@ -0,0 +1,15 @@ +package org.springframework.webflow.config; + +import org.springframework.util.Assert; + +public class EnableScopesService { + private EnableScopesUser user; + + public void setUser(EnableScopesUser user) { + this.user = user; + } + + public void execute() { + Assert.isTrue("foo".equals(user.getName())); + } +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/config/EnableScopesUser.java b/spring-webflow/src/test/java/org/springframework/webflow/config/EnableScopesUser.java new file mode 100644 index 00000000..7f3104d3 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/config/EnableScopesUser.java @@ -0,0 +1,9 @@ +package org.springframework.webflow.config; + +public class EnableScopesUser { + private String name = "foo"; + + public String getName() { + return name; + } +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/config/enable-flow-scopes-flowdef.xml b/spring-webflow/src/test/java/org/springframework/webflow/config/enable-flow-scopes-flowdef.xml new file mode 100644 index 00000000..29b88ff9 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/config/enable-flow-scopes-flowdef.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/spring-webflow/src/test/java/org/springframework/webflow/config/enable-flow-scopes.xml b/spring-webflow/src/test/java/org/springframework/webflow/config/enable-flow-scopes.xml new file mode 100644 index 00000000..5cd29890 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/config/enable-flow-scopes.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file