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:
+ *
+ *
{@link FlowExecutor}
+ *
{@link FlowDefinitionRegistry}
+ *
{@link FlowBuilderServices}
+ *
+ *
+ * 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:
+ *
+ *
{@link FlowExecutor}
+ *
{@link FlowDefinitionRegistry}
+ *
{@link FlowBuilderServices}
+ *
+ *
+ * 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