diff --git a/spring-faces/src/main/java/org/springframework/faces/config/AbstractFacesFlowConfiguration.java b/spring-faces/src/main/java/org/springframework/faces/config/AbstractFacesFlowConfiguration.java new file mode 100644 index 00000000..65ad3ab9 --- /dev/null +++ b/spring-faces/src/main/java/org/springframework/faces/config/AbstractFacesFlowConfiguration.java @@ -0,0 +1,130 @@ +/* + * Copyright 2004-2014 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.faces.config; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.faces.webflow.JsfResourceRequestHandler; +import org.springframework.util.ClassUtils; +import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; +import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter; +import org.springframework.webflow.config.FlowDefinitionRegistryBuilder; +import org.springframework.webflow.config.FlowExecutorBuilder; +import org.springframework.webflow.definition.registry.FlowDefinitionLocator; +import org.springframework.webflow.definition.registry.FlowDefinitionRegistry; +import org.springframework.webflow.engine.builder.support.FlowBuilderServices; +import org.springframework.webflow.executor.FlowExecutor; + + +/** + * A base class for {@link Configuration @Configuration} classes to configure + * Spring Web Flow in JSF applications. + *

+ * Provides protected method access to builders for one (or more) of the following: + *

+ *

+ * Also registers a HandlerMapping bean to provide JSF 2 resource handling at + * {@code "/javax.faces.resource/**"} or Rich Faces at {@code "/rfRes/**"}. + + * @author Rossen Stoyanchev + * @since 2.4 + */ +public class AbstractFacesFlowConfiguration { + + private static final boolean isRichFacesPresent = + ClassUtils.isPresent("org.richfaces.application.CoreConfiguration", + ResourcesBeanDefinitionParser.class.getClassLoader()); + + + private ApplicationContext applicationContext; + + + public void setApplicationContext(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + public ApplicationContext getApplicationContext() { + return this.applicationContext; + } + + /** + * Return a builder for creating a {@link FlowExecutor} instance. + * @param flowRegistry the {@link FlowDefinitionRegistry} to configure on the flow executor + * @return the created builder + */ + protected FlowExecutorBuilder getFlowExecutorBuilder(FlowDefinitionLocator flowRegistry) { + return new FlowExecutorBuilder(flowRegistry, this.applicationContext); + } + + /** + * Return a builder for creating a {@link FlowDefinitionRegistry} instance. + * @return the created builder + */ + protected FlowDefinitionRegistryBuilder getFlowDefinitionRegistryBuilder() { + return new FlowDefinitionRegistryBuilder(this.applicationContext); + } + + /** + * Return a builder for creating a {@link FlowDefinitionRegistry} instance. + * @param flowBuilderServices the {@link FlowBuilderServices} to configure on the flow registry with + * @return the created builder + */ + protected FlowDefinitionRegistryBuilder getFlowDefinitionRegistryBuilder(FlowBuilderServices flowBuilderServices) { + return new FlowDefinitionRegistryBuilder(this.applicationContext, flowBuilderServices); + } + + /** + * Return a builder for creating a {@link FlowBuilderServices} instance. + * @return the created builder + */ + protected FlowBuilderServicesBuilder getFlowBuilderServicesBuilder() { + return new FlowBuilderServicesBuilder(); + } + + @Bean + public SimpleUrlHandlerMapping jsrResourceHandlerMapping() { + + Map urlMap = new HashMap(); + urlMap.put("/javax.faces.resource/**", jsfResourceRequestHandler()); + if (isRichFacesPresent) { + urlMap.put("/rfRes/**", jsfResourceRequestHandler()); + } + + SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping(); + handlerMapping.setUrlMap(urlMap); + handlerMapping.setOrder(0); + return handlerMapping; + } + + @Bean + public JsfResourceRequestHandler jsfResourceRequestHandler() { + return new JsfResourceRequestHandler(); + } + + @Bean + public HttpRequestHandlerAdapter httpRequestHandlerAdapter() { + return new HttpRequestHandlerAdapter(); + } + +} diff --git a/spring-faces/src/main/java/org/springframework/faces/config/AbstractFacesPortletFlowConfiguration.java b/spring-faces/src/main/java/org/springframework/faces/config/AbstractFacesPortletFlowConfiguration.java new file mode 100644 index 00000000..31aa73c0 --- /dev/null +++ b/spring-faces/src/main/java/org/springframework/faces/config/AbstractFacesPortletFlowConfiguration.java @@ -0,0 +1,35 @@ +/* + * Copyright 2004-2014 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.faces.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.faces.webflow.context.portlet.JsfResourceRequestHandler; + +/** + * Extends {@link AbstractFacesFlowConfiguration} and registers a + * {@link JsfResourceRequestHandler} bean for serving resources in a Portlet environment. + + * @author Rossen Stoyanchev + * @since 2.4 + */ +public class AbstractFacesPortletFlowConfiguration extends AbstractFacesFlowConfiguration { + + @Bean + public JsfResourceRequestHandler jsfPortletResourceRequestHandler() { + return new JsfResourceRequestHandler(); + } + +} diff --git a/spring-faces/src/main/java/org/springframework/faces/config/FlowBuilderServicesBuilder.java b/spring-faces/src/main/java/org/springframework/faces/config/FlowBuilderServicesBuilder.java new file mode 100644 index 00000000..4a633e7f --- /dev/null +++ b/spring-faces/src/main/java/org/springframework/faces/config/FlowBuilderServicesBuilder.java @@ -0,0 +1,128 @@ +/* + * Copyright 2004-2014 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.faces.config; + +import org.springframework.binding.convert.ConversionService; +import org.springframework.binding.convert.service.DefaultConversionService; +import org.springframework.binding.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.faces.model.converter.FacesConversionService; +import org.springframework.faces.webflow.FacesSpringELExpressionParser; +import org.springframework.faces.webflow.JsfViewFactoryCreator; +import org.springframework.util.Assert; +import org.springframework.webflow.engine.builder.ViewFactoryCreator; +import org.springframework.webflow.engine.builder.support.FlowBuilderServices; +import org.springframework.webflow.expression.spel.WebFlowSpringELExpressionParser; + +/** + * A builder for {@link FlowBuilderServices} instances for use in JSF applications. + * Designed for programmatic use in {@code @Bean} factory methods. For XML + * configuration consider using the {@code webflow-config} and {@code faces-config} + * XML namespaces. + * + * @author Rossen Stoyanchev + * @since 2.4 + */ +public class FlowBuilderServicesBuilder { + + private boolean enableManagedBeans = false; + + private ConversionService conversionService = new FacesConversionService(); + + private ExpressionParser expressionParser; + + private ViewFactoryCreator viewFactoryCreator = new JsfViewFactoryCreator(); + + private boolean enableDevelopmentMode; + + + /** + * Whether to enable access to JSF-managed beans from EL expressions. + * When this attribute is set to true, a special EL expression parser will be registered. + * @param enableManagedBeans whether to enable JSF managed bean resolution + */ + public FlowBuilderServicesBuilder setEnableManagedBeans(boolean enableManagedBeans) { + this.enableManagedBeans = enableManagedBeans; + return this; + } + + /** + * Set the {@link ConversionService} to use. + * By default a {@link DefaultConversionService} instance is used. + * @param conversionService the conversion service + */ + public FlowBuilderServicesBuilder setConversionService(ConversionService conversionService) { + this.conversionService = conversionService; + return this; + } + + /** + * Set the {@link ExpressionParser} to use. + * By default a {@link WebFlowSpringELExpressionParser} with SpEL expressions is used. + * @param expressionParser the expression parser to use + */ + public FlowBuilderServicesBuilder setExpressionParser(ExpressionParser expressionParser) { + this.expressionParser = expressionParser; + return this; + } + + /** + * Set a custom {@link ViewFactoryCreator} to use for rendering. + * By default an {@link JsfViewFactoryCreator} instance is used. + * @param viewFactoryCreator the ViewFactory creator to use + */ + public FlowBuilderServicesBuilder setViewFactoryCreator(ViewFactoryCreator viewFactoryCreator) { + this.viewFactoryCreator = viewFactoryCreator; + return this; + } + + /** + * Put all flows in development mode. When set to {@code true}, changes to a flow + * definition are auto-detected and result in a flow refresh. + * By default this is set to {@code false} + * @param enableDevelopmentMode whether to enable development mode + */ + public FlowBuilderServicesBuilder setDevelopmentMode(boolean enableDevelopmentMode) { + this.enableDevelopmentMode = enableDevelopmentMode; + return this; + } + + /** + * Create and return a {@link FlowBuilderServices} instance. + */ + public FlowBuilderServices build() { + FlowBuilderServices flowBuilderServices = new FlowBuilderServices(); + flowBuilderServices.setConversionService(this.conversionService); + flowBuilderServices.setExpressionParser(getExpressionParser()); + flowBuilderServices.setViewFactoryCreator(this.viewFactoryCreator); + flowBuilderServices.setDevelopment(this.enableDevelopmentMode); + return flowBuilderServices; + } + + private ExpressionParser getExpressionParser() { + if (this.expressionParser != null) { + Assert.isTrue(!this.enableManagedBeans, + "Do not specify a custom expression-parser when enable-managed-beans is true"); + return this.expressionParser; + } + else { + return (this.enableManagedBeans ? + new FacesSpringELExpressionParser(new SpelExpressionParser(), this.conversionService) : + new WebFlowSpringELExpressionParser(new SpelExpressionParser(), this.conversionService)); + } + } + +} \ No newline at end of file diff --git a/spring-faces/src/main/java/org/springframework/faces/config/ResourcesBeanDefinitionParser.java b/spring-faces/src/main/java/org/springframework/faces/config/ResourcesBeanDefinitionParser.java index aadac9c5..69330ecb 100644 --- a/spring-faces/src/main/java/org/springframework/faces/config/ResourcesBeanDefinitionParser.java +++ b/spring-faces/src/main/java/org/springframework/faces/config/ResourcesBeanDefinitionParser.java @@ -43,7 +43,7 @@ public class ResourcesBeanDefinitionParser implements BeanDefinitionParser { static final String PORTLET_RESOURCE_HANDLER_BEAN_NAME = "jsfPortletResourceRequestHandler"; - private static final boolean RICH_FACES_PRESENT = + private static final boolean isRichFacesPresent = ClassUtils.isPresent("org.richfaces.application.CoreConfiguration", ResourcesBeanDefinitionParser.class.getClassLoader()); @@ -106,7 +106,7 @@ public class ResourcesBeanDefinitionParser implements BeanDefinitionParser { Map urlMap = new ManagedMap(); urlMap.put("/javax.faces.resource/**", SERVLET_RESOURCE_HANDLER_BEAN_NAME); - if (RICH_FACES_PRESENT) { + if (isRichFacesPresent) { urlMap.put("/rfRes/**", SERVLET_RESOURCE_HANDLER_BEAN_NAME); } diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/JsfResourceRequestHandler.java b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfResourceRequestHandler.java index 62ced4ec..6a0cdb5a 100644 --- a/spring-faces/src/main/java/org/springframework/faces/webflow/JsfResourceRequestHandler.java +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfResourceRequestHandler.java @@ -27,17 +27,19 @@ import org.springframework.web.HttpRequestHandler; import org.springframework.web.context.support.WebApplicationObjectSupport; /** - * Handles a request by delegating to the JSF ResourceHandler, which serves web application and classpath resources such - * as images, CSS and JavaScript files from well-known locations. - * + * Handles a request by delegating to the JSF ResourceHandler, which serves web + * application and classpath resources such as images, CSS and JavaScript files + * from well-known locations. + * * @since 2.2.0 * @author Rossen Stoyanchev * @see ResourceHandler */ public class JsfResourceRequestHandler extends WebApplicationObjectSupport implements HttpRequestHandler { - public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, - IOException { + public void handleRequest(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + FacesContextHelper helper = new FacesContextHelper(); try { FacesContext facesContext = helper.getFacesContext(getServletContext(), request, response); diff --git a/spring-faces/src/test/java/org/springframework/faces/config/AbstractFacesFlowBuilderServicesConfigurationTests.java b/spring-faces/src/test/java/org/springframework/faces/config/AbstractFacesFlowBuilderServicesConfigurationTests.java new file mode 100644 index 00000000..d2cf0351 --- /dev/null +++ b/spring-faces/src/test/java/org/springframework/faces/config/AbstractFacesFlowBuilderServicesConfigurationTests.java @@ -0,0 +1,126 @@ +package org.springframework.faces.config; + +import junit.framework.TestCase; + +import org.springframework.binding.convert.ConversionException; +import org.springframework.binding.convert.ConversionExecutionException; +import org.springframework.binding.convert.ConversionExecutor; +import org.springframework.binding.convert.ConversionExecutorNotFoundException; +import org.springframework.binding.convert.ConversionService; +import org.springframework.binding.expression.Expression; +import org.springframework.binding.expression.ExpressionParser; +import org.springframework.binding.expression.spel.SpringELExpressionParser; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.faces.model.converter.FacesConversionService; +import org.springframework.faces.webflow.FacesSpringELExpressionParser; +import org.springframework.faces.webflow.JSFMockHelper; +import org.springframework.faces.webflow.JsfViewFactoryCreator; +import org.springframework.validation.Validator; +import org.springframework.webflow.engine.builder.BinderConfiguration; +import org.springframework.webflow.engine.builder.ViewFactoryCreator; +import org.springframework.webflow.engine.builder.support.FlowBuilderServices; +import org.springframework.webflow.execution.ViewFactory; +import org.springframework.webflow.expression.spel.WebFlowSpringELExpressionParser; +import org.springframework.webflow.validation.ValidationHintResolver; + +public abstract class AbstractFacesFlowBuilderServicesConfigurationTests extends TestCase { + + protected ApplicationContext context; + + protected FlowBuilderServices builderServices; + + protected final JSFMockHelper jsf = new JSFMockHelper(); + + + public void setUp() throws Exception { + this.jsf.setUp(); + this.context = initApplicationContext(); + } + + protected abstract ApplicationContext initApplicationContext(); + + protected void tearDown() throws Exception { + this.jsf.tearDown(); + } + + public void testConfigureDefaults() { + this.builderServices = (FlowBuilderServices) this.context.getBean("flowBuilderServicesDefault"); + assertNotNull(this.builderServices); + assertTrue(this.builderServices.getExpressionParser() instanceof SpringELExpressionParser); + assertTrue(this.builderServices.getViewFactoryCreator() instanceof JsfViewFactoryCreator); + assertTrue(this.builderServices.getConversionService() instanceof FacesConversionService); + assertFalse(this.builderServices.getDevelopment()); + } + + public void testEnableManagedBeans() { + this.builderServices = (FlowBuilderServices) this.context.getBean("flowBuilderServicesLegacy"); + assertNotNull(this.builderServices); + assertTrue(this.builderServices.getExpressionParser() instanceof FacesSpringELExpressionParser); + assertTrue(this.builderServices.getViewFactoryCreator() instanceof JsfViewFactoryCreator); + assertTrue(this.builderServices.getConversionService() instanceof FacesConversionService); + assertFalse(this.builderServices.getDevelopment()); + } + + public void testFlowBuilderServicesAllCustomized() { + this.builderServices = (FlowBuilderServices) this.context.getBean("flowBuilderServicesAllCustom"); + assertNotNull(this.builderServices); + assertTrue(this.builderServices.getExpressionParser() instanceof WebFlowSpringELExpressionParser); + assertTrue(this.builderServices.getViewFactoryCreator() instanceof TestViewFactoryCreator); + assertTrue(this.builderServices.getConversionService() instanceof TestConversionService); + assertTrue(this.builderServices.getDevelopment()); + } + + public void testFlowBuilderServicesConversionServiceCustomized() { + this.builderServices = (FlowBuilderServices) this.context.getBean("flowBuilderServicesConversionServiceCustom"); + assertNotNull(this.builderServices); + assertTrue(this.builderServices.getConversionService() instanceof TestConversionService); + assertTrue(this.builderServices.getExpressionParser() instanceof WebFlowSpringELExpressionParser); + assertTrue(((SpringELExpressionParser) this.builderServices.getExpressionParser()).getConversionService() instanceof TestConversionService); + assertTrue(this.builderServices.getViewFactoryCreator() instanceof JsfViewFactoryCreator); + assertFalse(this.builderServices.getDevelopment()); + } + + public static class TestViewFactoryCreator implements ViewFactoryCreator { + + public ViewFactory createViewFactory(Expression viewIdExpression, ExpressionParser expressionParser, + ConversionService conversionService, BinderConfiguration binderConfiguration, + Validator validator, ValidationHintResolver validationHintResolver) { + throw new UnsupportedOperationException("Auto-generated method stub"); + } + + public String getViewIdByConvention(String viewStateId) { + return viewStateId; + } + + } + + public static class TestConversionService implements ConversionService { + + public Object executeConversion(Object source, Class targetClass) throws ConversionException { + throw new UnsupportedOperationException("Auto-generated method stub"); + } + + public Object executeConversion(String converterId, Object source, Class targetClass) { + throw new UnsupportedOperationException("Auto-generated method stub"); + } + + public ConversionExecutor getConversionExecutor(Class sourceClass, Class targetClass) + throws ConversionExecutionException { + throw new UnsupportedOperationException("Auto-generated method stub"); + } + + public ConversionExecutor getConversionExecutor(String id, Class sourceClass, Class targetClass) + throws ConversionExecutorNotFoundException { + throw new UnsupportedOperationException("Auto-generated method stub"); + } + + public Class getClassForAlias(String name) throws ConversionExecutionException { + throw new UnsupportedOperationException("Auto-generated method stub"); + } + + public org.springframework.core.convert.ConversionService getDelegateConversionService() { + throw new UnsupportedOperationException("Auto-generated method stub"); + } + } +} diff --git a/spring-faces/src/test/java/org/springframework/faces/config/AbstractResourcesConfigurationTests.java b/spring-faces/src/test/java/org/springframework/faces/config/AbstractResourcesConfigurationTests.java new file mode 100644 index 00000000..d1d355ae --- /dev/null +++ b/spring-faces/src/test/java/org/springframework/faces/config/AbstractResourcesConfigurationTests.java @@ -0,0 +1,45 @@ +package org.springframework.faces.config; + +import java.util.Map; + +import junit.framework.TestCase; + +import org.springframework.context.ApplicationContext; +import org.springframework.faces.webflow.JsfResourceRequestHandler; +import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; +import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter; + +public abstract class AbstractResourcesConfigurationTests extends TestCase { + + protected ApplicationContext context; + + public void setUp() throws Exception { + this.context = initApplicationContext(); + } + + protected abstract ApplicationContext initApplicationContext(); + + protected void tearDown() throws Exception { + } + + public void testConfigureDefaults() { + Map map = this.context.getBeansOfType(HttpRequestHandlerAdapter.class); + assertEquals(1, map.values().size()); + + Object resourceHandler = this.context.getBean(ResourcesBeanDefinitionParser.SERVLET_RESOURCE_HANDLER_BEAN_NAME); + assertNotNull(resourceHandler); + assertTrue(resourceHandler instanceof JsfResourceRequestHandler); + + map = this.context.getBeansOfType(SimpleUrlHandlerMapping.class); + assertEquals(1, map.values().size()); + SimpleUrlHandlerMapping handlerMapping = (SimpleUrlHandlerMapping) map.values().iterator().next(); + assertSame(resourceHandler, handlerMapping.getHandlerMap().get("/javax.faces.resource/**")); + assertEquals(0, handlerMapping.getOrder()); + } + + public void testConfigurePortlet() { + Object resourceHandler = this.context.getBean(ResourcesBeanDefinitionParser.PORTLET_RESOURCE_HANDLER_BEAN_NAME); + assertNotNull(resourceHandler); + assertTrue(resourceHandler instanceof org.springframework.faces.webflow.context.portlet.JsfResourceRequestHandler); + } +} diff --git a/spring-faces/src/test/java/org/springframework/faces/config/FacesFlowBuilderServicesBeanDefinitionParserTests.java b/spring-faces/src/test/java/org/springframework/faces/config/FacesFlowBuilderServicesBeanDefinitionParserTests.java index df009d89..6c6f4b34 100644 --- a/spring-faces/src/test/java/org/springframework/faces/config/FacesFlowBuilderServicesBeanDefinitionParserTests.java +++ b/spring-faces/src/test/java/org/springframework/faces/config/FacesFlowBuilderServicesBeanDefinitionParserTests.java @@ -1,120 +1,15 @@ package org.springframework.faces.config; -import junit.framework.TestCase; - -import org.springframework.binding.convert.ConversionException; -import org.springframework.binding.convert.ConversionExecutionException; -import org.springframework.binding.convert.ConversionExecutor; -import org.springframework.binding.convert.ConversionExecutorNotFoundException; -import org.springframework.binding.convert.ConversionService; -import org.springframework.binding.expression.Expression; -import org.springframework.binding.expression.ExpressionParser; -import org.springframework.binding.expression.spel.SpringELExpressionParser; +import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.springframework.faces.model.converter.FacesConversionService; -import org.springframework.faces.webflow.FacesSpringELExpressionParser; -import org.springframework.faces.webflow.JSFMockHelper; -import org.springframework.faces.webflow.JsfViewFactoryCreator; -import org.springframework.validation.Validator; -import org.springframework.webflow.engine.builder.BinderConfiguration; -import org.springframework.webflow.engine.builder.ViewFactoryCreator; -import org.springframework.webflow.engine.builder.support.FlowBuilderServices; -import org.springframework.webflow.execution.ViewFactory; -import org.springframework.webflow.expression.spel.WebFlowSpringELExpressionParser; -import org.springframework.webflow.validation.ValidationHintResolver; -public class FacesFlowBuilderServicesBeanDefinitionParserTests extends TestCase { +public class FacesFlowBuilderServicesBeanDefinitionParserTests + extends AbstractFacesFlowBuilderServicesConfigurationTests { - private ClassPathXmlApplicationContext context; - private FlowBuilderServices builderServices; - private final JSFMockHelper jsf = new JSFMockHelper(); - public void setUp() throws Exception { - this.jsf.setUp(); - this.context = new ClassPathXmlApplicationContext("org/springframework/faces/config/flow-builder-services.xml"); + @Override + protected ApplicationContext initApplicationContext() { + return new ClassPathXmlApplicationContext("org/springframework/faces/config/flow-builder-services.xml"); } - protected void tearDown() throws Exception { - this.jsf.tearDown(); - } - - public void testConfigureDefaults() { - this.builderServices = (FlowBuilderServices) this.context.getBean("flowBuilderServicesDefault"); - assertNotNull(this.builderServices); - assertTrue(this.builderServices.getExpressionParser() instanceof SpringELExpressionParser); - assertTrue(this.builderServices.getViewFactoryCreator() instanceof JsfViewFactoryCreator); - assertTrue(this.builderServices.getConversionService() instanceof FacesConversionService); - assertFalse(this.builderServices.getDevelopment()); - } - - public void testEnableManagedBeans() { - this.builderServices = (FlowBuilderServices) this.context.getBean("flowBuilderServicesLegacy"); - assertNotNull(this.builderServices); - assertTrue(this.builderServices.getExpressionParser() instanceof FacesSpringELExpressionParser); - assertTrue(this.builderServices.getViewFactoryCreator() instanceof JsfViewFactoryCreator); - assertTrue(this.builderServices.getConversionService() instanceof FacesConversionService); - assertFalse(this.builderServices.getDevelopment()); - } - - public void testFlowBuilderServicesAllCustomized() { - this.builderServices = (FlowBuilderServices) this.context.getBean("flowBuilderServicesAllCustom"); - assertNotNull(this.builderServices); - assertTrue(this.builderServices.getExpressionParser() instanceof WebFlowSpringELExpressionParser); - assertTrue(this.builderServices.getViewFactoryCreator() instanceof TestViewFactoryCreator); - assertTrue(this.builderServices.getConversionService() instanceof TestConversionService); - assertTrue(this.builderServices.getDevelopment()); - } - - public void testFlowBuilderServicesConversionServiceCustomized() { - this.builderServices = (FlowBuilderServices) this.context.getBean("flowBuilderServicesConversionServiceCustom"); - assertNotNull(this.builderServices); - assertTrue(this.builderServices.getConversionService() instanceof TestConversionService); - assertTrue(this.builderServices.getExpressionParser() instanceof WebFlowSpringELExpressionParser); - assertTrue(((SpringELExpressionParser) this.builderServices.getExpressionParser()).getConversionService() instanceof TestConversionService); - assertTrue(this.builderServices.getViewFactoryCreator() instanceof JsfViewFactoryCreator); - assertFalse(this.builderServices.getDevelopment()); - } - - public static class TestViewFactoryCreator implements ViewFactoryCreator { - - public ViewFactory createViewFactory(Expression viewIdExpression, ExpressionParser expressionParser, - ConversionService conversionService, BinderConfiguration binderConfiguration, - Validator validator, ValidationHintResolver validationHintResolver) { - throw new UnsupportedOperationException("Auto-generated method stub"); - } - - public String getViewIdByConvention(String viewStateId) { - return viewStateId; - } - - } - - public static class TestConversionService implements ConversionService { - - public Object executeConversion(Object source, Class targetClass) throws ConversionException { - throw new UnsupportedOperationException("Auto-generated method stub"); - } - - public Object executeConversion(String converterId, Object source, Class targetClass) { - throw new UnsupportedOperationException("Auto-generated method stub"); - } - - public ConversionExecutor getConversionExecutor(Class sourceClass, Class targetClass) - throws ConversionExecutionException { - throw new UnsupportedOperationException("Auto-generated method stub"); - } - - public ConversionExecutor getConversionExecutor(String id, Class sourceClass, Class targetClass) - throws ConversionExecutorNotFoundException { - throw new UnsupportedOperationException("Auto-generated method stub"); - } - - public Class getClassForAlias(String name) throws ConversionExecutionException { - throw new UnsupportedOperationException("Auto-generated method stub"); - } - - public org.springframework.core.convert.ConversionService getDelegateConversionService() { - throw new UnsupportedOperationException("Auto-generated method stub"); - } - } } diff --git a/spring-faces/src/test/java/org/springframework/faces/config/FacesFlowBuilderServicesJavaConfigTests.java b/spring-faces/src/test/java/org/springframework/faces/config/FacesFlowBuilderServicesJavaConfigTests.java new file mode 100644 index 00000000..04d8de0b --- /dev/null +++ b/spring-faces/src/test/java/org/springframework/faces/config/FacesFlowBuilderServicesJavaConfigTests.java @@ -0,0 +1,65 @@ +package org.springframework.faces.config; + +import org.springframework.binding.convert.ConversionService; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.webflow.engine.builder.ViewFactoryCreator; +import org.springframework.webflow.engine.builder.support.FlowBuilderServices; +import org.springframework.webflow.expression.spel.WebFlowSpringELExpressionParser; + +public class FacesFlowBuilderServicesJavaConfigTests extends AbstractFacesFlowBuilderServicesConfigurationTests { + + + @Override + protected ApplicationContext initApplicationContext() { + return new AnnotationConfigApplicationContext(FacesFlowConfig.class); + } + + + static class FacesFlowConfig extends AbstractFacesFlowConfiguration { + + @Bean + public FlowBuilderServices flowBuilderServicesDefault() { + return getFlowBuilderServicesBuilder().build(); + } + + @Bean + public FlowBuilderServices flowBuilderServicesLegacy() { + return getFlowBuilderServicesBuilder().setEnableManagedBeans(true).build(); + } + + @Bean + public FlowBuilderServices flowBuilderServicesAllCustom() { + return getFlowBuilderServicesBuilder() + .setExpressionParser(customExpressionParser()) + .setViewFactoryCreator(customViewFactoryCreator()) + .setConversionService(customConversionService()) + .setDevelopmentMode(true) + .build(); + } + + @Bean + public FlowBuilderServices flowBuilderServicesConversionServiceCustom() { + return getFlowBuilderServicesBuilder().setConversionService(customConversionService()).build(); + } + + @Bean + public WebFlowSpringELExpressionParser customExpressionParser() { + return new WebFlowSpringELExpressionParser(new SpelExpressionParser()); + } + + @Bean + public ViewFactoryCreator customViewFactoryCreator() { + return new TestViewFactoryCreator(); + } + + @Bean + public ConversionService customConversionService() { + return new TestConversionService(); + } + + } + +} diff --git a/spring-faces/src/test/java/org/springframework/faces/config/ResourcesBeanDefinitionParserTests.java b/spring-faces/src/test/java/org/springframework/faces/config/ResourcesBeanDefinitionParserTests.java index 5f1d9018..9cdf6e1f 100644 --- a/spring-faces/src/test/java/org/springframework/faces/config/ResourcesBeanDefinitionParserTests.java +++ b/spring-faces/src/test/java/org/springframework/faces/config/ResourcesBeanDefinitionParserTests.java @@ -1,44 +1,14 @@ package org.springframework.faces.config; -import java.util.Map; - -import junit.framework.TestCase; - +import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.springframework.faces.webflow.JsfResourceRequestHandler; -import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; -import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter; -public class ResourcesBeanDefinitionParserTests extends TestCase { +public class ResourcesBeanDefinitionParserTests extends AbstractResourcesConfigurationTests { - private ClassPathXmlApplicationContext context; - public void setUp() throws Exception { - this.context = new ClassPathXmlApplicationContext("org/springframework/faces/config/resources.xml"); + @Override + protected ApplicationContext initApplicationContext() { + return new ClassPathXmlApplicationContext("org/springframework/faces/config/resources.xml"); } - protected void tearDown() throws Exception { - } - - public void testConfigureDefaults() { - Map map = this.context.getBeansOfType(HttpRequestHandlerAdapter.class); - assertEquals(1, map.values().size()); - - Object resourceHandler = this.context.getBean(ResourcesBeanDefinitionParser.SERVLET_RESOURCE_HANDLER_BEAN_NAME); - assertNotNull(resourceHandler); - assertTrue(resourceHandler instanceof JsfResourceRequestHandler); - - map = this.context.getBeansOfType(SimpleUrlHandlerMapping.class); - assertEquals(1, map.values().size()); - SimpleUrlHandlerMapping handlerMapping = (SimpleUrlHandlerMapping) map.values().iterator().next(); - assertEquals(ResourcesBeanDefinitionParser.SERVLET_RESOURCE_HANDLER_BEAN_NAME, - handlerMapping.getUrlMap().get("/javax.faces.resource/**")); - assertEquals(0, handlerMapping.getOrder()); - } - - public void testConfigurePortlet() { - Object resourceHandler = this.context.getBean(ResourcesBeanDefinitionParser.PORTLET_RESOURCE_HANDLER_BEAN_NAME); - assertNotNull(resourceHandler); - assertTrue(resourceHandler instanceof org.springframework.faces.webflow.context.portlet.JsfResourceRequestHandler); - } } diff --git a/spring-faces/src/test/java/org/springframework/faces/config/ResourcesJavaConfigTests.java b/spring-faces/src/test/java/org/springframework/faces/config/ResourcesJavaConfigTests.java new file mode 100644 index 00000000..b2f4021f --- /dev/null +++ b/spring-faces/src/test/java/org/springframework/faces/config/ResourcesJavaConfigTests.java @@ -0,0 +1,20 @@ +package org.springframework.faces.config; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; + +public class ResourcesJavaConfigTests extends AbstractResourcesConfigurationTests { + + + @Override + protected ApplicationContext initApplicationContext() { + return new AnnotationConfigApplicationContext(FacesFlowConfig.class); + } + + @Configuration + static class FacesFlowConfig extends AbstractFacesPortletFlowConfiguration { + + } + +} diff --git a/spring-faces/src/test/java/org/springframework/faces/config/flow-builder-services.xml b/spring-faces/src/test/java/org/springframework/faces/config/flow-builder-services.xml index d0b45bb0..ab97d5aa 100644 --- a/spring-faces/src/test/java/org/springframework/faces/config/flow-builder-services.xml +++ b/spring-faces/src/test/java/org/springframework/faces/config/flow-builder-services.xml @@ -26,8 +26,10 @@ - + - + diff --git a/spring-webflow/src/main/java/org/springframework/webflow/config/AbstractFlowConfiguration.java b/spring-webflow/src/main/java/org/springframework/webflow/config/AbstractFlowConfiguration.java new file mode 100644 index 00000000..24e5b3c1 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/config/AbstractFlowConfiguration.java @@ -0,0 +1,92 @@ +/* + * Copyright 2004-2014 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.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.Configuration; +import org.springframework.webflow.definition.registry.FlowDefinitionLocator; +import org.springframework.webflow.definition.registry.FlowDefinitionRegistry; +import org.springframework.webflow.engine.builder.support.FlowBuilderServices; +import org.springframework.webflow.executor.FlowExecutor; + +/** + * A base class for {@link Configuration @Configuration} classes to configure + * Spring Web Flow. + *

+ * Does not provides any configuration (i.e. no {@code @Bean} methods}. + * Instead it provides access, via protected methods, to builders for one (or more) + * of the following: + *

+ *

+ * Sub-classes are expected to declare {@code @Bean} methods themselves and use the + * appropriate builder from these methods. + + * @author Rossen Stoyanchev + * @since 2.4 + */ +public class AbstractFlowConfiguration implements ApplicationContextAware { + + private ApplicationContext applicationContext; + + + public void setApplicationContext(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + public ApplicationContext getApplicationContext() { + return this.applicationContext; + } + + /** + * Return a builder for creating a {@link FlowExecutor} instance. + * @param flowRegistry the {@link FlowDefinitionRegistry} to configure on the flow executor + * @return the created builder + */ + protected FlowExecutorBuilder getFlowExecutorBuilder(FlowDefinitionLocator flowRegistry) { + return new FlowExecutorBuilder(flowRegistry, this.applicationContext); + } + + /** + * Return a builder for creating a {@link FlowDefinitionRegistry} instance. + * @return the created builder + */ + protected FlowDefinitionRegistryBuilder getFlowDefinitionRegistryBuilder() { + return new FlowDefinitionRegistryBuilder(this.applicationContext); + } + + /** + * Return a builder for creating a {@link FlowDefinitionRegistry} instance. + * @param flowBuilderServices the {@link FlowBuilderServices} to configure on the flow registry with + * @return the created builder + */ + protected FlowDefinitionRegistryBuilder getFlowDefinitionRegistryBuilder(FlowBuilderServices flowBuilderServices) { + return new FlowDefinitionRegistryBuilder(this.applicationContext, flowBuilderServices); + } + + /** + * Return a builder for creating a {@link FlowBuilderServices} instance. + * @return the created builder + */ + protected FlowBuilderServicesBuilder getFlowBuilderServicesBuilder() { + return new FlowBuilderServicesBuilder(this.applicationContext); + } + +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/config/FlowBuilderServicesBuilder.java b/spring-webflow/src/main/java/org/springframework/webflow/config/FlowBuilderServicesBuilder.java new file mode 100644 index 00000000..cb7db463 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/config/FlowBuilderServicesBuilder.java @@ -0,0 +1,158 @@ +/* + * Copyright 2004-2014 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.binding.convert.ConversionService; +import org.springframework.binding.convert.service.DefaultConversionService; +import org.springframework.binding.expression.ExpressionParser; +import org.springframework.context.ApplicationContext; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.util.Assert; +import org.springframework.validation.Validator; +import org.springframework.webflow.engine.builder.ViewFactoryCreator; +import org.springframework.webflow.engine.builder.support.FlowBuilderServices; +import org.springframework.webflow.expression.spel.WebFlowSpringELExpressionParser; +import org.springframework.webflow.mvc.builder.MvcViewFactoryCreator; +import org.springframework.webflow.validation.BeanValidationHintResolver; +import org.springframework.webflow.validation.ValidationHintResolver; + +/** + * A builder for creating {@link FlowBuilderServices} instances designed for programmatic + * use in {@code @Bean} factory methods. For XML configuration consider using the + * {@code webflow-config} XML namespace. + * + * @author Rossen Stoyanchev + * @since 2.4 + */ +public class FlowBuilderServicesBuilder { + + private ConversionService conversionService = new DefaultConversionService(); + + private ExpressionParser expressionParser; + + private ViewFactoryCreator viewFactoryCreator; + + private Validator validator; + + private ValidationHintResolver validationHintResolver; + + private boolean enableDevelopmentMode; + + + /** + * Create a new instance with the given ApplicationContext. + * + * @param applicationContext the ApplicationContext to use to initialize a + * default ViewFactoryCreator instance with. + */ + public FlowBuilderServicesBuilder(ApplicationContext applicationContext) { + Assert.notNull(applicationContext, "applicationContext is required"); + this.viewFactoryCreator = initViewFactoryCreator(applicationContext); + } + + private static ViewFactoryCreator initViewFactoryCreator(ApplicationContext applicationContext) { + MvcViewFactoryCreator viewFactoryCreator = new MvcViewFactoryCreator(); + viewFactoryCreator.setApplicationContext(applicationContext); + return viewFactoryCreator; + } + + + /** + * Set the {@link ConversionService} to use. + * By default a {@link DefaultConversionService} instance is used. + * @param conversionService the conversion service + */ + public FlowBuilderServicesBuilder setConversionService(ConversionService conversionService) { + this.conversionService = conversionService; + return this; + } + + /** + * Set the {@link ExpressionParser} to use. + * By default a {@link WebFlowSpringELExpressionParser} with SpEL expressions is used. + * @param expressionParser the expression parser to use + */ + public FlowBuilderServicesBuilder setExpressionParser(ExpressionParser expressionParser) { + this.expressionParser = expressionParser; + return this; + } + + /** + * Set a custom {@link ViewFactoryCreator} to use for rendering. + * By default an {@link MvcViewFactoryCreator} instance is used. + * @param viewFactoryCreator the ViewFactory creator to use + */ + public FlowBuilderServicesBuilder setViewFactoryCreator(ViewFactoryCreator viewFactoryCreator) { + this.viewFactoryCreator = viewFactoryCreator; + return this; + } + + /** + * Set the {@link Validator} to use for validating a model declared on a view state. + * By default bean validation (JSR-303) is enabled if a bean validation provider is + * present on the classpath. + * @param validator the validator to use + */ + public FlowBuilderServicesBuilder setValidator(Validator validator) { + this.validator = validator; + return this; + } + + /** + * The {@link ValidationHintResolver} to use to resolve validation hints such as bean validation groups. + * By default a {@link BeanValidationHintResolver} is used. + * @param resolver the resolver to use + */ + public FlowBuilderServicesBuilder setValidationHintResolver(ValidationHintResolver resolver) { + this.validationHintResolver = resolver; + return this; + } + + /** + * Put all flows in development mode. When set to {@code true}, changes to a flow + * definition are auto-detected and result in a flow refresh. + * By default this is set to {@code false} + * @param enableDevelopmentMode whether to enable development mode + */ + public FlowBuilderServicesBuilder setDevelopmentMode(boolean enableDevelopmentMode) { + this.enableDevelopmentMode = enableDevelopmentMode; + return this; + } + + /** + * Create and return a {@link FlowBuilderServices} instance. + */ + public FlowBuilderServices build() { + FlowBuilderServices flowBuilderServices = new FlowBuilderServices(); + flowBuilderServices.setConversionService(this.conversionService); + flowBuilderServices.setExpressionParser(getExpressionParser()); + flowBuilderServices.setViewFactoryCreator(this.viewFactoryCreator); + flowBuilderServices.setValidator(this.validator); + flowBuilderServices.setValidationHintResolver(this.validationHintResolver); + flowBuilderServices.setDevelopment(this.enableDevelopmentMode); + return flowBuilderServices; + } + + private ExpressionParser getExpressionParser() { + if (this.expressionParser != null) { + return this.expressionParser; + } + else { + return new WebFlowSpringELExpressionParser(new SpelExpressionParser(), this.conversionService); + } + } + +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/config/FlowDefinitionRegistryBuilder.java b/spring-webflow/src/main/java/org/springframework/webflow/config/FlowDefinitionRegistryBuilder.java new file mode 100644 index 00000000..2f330b09 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/config/FlowDefinitionRegistryBuilder.java @@ -0,0 +1,363 @@ +/* + * Copyright 2004-2014 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 java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.springframework.context.ApplicationContext; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; +import org.springframework.webflow.core.collection.AttributeMap; +import org.springframework.webflow.core.collection.LocalAttributeMap; +import org.springframework.webflow.definition.registry.FlowDefinitionRegistry; +import org.springframework.webflow.engine.builder.DefaultFlowHolder; +import org.springframework.webflow.engine.builder.FlowAssembler; +import org.springframework.webflow.engine.builder.FlowBuilder; +import org.springframework.webflow.engine.builder.FlowBuilderContext; +import org.springframework.webflow.engine.builder.model.FlowModelFlowBuilder; +import org.springframework.webflow.engine.builder.support.FlowBuilderContextImpl; +import org.springframework.webflow.engine.builder.support.FlowBuilderServices; +import org.springframework.webflow.engine.model.builder.DefaultFlowModelHolder; +import org.springframework.webflow.engine.model.builder.FlowModelBuilder; +import org.springframework.webflow.engine.model.builder.xml.XmlFlowModelBuilder; +import org.springframework.webflow.engine.model.registry.FlowModelHolder; + +/** + * A builder for creating {@link FlowDefinitionRegistry} instances designed for programmatic + * use in {@code @Bean} factory methods. For XML configuration consider using the + * {@code webflow-config} XML namespace. + * + * @author Rossen Stoyanchev + * @since 2.4 + */ +public class FlowDefinitionRegistryBuilder { + + private final List flowLocations = new ArrayList(); + + private final List flowLocationPatterns = new ArrayList(); + + private final List flowBuilderInfos = new ArrayList(); + + private FlowBuilderServices flowBuilderServices; + + private FlowDefinitionRegistry parent; + + private FlowDefinitionResourceFactory flowResourceFactory; + + + /** + * Create a new instance with the given ApplicationContext. + * + * @param applicationContext the ApplicationContext to use for initializing the + * FlowDefinitionResourceFactory and FlowBuilderServices instances with + */ + public FlowDefinitionRegistryBuilder(ApplicationContext appContext) { + this(appContext, null); + } + + /** + * Create a new instance with the given ApplicationContext and {@link FlowBuilderServices}. + * + * @param applicationContext the ApplicationContext to use for initializing the + * FlowDefinitionResourceFactory and FlowBuilderServices instances with + * @param builderServices a {@link FlowBuilderServices} instance to configure + * on the FlowDefinitionRegistry + */ + public FlowDefinitionRegistryBuilder(ApplicationContext appContext, FlowBuilderServices builderServices) { + Assert.notNull(appContext, "applicationContext is required"); + this.flowResourceFactory = new FlowDefinitionResourceFactory(appContext); + if (builderServices != null) { + this.flowBuilderServices = builderServices; + } + else { + this.flowBuilderServices = new FlowBuilderServicesBuilder(appContext).build(); + this.flowBuilderServices.setApplicationContext(appContext); + } + } + + + /** + * Configure the base path where flow definitions are found. When specified, all + * flow locations are relative to this path. Also when specified, by default flows + * are assigned an id equal to the the path segment between their base path and + * file name. + *

+ * For example, if a flow definition is located at + * '/WEB-INF/hotels/booking/booking-flow.xml' and the base path is '/WEB-INF', the + * remaining path to this flow is 'hotels/booking' which then becomes the flow id. + *

+ * If a flow definition is found directly on the base path, the file name minus + * its extension is used as the flow id. + * @param basePath the base path to use + */ + public FlowDefinitionRegistryBuilder setBasePath(String basePath) { + if (basePath != null) { + this.flowResourceFactory.setBasePath(basePath); + } + return this; + } + + /** + * Register a flow defined at the following location as an .xml file. + * This may be a path to a single resource or a ANT-style path expression that + * matches multiple resources. + * @param path the resource path to the externalized flow definition resource. + */ + public FlowDefinitionRegistryBuilder addFlowLocation(String path) { + this.addFlowLocation(path, null, null); + return this; + } + + /** + * Register a flow defined at the following location as an .xml file. + * This may be a path to a single resource or a ANT-style path expression that + * matches multiple resources. + * @param path the resource path to the externalized flow definition resource. + * @param id the unique id to assign to the added flow definition in the registry + * Specify only if you wish to provide a custom flow definition identifier. + */ + public FlowDefinitionRegistryBuilder addFlowLocation(String path, String id) { + this.flowLocations.add(new FlowLocation(path, id, null)); + return this; + } + + /** + * Register a flow defined at the following location as an .xml file. + * This may be a path to a single resource or a ANT-style path expression that + * matches multiple resources. + * @param path the resource path to the externalized flow definition resource. + * @param id the unique id to assign to the added flow definition in the registry + * Specify only if you wish to provide a custom flow definition identifier. + * @param attributes meta-attributes to assign to the flow definition + */ + public FlowDefinitionRegistryBuilder addFlowLocation(String path, String id, Map attributes) { + this.flowLocations.add(new FlowLocation(path, id, attributes)); + return this; + } + + /** + * Registers a set of flows resolved from a resource location pattern. + * @param pattern the pattern to use + */ + public FlowDefinitionRegistryBuilder addFlowLocationPattern(String pattern) { + this.flowLocationPatterns.add(pattern); + return this; + } + + /** + * Set the {@link FlowBuilderServices} to use for defining custom services needed + * to build the flows registered in this registry. + * @param flowBuilderServices the {@link FlowBuilderServices} instance + */ + public FlowDefinitionRegistryBuilder setFlowBuilderServices(FlowBuilderServices flowBuilderServices) { + this.flowBuilderServices = flowBuilderServices; + return this; + } + + /** + * Register a custom {@link FlowBuilder} instance. + * @param builder the FlowBuilder to configure + */ + public FlowDefinitionRegistryBuilder addFlowBuilder(FlowBuilder builder) { + addFlowBuilder(builder, null, null); + return this; + } + + /** + * Register a custom {@link FlowBuilder} instance with the given flow id. + * @param builder the FlowBuilder to configure + * @param id the id assign to the flow definition in this registry. + * Specify when you wish to provide a custom flow definition identifier. + */ + public FlowDefinitionRegistryBuilder addFlowBuilder(FlowBuilder builder, String id) { + addFlowBuilder(builder, id, null); + return this; + } + + /** + * Register a custom {@link FlowBuilder} instance with the given flow id. + * @param builder the FlowBuilder to configure + * @param id the id assign to the flow definition in this registry. + * Specify when you wish to provide a custom flow definition identifier. + * @param attributes attributes to assign to the flow definition. + */ + public FlowDefinitionRegistryBuilder addFlowBuilder(FlowBuilder builder, String id, Map attributes) { + if (!StringUtils.hasText(id)) { + id = StringUtils.uncapitalize(StringUtils.delete( + ClassUtils.getShortName(builder.getClass()), "FlowBuilder")); + } + this.flowBuilderInfos.add(new FlowBuilderInfo(builder, id, attributes)); + return this; + } + + /** + * Configure a parent registry. Registries can be organized in a hierarchy. + * If a child registry does not contain a flow, its parent registry is queried. + * @param parent the parent registry + */ + public FlowDefinitionRegistryBuilder setParent(FlowDefinitionRegistry parent) { + this.parent = parent; + return this; + } + + /** + * Create and return a {@link FlowDefinitionRegistry} instance. + */ + public FlowDefinitionRegistry build() { + + DefaultFlowRegistry flowRegistry = new DefaultFlowRegistry(); + flowRegistry.setParent(this.parent); + + registerFlowLocations(flowRegistry); + registerFlowLocationPatterns(flowRegistry); + registerFlowBuilders(flowRegistry); + + return flowRegistry; + } + + private void registerFlowLocations(DefaultFlowRegistry flowRegistry) { + for (FlowLocation location : this.flowLocations) { + String path = location.getPath(); + String id = location.getId(); + AttributeMap attributes = location.getAttributes(); + updateFlowAttributes(attributes); + FlowDefinitionResource resource = this.flowResourceFactory.createResource(path, attributes, id); + registerFlow(resource, flowRegistry); + } + } + + private void registerFlowLocationPatterns(DefaultFlowRegistry flowRegistry) { + for (String pattern : this.flowLocationPatterns) { + AttributeMap attributes = new LocalAttributeMap(); + updateFlowAttributes(attributes); + FlowDefinitionResource[] resources; + try { + resources = this.flowResourceFactory.createResources(pattern, attributes); + } catch (IOException e) { + IllegalStateException ise = new IllegalStateException( + "An I/O Exception occurred resolving the flow location pattern '" + pattern + "'"); + ise.initCause(e); + throw ise; + } + for (FlowDefinitionResource resource : resources) { + registerFlow(resource, flowRegistry); + } + } + } + + private void registerFlow(FlowDefinitionResource resource, DefaultFlowRegistry flowRegistry) { + FlowModelBuilder flowModelBuilder = null; + if (resource.getPath().getFilename().endsWith(".xml")) { + flowModelBuilder = new XmlFlowModelBuilder(resource.getPath(), flowRegistry.getFlowModelRegistry()); + } else { + throw new IllegalArgumentException(resource + + " is not a supported resource type; supported types are [.xml]"); + } + FlowModelHolder flowModelHolder = new DefaultFlowModelHolder(flowModelBuilder); + FlowBuilder flowBuilder = new FlowModelFlowBuilder(flowModelHolder); + FlowBuilderContext builderContext = new FlowBuilderContextImpl( + resource.getId(), resource.getAttributes(), flowRegistry, this.flowBuilderServices); + FlowAssembler assembler = new FlowAssembler(flowBuilder, builderContext); + DefaultFlowHolder flowHolder = new DefaultFlowHolder(assembler); + + flowRegistry.getFlowModelRegistry().registerFlowModel(resource.getId(), flowModelHolder); + flowRegistry.registerFlowDefinition(flowHolder); + } + + private void registerFlowBuilders(DefaultFlowRegistry flowRegistry) { + for (FlowBuilderInfo info : this.flowBuilderInfos) { + AttributeMap attributes = info.getAttributes(); + updateFlowAttributes(attributes); + FlowBuilderContext builderContext = new FlowBuilderContextImpl( + info.getId(), attributes, flowRegistry, this.flowBuilderServices); + FlowAssembler assembler = new FlowAssembler(info.getBuilder(), builderContext); + flowRegistry.registerFlowDefinition(assembler.assembleFlow()); + } + } + + private void updateFlowAttributes(AttributeMap attributes) { + if (this.flowBuilderServices.getDevelopment()) { + attributes.asMap().put("development", true); + } + } + + + private static class FlowLocation { + + private final String path; + + private final String id; + + private final AttributeMap attributes; + + + public FlowLocation(String path, String id, Map attributes) { + this.path = path; + this.id = id; + this.attributes = (attributes != null) ? + new LocalAttributeMap(attributes) : + new LocalAttributeMap(Collections.emptyMap()); + } + + public String getPath() { + return this.path; + } + + public String getId() { + return this.id; + } + + public AttributeMap getAttributes() { + return this.attributes; + } + } + + private static class FlowBuilderInfo { + + private final FlowBuilder builder; + + private final String id; + + private final AttributeMap attributes; + + public FlowBuilderInfo(FlowBuilder builder, String id, Map attributes) { + this.builder = builder; + this.id = id; + this.attributes = (attributes != null) ? + new LocalAttributeMap(attributes) : + new LocalAttributeMap(Collections.emptyMap()); + } + + public FlowBuilder getBuilder() { + return this.builder; + } + + public String getId() { + return this.id; + } + + public AttributeMap getAttributes() { + return this.attributes; + } + } + + +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/config/FlowExecutorBuilder.java b/spring-webflow/src/main/java/org/springframework/webflow/config/FlowExecutorBuilder.java new file mode 100644 index 00000000..cd69c0c0 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/config/FlowExecutorBuilder.java @@ -0,0 +1,227 @@ +/* + * Copyright 2004-2014 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.context.ApplicationContext; +import org.springframework.util.Assert; +import org.springframework.webflow.conversation.ConversationManager; +import org.springframework.webflow.conversation.impl.SessionBindingConversationManager; +import org.springframework.webflow.core.collection.LocalAttributeMap; +import org.springframework.webflow.definition.registry.FlowDefinitionLocator; +import org.springframework.webflow.engine.impl.FlowExecutionImplFactory; +import org.springframework.webflow.execution.FlowExecutionFactory; +import org.springframework.webflow.execution.FlowExecutionListener; +import org.springframework.webflow.execution.factory.ConditionalFlowExecutionListenerLoader; +import org.springframework.webflow.execution.factory.FlowExecutionListenerCriteriaFactory; +import org.springframework.webflow.execution.repository.impl.DefaultFlowExecutionRepository; +import org.springframework.webflow.execution.repository.snapshot.FlowExecutionSnapshotFactory; +import org.springframework.webflow.execution.repository.snapshot.SerializedFlowExecutionSnapshotFactory; +import org.springframework.webflow.execution.repository.snapshot.SimpleFlowExecutionSnapshotFactory; +import org.springframework.webflow.executor.FlowExecutor; +import org.springframework.webflow.executor.FlowExecutorImpl; +import org.springframework.webflow.mvc.builder.MvcEnvironment; + +/** + * A builder for {@link FlowExecutor} instances designed for programmatic use in + * {@code @Bean} factory methods. For XML configuration consider using the + * {@code webflow-config} XML namespace. + * + * @author Rossen Stoyanchev + * @since 2.4 + */ +public class FlowExecutorBuilder { + + private final FlowDefinitionLocator flowRegistry; + + private Integer maxFlowExecutions; + + private Integer maxFlowExecutionSnapshots; + + private MvcEnvironment environment; + + private LocalAttributeMap executionAttributes = new LocalAttributeMap(); + + private ConditionalFlowExecutionListenerLoader listenerLoader; + + private FlowExecutionListenerCriteriaFactory listenerCriteriaFactory = new FlowExecutionListenerCriteriaFactory(); + + private ConversationManager conversationManager; + + + /** + * Create a new instance with the given flow registry and ApplicationContext. + * + * @param flowRegistry the flow registry that will locate flow definitions + * @param applicationContext the Spring ApplicationContext to use for + * initializing an instance of {@link MvcEnvironment} + */ + public FlowExecutorBuilder(FlowDefinitionLocator flowRegistry, ApplicationContext applicationContext) { + Assert.notNull(flowRegistry, "FlowDefinitionLocator is required"); + Assert.notNull(applicationContext, "applicationContext is required"); + this.flowRegistry = flowRegistry; + this.environment = MvcEnvironment.environmentFor(applicationContext); + } + + + /** + * Set the maximum number of allowed flow executions per user. + * @param maxFlowExecutions the max flow executions + */ + public FlowExecutorBuilder setMaxFlowExecutions(int maxFlowExecutions) { + this.maxFlowExecutions = maxFlowExecutions; + return this; + } + + /** + * Set the maximum number of history snapshots allowed per flow execution. + * @param maxFlowExecutionSnapshots the max flow execution snapshots + */ + public FlowExecutorBuilder setMaxFlowExecutionSnapshots(int maxFlowExecutionSnapshots) { + this.maxFlowExecutionSnapshots = maxFlowExecutionSnapshots; + return this; + } + + /** + * Whether flow executions should redirect after they pause before rendering. + * @param redirectOnPause whether to redirect or not + */ + public FlowExecutorBuilder setAlwaysRedirectOnPause(boolean redirectOnPause) { + this.executionAttributes.put("alwaysRedirectOnPause", redirectOnPause); + return this; + } + + /** + * Whether 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 transitions. + * @param redirectInSameState whether to redirect or not + */ + public FlowExecutorBuilder setRedirectInSameState(boolean redirectInSameState) { + this.executionAttributes.put("redirectInSameState", redirectInSameState); + return this; + } + + /** + * Add a single flow execution meta attribute. + * @param name the attribute name + * @param value the attribute value + */ + public FlowExecutorBuilder addFlowExecutionAttribute(String name, Object value) { + this.executionAttributes.put(name, value); + return this; + } + + /** + * Register a {@link FlowExecutionListener} that observes the lifecycle of all flow + * executions launched by this executor. + * @param listener the listener to be registered + */ + public FlowExecutorBuilder addFlowExecutionListener(FlowExecutionListener listener) { + return addFlowExecutionListener(listener, "*"); + } + + /** + * Register a {@link FlowExecutionListener} that observes the lifecycle of flow + * executions launched by this executor. + * @param listener the listener to be registered + * @param criteria the criteria that determines the flow definitions a listener + * should observe, delimited by commas or '*' for "all". + * Example: 'flow1,flow2,flow3'. + */ + public FlowExecutorBuilder addFlowExecutionListener(FlowExecutionListener listener, String criteria) { + if (this.listenerLoader == null) { + this.listenerLoader = new ConditionalFlowExecutionListenerLoader(); + } + this.listenerLoader.addListener(listener, this.listenerCriteriaFactory.getListenerCriteria(criteria)); + return this; + } + + /** + * Set the ConversationManager implementation to use for storing conversations + * in the session effectively controlling how state is stored physically when + * a flow execution is paused.. Note that when this attribute is provided, the + * "max-execution-snapshots" attribute is meaningless. + * @param conversationManager the ConversationManager instance to use + */ + public FlowExecutorBuilder setConversationManager(ConversationManager conversationManager) { + this.conversationManager = conversationManager; + return this; + } + + /** + * Create and return a {@link FlowExecutor} instance. + */ + public FlowExecutor build() { + FlowExecutionImplFactory executionFactory = getExecutionFactory(); + DefaultFlowExecutionRepository executionRepository = getFlowExecutionRepository(executionFactory); + executionFactory.setExecutionKeyFactory(executionRepository); + return new FlowExecutorImpl(this.flowRegistry, executionFactory, executionRepository); + } + + + private FlowExecutionImplFactory getExecutionFactory() { + FlowExecutionImplFactory executionFactory = new FlowExecutionImplFactory(); + executionFactory.setExecutionAttributes(getExecutionAttributes()); + if (this.listenerLoader != null) { + executionFactory.setExecutionListenerLoader(this.listenerLoader); + } + return executionFactory; + } + + private DefaultFlowExecutionRepository getFlowExecutionRepository(FlowExecutionFactory executionFactory) { + ConversationManager manager = getConversationManager(); + FlowExecutionSnapshotFactory snapshotFactory = getSnapshotFactory(executionFactory); + DefaultFlowExecutionRepository repository = new DefaultFlowExecutionRepository(manager, snapshotFactory); + if (this.maxFlowExecutionSnapshots != null) { + repository.setMaxSnapshots((this.maxFlowExecutionSnapshots == 0) ? 1 : this.maxFlowExecutionSnapshots); + } + return repository; + } + + private ConversationManager getConversationManager() { + ConversationManager manager = this.conversationManager; + if (manager == null) { + manager = new SessionBindingConversationManager(); + } + if (this.maxFlowExecutions != null && manager instanceof SessionBindingConversationManager) { + ((SessionBindingConversationManager) manager).setMaxConversations(this.maxFlowExecutions); + } + return manager; + } + + private FlowExecutionSnapshotFactory getSnapshotFactory(FlowExecutionFactory executionFactory) { + FlowExecutionSnapshotFactory factory = null; + if (this.maxFlowExecutionSnapshots != null && this.maxFlowExecutionSnapshots == 0) { + factory = new SimpleFlowExecutionSnapshotFactory(executionFactory, this.flowRegistry); + } + else { + factory = new SerializedFlowExecutionSnapshotFactory(executionFactory, this.flowRegistry); + } + return factory; + } + + private LocalAttributeMap getExecutionAttributes() { + LocalAttributeMap attributes = new LocalAttributeMap(this.executionAttributes.asMap()); + if (!attributes.contains("alwaysRedirectOnPause")) { + attributes.put("alwaysRedirectOnPause", (this.environment != MvcEnvironment.PORTLET)); + } + if (!attributes.contains("redirectInSameState")) { + attributes.put("redirectInSameState", (this.environment != MvcEnvironment.PORTLET)); + } + return attributes; + } + +} diff --git a/spring-webflow/src/main/resources/org/springframework/webflow/config/spring-webflow-config-2.4.xsd b/spring-webflow/src/main/resources/org/springframework/webflow/config/spring-webflow-config-2.4.xsd index 321cd0b1..2a5f5f3d 100644 --- a/spring-webflow/src/main/resources/org/springframework/webflow/config/spring-webflow-config-2.4.xsd +++ b/spring-webflow/src/main/resources/org/springframework/webflow/config/spring-webflow-config-2.4.xsd @@ -411,7 +411,7 @@ Determines if flow executions always redirect after they pause. diff --git a/spring-webflow/src/test/java/org/springframework/webflow/config/AbstractFlowBuilderServicesConfigurationTests.java b/spring-webflow/src/test/java/org/springframework/webflow/config/AbstractFlowBuilderServicesConfigurationTests.java new file mode 100644 index 00000000..1ceef698 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/config/AbstractFlowBuilderServicesConfigurationTests.java @@ -0,0 +1,118 @@ +package org.springframework.webflow.config; + +import java.util.Set; + +import junit.framework.TestCase; + +import org.springframework.binding.convert.ConversionException; +import org.springframework.binding.convert.ConversionExecutionException; +import org.springframework.binding.convert.ConversionExecutor; +import org.springframework.binding.convert.ConversionExecutorNotFoundException; +import org.springframework.binding.convert.ConversionService; +import org.springframework.binding.convert.service.DefaultConversionService; +import org.springframework.binding.expression.Expression; +import org.springframework.binding.expression.ExpressionParser; +import org.springframework.binding.expression.spel.SpringELExpressionParser; +import org.springframework.context.ApplicationContext; +import org.springframework.validation.Validator; +import org.springframework.webflow.engine.builder.BinderConfiguration; +import org.springframework.webflow.engine.builder.ViewFactoryCreator; +import org.springframework.webflow.engine.builder.support.FlowBuilderServices; +import org.springframework.webflow.execution.ViewFactory; +import org.springframework.webflow.mvc.builder.MvcViewFactoryCreator; +import org.springframework.webflow.validation.ValidationHintResolver; + +public abstract class AbstractFlowBuilderServicesConfigurationTests extends TestCase { + + protected ApplicationContext context; + + protected FlowBuilderServices builderServices; + + public void setUp() { + context = initApplicationContext(); + } + + protected abstract ApplicationContext initApplicationContext(); + + public void testFlowBuilderServicesDefaultConfig() { + builderServices = (FlowBuilderServices) context.getBean("flowBuilderServicesDefault"); + assertNotNull(builderServices); + assertTrue(builderServices.getExpressionParser() instanceof SpringELExpressionParser); + assertTrue(builderServices.getViewFactoryCreator() instanceof MvcViewFactoryCreator); + assertTrue(builderServices.getConversionService() instanceof DefaultConversionService); + assertNull(builderServices.getValidator()); + assertFalse(builderServices.getDevelopment()); + } + + public void testFlowBuilderServicesAllCustomized() { + builderServices = (FlowBuilderServices) context.getBean("flowBuilderServicesAllCustom"); + assertNotNull(builderServices); + assertTrue(builderServices.getExpressionParser() instanceof SpringELExpressionParser); + assertTrue(builderServices.getViewFactoryCreator() instanceof TestViewFactoryCreator); + assertTrue(builderServices.getConversionService() instanceof TestConversionService); + assertTrue(builderServices.getValidator() instanceof EmptySpringValidator); + assertTrue(builderServices.getValidationHintResolver() instanceof MyBeanValidationHintResolver); + assertTrue(builderServices.getDevelopment()); + } + + public void testFlowBuilderServicesConversionServiceCustomized() { + builderServices = (FlowBuilderServices) context.getBean("flowBuilderServicesConversionServiceCustom"); + assertNotNull(builderServices); + assertTrue(builderServices.getConversionService() instanceof TestConversionService); + assertTrue(builderServices.getExpressionParser() instanceof SpringELExpressionParser); + assertTrue(((SpringELExpressionParser) builderServices.getExpressionParser()).getConversionService() instanceof TestConversionService); + assertTrue(builderServices.getViewFactoryCreator() instanceof MvcViewFactoryCreator); + assertNull(builderServices.getValidator()); + assertNull(builderServices.getValidationHintResolver()); + assertFalse(builderServices.getDevelopment()); + } + + public static class TestViewFactoryCreator implements ViewFactoryCreator { + + public ViewFactory createViewFactory(Expression viewIdExpression, ExpressionParser expressionParser, + ConversionService conversionService, BinderConfiguration binderConfiguration, + Validator validator, ValidationHintResolver validationHintResolver) { + throw new UnsupportedOperationException("Auto-generated method stub"); + } + + public String getViewIdByConvention(String viewStateId) { + return viewStateId; + } + + } + + public static class TestConversionService implements ConversionService { + + public Object executeConversion(Object source, Class targetClass) throws ConversionException { + throw new UnsupportedOperationException("Auto-generated method stub"); + } + + public Object executeConversion(String converterId, Object source, Class targetClass) { + throw new UnsupportedOperationException("Auto-generated method stub"); + } + + public ConversionExecutor getConversionExecutor(Class sourceClass, Class targetClass) + throws ConversionExecutionException { + throw new UnsupportedOperationException("Auto-generated method stub"); + } + + public ConversionExecutor getConversionExecutor(String id, Class sourceClass, Class targetClass) + throws ConversionExecutorNotFoundException { + throw new UnsupportedOperationException("Auto-generated method stub"); + } + + public Set getConversionExecutors(Class sourceClass) { + throw new UnsupportedOperationException("Auto-generated method stub"); + } + + public Class getClassForAlias(String alias) throws ConversionExecutionException { + throw new UnsupportedOperationException("Auto-generated method stub"); + } + + public org.springframework.core.convert.ConversionService getDelegateConversionService() { + throw new UnsupportedOperationException("Auto-generated method stub"); + } + + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/config/AbstractFlowExecutorConfigurationTests.java b/spring-webflow/src/test/java/org/springframework/webflow/config/AbstractFlowExecutorConfigurationTests.java new file mode 100644 index 00000000..77154c89 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/config/AbstractFlowExecutorConfigurationTests.java @@ -0,0 +1,73 @@ +package org.springframework.webflow.config; + +import junit.framework.TestCase; + +import org.springframework.context.ApplicationContext; +import org.springframework.webflow.conversation.Conversation; +import org.springframework.webflow.conversation.ConversationException; +import org.springframework.webflow.conversation.ConversationId; +import org.springframework.webflow.conversation.ConversationManager; +import org.springframework.webflow.conversation.ConversationParameters; +import org.springframework.webflow.core.collection.AttributeMap; +import org.springframework.webflow.definition.FlowDefinition; +import org.springframework.webflow.execution.FlowExecutionListenerAdapter; +import org.springframework.webflow.execution.RequestContext; +import org.springframework.webflow.executor.FlowExecutor; +import org.springframework.webflow.executor.FlowExecutorImpl; +import org.springframework.webflow.test.MockExternalContext; + +public abstract class AbstractFlowExecutorConfigurationTests extends TestCase { + + private ApplicationContext context; + + public void setUp() { + context = initApplicationContext(); + } + + protected abstract ApplicationContext initApplicationContext(); + + + public void testConfigOk() { + FlowExecutor executor = context.getBean("flowExecutor", FlowExecutor.class); + executor.launchExecution("flow", null, new MockExternalContext()); + FlowExecutor executor2 = context.getBean("flowExecutorSimpleRepo", FlowExecutor.class); + executor2.launchExecution("flow", null, new MockExternalContext()); + } + + public void testCustomConversationManager() { + FlowExecutorImpl executor = context.getBean("flowExecutor", FlowExecutorImpl.class); + try { + executor.getExecutionRepository().parseFlowExecutionKey("e1s1"); + fail("ExceptionThrowingConversationManager would have raised an exception"); + } catch (UnsupportedOperationException e) { + } + } + + public static class ConfigurationListener extends FlowExecutionListenerAdapter { + + public void sessionCreating(RequestContext context, FlowDefinition definition) { + AttributeMap attributes = context.getFlowExecutionContext().getAttributes(); + assertEquals(4, attributes.size()); + assertEquals(Boolean.FALSE, attributes.getBoolean("alwaysRedirectOnPause")); + assertEquals(Boolean.TRUE, attributes.getBoolean("redirectInSameState")); + assertEquals("bar", attributes.get("foo")); + assertEquals(new Integer(2), attributes.get("bar")); + } + } + + public static class ExceptionThrowingConversationManager implements ConversationManager { + + public Conversation beginConversation(ConversationParameters params) throws ConversationException { + throw new UnsupportedOperationException(); + } + + public Conversation getConversation(ConversationId id) throws ConversationException { + throw new UnsupportedOperationException(); + } + + public ConversationId parseConversationId(String encodedId) throws ConversationException { + throw new UnsupportedOperationException(); + } + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/config/AbstractFlowRegistryConfigurationTests.java b/spring-webflow/src/test/java/org/springframework/webflow/config/AbstractFlowRegistryConfigurationTests.java new file mode 100644 index 00000000..63342c6c --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/config/AbstractFlowRegistryConfigurationTests.java @@ -0,0 +1,77 @@ +package org.springframework.webflow.config; + +import junit.framework.TestCase; + +import org.springframework.context.ApplicationContext; +import org.springframework.webflow.definition.FlowDefinition; +import org.springframework.webflow.definition.registry.FlowDefinitionConstructionException; +import org.springframework.webflow.definition.registry.FlowDefinitionRegistry; +import org.springframework.webflow.definition.registry.NoSuchFlowDefinitionException; + +public abstract class AbstractFlowRegistryConfigurationTests extends TestCase { + + protected ApplicationContext context; + + protected FlowDefinitionRegistry registry; + + public void setUp() { + this.context = initApplicationContext(); + this.registry = (FlowDefinitionRegistry) context.getBean("flowRegistry"); + } + + protected abstract ApplicationContext initApplicationContext(); + + + public void testRegistryFlowLocationsPopulated() { + FlowDefinition flow = registry.getFlowDefinition("flow"); + assertEquals("flow", flow.getId()); + assertEquals("bar", flow.getAttributes().get("foo")); + assertEquals(new Integer(2), flow.getAttributes().get("bar")); + } + + public void testRegistryFlowLocationPatternsPopulated() { + FlowDefinition flow1 = registry.getFlowDefinition("flow1"); + assertEquals("flow1", flow1.getId()); + FlowDefinition flow2 = registry.getFlowDefinition("flow2"); + assertEquals("flow2", flow2.getId()); + } + + public void testRegistryFlowBuildersPopulated() { + FlowDefinition foo = registry.getFlowDefinition("foo"); + assertEquals("foo", foo.getId()); + } + + public void testRegistryFlowBuildersPopulatedWithId() { + FlowDefinition foo = registry.getFlowDefinition("foo2"); + assertEquals("foo2", foo.getId()); + } + + public void testRegistryFlowBuildersPopulatedWithAttributes() { + FlowDefinition foo3 = registry.getFlowDefinition("foo3"); + assertEquals("foo3", foo3.getId()); + assertEquals("bar", foo3.getAttributes().get("foo")); + assertEquals(new Integer(2), foo3.getAttributes().get("bar")); + } + + public void testNoSuchFlow() { + try { + registry.getFlowDefinition("not there"); + } catch (NoSuchFlowDefinitionException e) { + + } + } + + public void testBogusPath() { + try { + registry.getFlowDefinition("bogus"); + fail("Should have failed"); + } catch (FlowDefinitionConstructionException e) { + } + } + + public void testParent() { + assertNotNull(registry.getParent()); + assertEquals("parentFlow", registry.getParent().getFlowDefinition("parentFlow").getId()); + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/config/FlowBuilderServicesBeanDefinitionParserTests.java b/spring-webflow/src/test/java/org/springframework/webflow/config/FlowBuilderServicesBeanDefinitionParserTests.java index 393049b0..56f73e79 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/config/FlowBuilderServicesBeanDefinitionParserTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/config/FlowBuilderServicesBeanDefinitionParserTests.java @@ -1,115 +1,13 @@ package org.springframework.webflow.config; -import java.util.Set; - -import junit.framework.TestCase; - -import org.springframework.binding.convert.ConversionException; -import org.springframework.binding.convert.ConversionExecutionException; -import org.springframework.binding.convert.ConversionExecutor; -import org.springframework.binding.convert.ConversionExecutorNotFoundException; -import org.springframework.binding.convert.ConversionService; -import org.springframework.binding.convert.service.DefaultConversionService; -import org.springframework.binding.expression.Expression; -import org.springframework.binding.expression.ExpressionParser; -import org.springframework.binding.expression.spel.SpringELExpressionParser; +import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.springframework.validation.Validator; -import org.springframework.webflow.engine.builder.BinderConfiguration; -import org.springframework.webflow.engine.builder.ViewFactoryCreator; -import org.springframework.webflow.engine.builder.support.FlowBuilderServices; -import org.springframework.webflow.execution.ViewFactory; -import org.springframework.webflow.mvc.builder.MvcViewFactoryCreator; -import org.springframework.webflow.validation.ValidationHintResolver; -public class FlowBuilderServicesBeanDefinitionParserTests extends TestCase { - - private ClassPathXmlApplicationContext context; - private FlowBuilderServices builderServices; - - public void setUp() { - context = new ClassPathXmlApplicationContext("org/springframework/webflow/config/flow-builder-services.xml"); - } - - public void testFlowBuilderServicesDefaultConfig() { - builderServices = (FlowBuilderServices) context.getBean("flowBuilderServicesDefault"); - assertNotNull(builderServices); - assertTrue(builderServices.getExpressionParser() instanceof SpringELExpressionParser); - assertTrue(builderServices.getViewFactoryCreator() instanceof MvcViewFactoryCreator); - assertTrue(builderServices.getConversionService() instanceof DefaultConversionService); - assertNull(builderServices.getValidator()); - assertFalse(builderServices.getDevelopment()); - } - - public void testFlowBuilderServicesAllCustomized() { - builderServices = (FlowBuilderServices) context.getBean("flowBuilderServicesAllCustom"); - assertNotNull(builderServices); - assertTrue(builderServices.getExpressionParser() instanceof SpringELExpressionParser); - assertTrue(builderServices.getViewFactoryCreator() instanceof TestViewFactoryCreator); - assertTrue(builderServices.getConversionService() instanceof TestConversionService); - assertTrue(builderServices.getValidator() instanceof EmptySpringValidator); - assertTrue(builderServices.getValidationHintResolver() instanceof MyBeanValidationHintResolver); - assertTrue(builderServices.getDevelopment()); - } - - public void testFlowBuilderServicesConversionServiceCustomized() { - builderServices = (FlowBuilderServices) context.getBean("flowBuilderServicesConversionServiceCustom"); - assertNotNull(builderServices); - assertTrue(builderServices.getConversionService() instanceof TestConversionService); - assertTrue(builderServices.getExpressionParser() instanceof SpringELExpressionParser); - assertTrue(((SpringELExpressionParser) builderServices.getExpressionParser()).getConversionService() instanceof TestConversionService); - assertTrue(builderServices.getViewFactoryCreator() instanceof MvcViewFactoryCreator); - assertNull(builderServices.getValidator()); - assertNull(builderServices.getValidationHintResolver()); - assertFalse(builderServices.getDevelopment()); - } - - public static class TestViewFactoryCreator implements ViewFactoryCreator { - - public ViewFactory createViewFactory(Expression viewIdExpression, ExpressionParser expressionParser, - ConversionService conversionService, BinderConfiguration binderConfiguration, - Validator validator, ValidationHintResolver validationHintResolver) { - throw new UnsupportedOperationException("Auto-generated method stub"); - } - - public String getViewIdByConvention(String viewStateId) { - return viewStateId; - } - - } - - public static class TestConversionService implements ConversionService { - - public Object executeConversion(Object source, Class targetClass) throws ConversionException { - throw new UnsupportedOperationException("Auto-generated method stub"); - } - - public Object executeConversion(String converterId, Object source, Class targetClass) { - throw new UnsupportedOperationException("Auto-generated method stub"); - } - - public ConversionExecutor getConversionExecutor(Class sourceClass, Class targetClass) - throws ConversionExecutionException { - throw new UnsupportedOperationException("Auto-generated method stub"); - } - - public ConversionExecutor getConversionExecutor(String id, Class sourceClass, Class targetClass) - throws ConversionExecutorNotFoundException { - throw new UnsupportedOperationException("Auto-generated method stub"); - } - - public Set getConversionExecutors(Class sourceClass) { - throw new UnsupportedOperationException("Auto-generated method stub"); - } - - public Class getClassForAlias(String alias) throws ConversionExecutionException { - throw new UnsupportedOperationException("Auto-generated method stub"); - } - - public org.springframework.core.convert.ConversionService getDelegateConversionService() { - throw new UnsupportedOperationException("Auto-generated method stub"); - } +public class FlowBuilderServicesBeanDefinitionParserTests extends AbstractFlowBuilderServicesConfigurationTests { + @Override + protected ApplicationContext initApplicationContext() { + return new ClassPathXmlApplicationContext("org/springframework/webflow/config/flow-builder-services.xml"); } } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/config/FlowBuilderServicesJavaConfigTests.java b/spring-webflow/src/test/java/org/springframework/webflow/config/FlowBuilderServicesJavaConfigTests.java new file mode 100644 index 00000000..896f0c4e --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/config/FlowBuilderServicesJavaConfigTests.java @@ -0,0 +1,74 @@ +package org.springframework.webflow.config; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.webflow.definition.registry.FlowDefinitionRegistry; +import org.springframework.webflow.engine.builder.support.FlowBuilderServices; +import org.springframework.webflow.expression.spel.WebFlowSpringELExpressionParser; + +public class FlowBuilderServicesJavaConfigTests extends AbstractFlowBuilderServicesConfigurationTests { + + protected ApplicationContext initApplicationContext() { + return new AnnotationConfigApplicationContext(WebFlowConfig.class); + } + + + static class WebFlowConfig extends AbstractFlowConfiguration { + + @Bean + public FlowBuilderServices flowBuilderServicesDefault() { + return getFlowBuilderServicesBuilder().build(); + } + + @Bean + public FlowBuilderServices flowBuilderServicesAllCustom() { + return getFlowBuilderServicesBuilder() + .setExpressionParser(customExpressionParser()) + .setViewFactoryCreator(customViewFactoryCreator()) + .setConversionService(customConversionService()) + .setValidator(customValidator()) + .setValidationHintResolver(customValidationHintResolver()) + .setDevelopmentMode(true) + .build(); + } + + @Bean + public FlowBuilderServices flowBuilderServicesConversionServiceCustom() { + return getFlowBuilderServicesBuilder() + .setConversionService(customConversionService()) + .build(); + } + + @Bean + public WebFlowSpringELExpressionParser customExpressionParser() { + return new WebFlowSpringELExpressionParser(new SpelExpressionParser()); + } + + @Bean + public TestViewFactoryCreator customViewFactoryCreator() { + return new TestViewFactoryCreator(); + } + + @Bean + public TestConversionService customConversionService() { + return new TestConversionService(); + } + + @Bean + public EmptySpringValidator customValidator() { + return new EmptySpringValidator(); + } + + @Bean + public MyBeanValidationHintResolver customValidationHintResolver() { + return new MyBeanValidationHintResolver(); + } + + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/config/FlowDefinitionRegistryJavaConfigTests.java b/spring-webflow/src/test/java/org/springframework/webflow/config/FlowDefinitionRegistryJavaConfigTests.java new file mode 100644 index 00000000..960738b5 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/config/FlowDefinitionRegistryJavaConfigTests.java @@ -0,0 +1,46 @@ +package org.springframework.webflow.config; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.webflow.definition.registry.FlowDefinitionRegistry; + +public class FlowDefinitionRegistryJavaConfigTests extends AbstractFlowRegistryConfigurationTests { + + protected ApplicationContext initApplicationContext() { + return new AnnotationConfigApplicationContext(WebFlowConfig.class); + } + + + static class WebFlowConfig extends AbstractFlowConfiguration { + + @Bean + public FlowDefinitionRegistry flowRegistry() { + + Map flowAttributes = new HashMap(); + flowAttributes.put("foo", "bar"); + flowAttributes.put("bar", 2); + + return getFlowDefinitionRegistryBuilder().setParent(parentRegistry()) + .addFlowLocation("org/springframework/webflow/config/flow.xml", "flow", flowAttributes) + .addFlowLocation("/some/path/that/is/bogus.xml") + .addFlowLocationPattern("org/springframework/webflow/config/flows/*.xml") + .addFlowBuilder(new FooFlowBuilder()) + .addFlowBuilder(new FooFlowBuilder(), "foo2") + .addFlowBuilder(new FooFlowBuilder(), "foo3", flowAttributes) + .build(); + } + + @Bean + public FlowDefinitionRegistry parentRegistry() { + return getFlowDefinitionRegistryBuilder() + .addFlowLocation("org/springframework/webflow/config/flow.xml", "parentFlow") + .build(); + } + + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/config/FlowExecutorBeanDefinitionParserTests.java b/spring-webflow/src/test/java/org/springframework/webflow/config/FlowExecutorBeanDefinitionParserTests.java index c8c0e1d3..2d0252e9 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/config/FlowExecutorBeanDefinitionParserTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/config/FlowExecutorBeanDefinitionParserTests.java @@ -1,70 +1,12 @@ package org.springframework.webflow.config; -import junit.framework.TestCase; - +import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.springframework.webflow.conversation.Conversation; -import org.springframework.webflow.conversation.ConversationException; -import org.springframework.webflow.conversation.ConversationId; -import org.springframework.webflow.conversation.ConversationManager; -import org.springframework.webflow.conversation.ConversationParameters; -import org.springframework.webflow.definition.FlowDefinition; -import org.springframework.webflow.execution.FlowExecutionListenerAdapter; -import org.springframework.webflow.execution.RequestContext; -import org.springframework.webflow.executor.FlowExecutor; -import org.springframework.webflow.executor.FlowExecutorImpl; -import org.springframework.webflow.test.MockExternalContext; -public class FlowExecutorBeanDefinitionParserTests extends TestCase { - private ClassPathXmlApplicationContext context; - - public void setUp() { - context = new ClassPathXmlApplicationContext("org/springframework/webflow/config/flow-executor.xml"); - } - - public void testConfigOk() { - FlowExecutor executor = context.getBean("flowExecutor", FlowExecutor.class); - executor.launchExecution("flow", null, new MockExternalContext()); - FlowExecutor executor2 = context.getBean("flowExecutorSimpleRepo", FlowExecutor.class); - executor2.launchExecution("flow", null, new MockExternalContext()); - } - - public void testCustomConversationManager() { - FlowExecutorImpl executor = context.getBean("flowExecutor", FlowExecutorImpl.class); - try { - executor.getExecutionRepository().parseFlowExecutionKey("e1s1"); - fail("ExceptionThrowingConversationManager would have raised an exception"); - } catch (UnsupportedOperationException e) { - } - } - - public static class ConfigurationListener extends FlowExecutionListenerAdapter { - public void sessionCreating(RequestContext context, FlowDefinition definition) { - 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")); - } - } - - public static class ExceptionThrowingConversationManager implements ConversationManager { - - public Conversation beginConversation(ConversationParameters conversationParameters) - throws ConversationException { - throw new UnsupportedOperationException(); - } - - public Conversation getConversation(ConversationId id) throws ConversationException { - throw new UnsupportedOperationException(); - } - - public ConversationId parseConversationId(String encodedId) throws ConversationException { - throw new UnsupportedOperationException(); - } +public class FlowExecutorBeanDefinitionParserTests extends AbstractFlowExecutorConfigurationTests { + protected ApplicationContext initApplicationContext() { + return new ClassPathXmlApplicationContext("org/springframework/webflow/config/flow-executor.xml"); } } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/config/FlowExecutorJavaConfigTests.java b/spring-webflow/src/test/java/org/springframework/webflow/config/FlowExecutorJavaConfigTests.java new file mode 100644 index 00000000..97a9ec1e --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/config/FlowExecutorJavaConfigTests.java @@ -0,0 +1,68 @@ +/* + * Copyright 2004-2014 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.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.webflow.definition.registry.FlowDefinitionRegistry; +import org.springframework.webflow.executor.FlowExecutor; + +/** + * Tests for configuring Web Flow by extending {@link AbstractFlowConfiguration}. + * + * @author Rossen Stoyanchev + */ +public class FlowExecutorJavaConfigTests extends AbstractFlowExecutorConfigurationTests { + + @Override + protected ApplicationContext initApplicationContext() { + return new AnnotationConfigApplicationContext(WebFlowConfig.class); + } + + + @Configuration + static class WebFlowConfig extends AbstractFlowConfiguration { + + @Bean + public FlowExecutor flowExecutor() { + return getFlowExecutorBuilder(flowRegistry()) + .setMaxFlowExecutions(1).setMaxFlowExecutionSnapshots(2) + .setConversationManager(new ExceptionThrowingConversationManager()) + .setAlwaysRedirectOnPause(false) + .setRedirectInSameState(true) + .addFlowExecutionAttribute("foo", "bar") + .addFlowExecutionAttribute("bar", 2) + .addFlowExecutionListener(new ConfigurationListener(), "*") + .build(); + } + + @Bean + public FlowDefinitionRegistry flowRegistry() { + return getFlowDefinitionRegistryBuilder() + .addFlowLocation("org/springframework/webflow/config/flow.xml").build(); + } + + @Bean + public FlowExecutor flowExecutorSimpleRepo() { + return getFlowExecutorBuilder(flowRegistry()) + .setMaxFlowExecutions(1).setMaxFlowExecutionSnapshots(0) + .build(); + } + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/config/FlowRegistryBeanDefinitionParserTests.java b/spring-webflow/src/test/java/org/springframework/webflow/config/FlowRegistryBeanDefinitionParserTests.java index 9a05a183..372ce7b3 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/config/FlowRegistryBeanDefinitionParserTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/config/FlowRegistryBeanDefinitionParserTests.java @@ -2,56 +2,17 @@ package org.springframework.webflow.config; import java.util.Map; -import junit.framework.TestCase; - import org.springframework.binding.convert.service.DefaultConversionService; import org.springframework.binding.expression.spel.SpringELExpressionParser; +import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.springframework.webflow.definition.FlowDefinition; -import org.springframework.webflow.definition.registry.FlowDefinitionConstructionException; -import org.springframework.webflow.definition.registry.FlowDefinitionRegistry; -import org.springframework.webflow.definition.registry.NoSuchFlowDefinitionException; import org.springframework.webflow.engine.builder.support.FlowBuilderServices; import org.springframework.webflow.mvc.builder.MvcViewFactoryCreator; -public class FlowRegistryBeanDefinitionParserTests extends TestCase { - private ClassPathXmlApplicationContext context; - private FlowDefinitionRegistry registry; +public class FlowRegistryBeanDefinitionParserTests extends AbstractFlowRegistryConfigurationTests { - public void setUp() { - context = new ClassPathXmlApplicationContext("org/springframework/webflow/config/flow-registry.xml"); - registry = (FlowDefinitionRegistry) context.getBean("flowRegistry"); - } - - public void testRegistryFlowLocationsPopulated() { - FlowDefinition flow = registry.getFlowDefinition("flow"); - assertEquals("flow", flow.getId()); - assertEquals("bar", flow.getAttributes().get("foo")); - assertEquals(new Integer(2), flow.getAttributes().get("bar")); - } - - public void testRegistryFlowLocationPatternsPopulated() { - FlowDefinition flow1 = registry.getFlowDefinition("flow1"); - assertEquals("flow1", flow1.getId()); - FlowDefinition flow2 = registry.getFlowDefinition("flow2"); - assertEquals("flow2", flow2.getId()); - } - - public void testRegistryFlowBuildersPopulated() { - FlowDefinition foo = registry.getFlowDefinition("foo"); - assertEquals("foo", foo.getId()); - } - - public void testRegistryFlowBuildersPopulatedWithId() { - FlowDefinition foo = registry.getFlowDefinition("foo2"); - assertEquals("foo2", foo.getId()); - } - - public void testRegistryFlowBuildersPopulatedWithAttributes() { - FlowDefinition foo3 = registry.getFlowDefinition("foo3"); - assertEquals("foo3", foo3.getId()); - assertEquals("bar", foo3.getAttributes().get("foo")); - assertEquals(new Integer(2), foo3.getAttributes().get("bar")); + protected ApplicationContext initApplicationContext() { + return new ClassPathXmlApplicationContext("org/springframework/webflow/config/flow-registry.xml"); } public void testDefaultFlowBuilderServices() { @@ -66,24 +27,4 @@ public class FlowRegistryBeanDefinitionParserTests extends TestCase { } } - public void testNoSuchFlow() { - try { - registry.getFlowDefinition("not there"); - } catch (NoSuchFlowDefinitionException e) { - - } - } - - public void testBogusPath() { - try { - registry.getFlowDefinition("bogus"); - fail("Should have failed"); - } catch (FlowDefinitionConstructionException e) { - } - } - - public void testParent() { - assertNotNull(registry.getParent()); - assertEquals("parentFlow", registry.getParent().getFlowDefinition("parentFlow").getId()); - } } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/config/flow-builder-services.xml b/spring-webflow/src/test/java/org/springframework/webflow/config/flow-builder-services.xml index 9d5578e9..cff52068 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/config/flow-builder-services.xml +++ b/spring-webflow/src/test/java/org/springframework/webflow/config/flow-builder-services.xml @@ -27,9 +27,11 @@ - + - + diff --git a/spring-webflow/src/test/java/org/springframework/webflow/config/flow-executor.xml b/spring-webflow/src/test/java/org/springframework/webflow/config/flow-executor.xml index aa28d618..643f5933 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/config/flow-executor.xml +++ b/spring-webflow/src/test/java/org/springframework/webflow/config/flow-executor.xml @@ -21,9 +21,11 @@ - + - +