From 4fb4c65280fba35b8c9339019a4360e6003ba59a Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Tue, 14 Oct 2008 22:22:20 +0000 Subject: [PATCH] SWF-909 Default flow id will be the path to the flow definition resource if a 'base-path' is provided on the flow-registry. Otherwise the current convension of filename minus extension is used. Flow locations are defined relative to the base-path --- .../config/FlowDefinitionResourceFactory.java | 98 +++++++++++++++++-- .../FlowRegistryBeanDefinitionParser.java | 7 ++ .../config/FlowRegistryFactoryBean.java | 13 +++ .../config/spring-webflow-config-2.0.xsd | 13 ++- .../FlowDefinitionResourceFactoryTests.java | 68 +++++++++++++ 5 files changed, 189 insertions(+), 10 deletions(-) create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/config/FlowDefinitionResourceFactoryTests.java diff --git a/spring-webflow/src/main/java/org/springframework/webflow/config/FlowDefinitionResourceFactory.java b/spring-webflow/src/main/java/org/springframework/webflow/config/FlowDefinitionResourceFactory.java index fb465c0b..6f54f097 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/config/FlowDefinitionResourceFactory.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/config/FlowDefinitionResourceFactory.java @@ -19,22 +19,30 @@ import java.io.File; import java.io.IOException; import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.ContextResource; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; import org.springframework.webflow.core.collection.AttributeMap; /** * A factory for creating flow definition resources that serve as pointers to external Flow definition files. * * @author Keith Donald + * @author Scott Andrews */ public class FlowDefinitionResourceFactory { + private static final String CLASSPATH_SCHEME = "classpath:"; + private static final String CLASSPATH_STAR_SCHEME = "classpath*:"; + private static final String SLASH = "/"; + private ResourceLoader resourceLoader; + private String basePath; /** * Creates a new flow definition resource factory using a default resource loader. @@ -52,6 +60,16 @@ public class FlowDefinitionResourceFactory { this.resourceLoader = resourceLoader; } + /** + * Sets the base removed from the flow path when determining the default flow id. + *

+ * '/WEB-INF' by default + * @param basePath the flow's base path + */ + public void setBasePath(String basePath) { + this.basePath = basePath; + } + /** * Create a flow definition resource from the path location provided. * @param path the encoded {@link Resource} path. @@ -81,7 +99,16 @@ public class FlowDefinitionResourceFactory { * @return the flow definition resource */ public FlowDefinitionResource createResource(String path, AttributeMap attributes, String flowId) { - Resource resource = resourceLoader.getResource(path); + Resource resource; + if (basePath == null) { + resource = resourceLoader.getResource(path); + } else { + try { + resource = resourceLoader.getResource(basePath).createRelative(path); + } catch (IOException e) { + throw new IllegalStateException("The base path cannot be resolved from '" + basePath + "'", e); + } + } if (flowId == null || flowId.length() == 0) { flowId = getFlowId(resource); } @@ -96,7 +123,16 @@ public class FlowDefinitionResourceFactory { public FlowDefinitionResource[] createResources(String pattern) throws IOException { if (resourceLoader instanceof ResourcePatternResolver) { ResourcePatternResolver resolver = (ResourcePatternResolver) resourceLoader; - Resource[] resources = resolver.getResources(pattern); + Resource[] resources; + if (basePath == null) { + resources = resolver.getResources(pattern); + } else { + if (basePath.endsWith(SLASH) || pattern.startsWith(SLASH)) { + resources = resolver.getResources(basePath + pattern); + } else { + resources = resolver.getResources(basePath + SLASH + pattern); + } + } FlowDefinitionResource[] flowResources = new FlowDefinitionResource[resources.length]; for (int i = 0; i < resources.length; i++) { Resource resource = resources[i]; @@ -133,19 +169,63 @@ public class FlowDefinitionResourceFactory { // subclassing hooks /** - * Obtains the flow id from the flow resource. By default, the flow id becomes the filename of the resource minus - * the extension. Subclasses may override. + * Obtains the flow id from the flow resource. By default, the flow id becomes the portion of the path between the + * basePath and the filename. If no directory structure is available then the filename without the extension is + * used. Subclasses may override. + *

+ * For example, '${basePath}/booking.xml' becomes 'booking' and '${basePath}/hotels/booking/booking.xml' becomes + * 'hotels/booking' * @param flowResource the flow resource * @return the flow id */ protected String getFlowId(Resource flowResource) { - String fileName = flowResource.getFilename(); - int extensionIndex = fileName.lastIndexOf('.'); - if (extensionIndex != -1) { - return fileName.substring(0, extensionIndex); + String basePath = this.basePath; + String filePath; + if (basePath == null) { + // default to the filename + return getFlowIdFromFileName(flowResource); + } else if (flowResource instanceof ClassPathResource) { + filePath = ((ClassPathResource) flowResource).getPath(); + // remove classpath scheme + if (basePath.startsWith(CLASSPATH_SCHEME)) { + basePath = basePath.substring(CLASSPATH_SCHEME.length()); + } else if (basePath.startsWith(CLASSPATH_STAR_SCHEME)) { + basePath = basePath.substring(CLASSPATH_STAR_SCHEME.length()); + } + } else if (!(flowResource instanceof ContextResource)) { + // default to the filename + return getFlowIdFromFileName(flowResource); } else { - return fileName; + filePath = ((ContextResource) flowResource).getPathWithinContext(); } + + int beginIndex = 0; + int endIndex = filePath.length(); + if (filePath.startsWith(SLASH) || !basePath.startsWith(SLASH)) { + if (filePath.startsWith(basePath)) { + beginIndex = basePath.length(); + } + } else { + if (filePath.startsWith(SLASH + basePath)) { + beginIndex = basePath.length() + 1; + } + } + // ignore a leading slash + if (filePath.startsWith(SLASH, beginIndex)) { + beginIndex++; + } + if (filePath.lastIndexOf(SLASH) >= beginIndex) { + // ignore the filename + endIndex = filePath.lastIndexOf(SLASH); + } else { + // there is no path info, default to the filename + return getFlowIdFromFileName(flowResource); + } + return filePath.substring(beginIndex, endIndex); + } + + private String getFlowIdFromFileName(Resource flowResource) { + return StringUtils.stripFilenameExtension(flowResource.getFilename()); } } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/config/FlowRegistryBeanDefinitionParser.java b/spring-webflow/src/main/java/org/springframework/webflow/config/FlowRegistryBeanDefinitionParser.java index 294da31f..daacacf8 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/config/FlowRegistryBeanDefinitionParser.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/config/FlowRegistryBeanDefinitionParser.java @@ -37,6 +37,7 @@ import org.w3c.dom.Element; * {@link BeanDefinitionParser} for the flow <flow-registry> tag. * * @author Keith Donald + * @author Scott Andrews */ class FlowRegistryBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { @@ -62,6 +63,12 @@ class FlowRegistryBeanDefinitionParser extends AbstractSingleBeanDefinitionParse if (StringUtils.hasText(parent)) { definitionBuilder.addPropertyReference("parent", parent); } + + String basePath = element.getAttribute("base-path"); + if (StringUtils.hasText(basePath)) { + definitionBuilder.addPropertyValue("basePath", basePath); + } + definitionBuilder.addPropertyValue("flowLocations", parseLocations(element)); definitionBuilder.addPropertyValue("flowLocationPatterns", parseLocationPatterns(element)); definitionBuilder.addPropertyValue("flowBuilders", parseFlowBuilders(element)); diff --git a/spring-webflow/src/main/java/org/springframework/webflow/config/FlowRegistryFactoryBean.java b/spring-webflow/src/main/java/org/springframework/webflow/config/FlowRegistryFactoryBean.java index 8667ddb9..3d12ddc6 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/config/FlowRegistryFactoryBean.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/config/FlowRegistryFactoryBean.java @@ -54,6 +54,7 @@ import org.springframework.webflow.engine.model.registry.FlowModelRegistryImpl; * * @author Keith Donald * @author Jeremy Grelle + * @author Scott Andrews */ class FlowRegistryFactoryBean implements FactoryBean, BeanClassLoaderAware, InitializingBean { @@ -67,6 +68,8 @@ class FlowRegistryFactoryBean implements FactoryBean, BeanClassLoaderAware, Init private FlowDefinitionRegistry parent; + private String basePath; + private ClassLoader classLoader; /** @@ -109,6 +112,13 @@ class FlowRegistryFactoryBean implements FactoryBean, BeanClassLoaderAware, Init this.flowBuilderServices = flowBuilderServices; } + /** + * Base path used when determining the default flow id + */ + public void setBasePath(String basePath) { + this.basePath = basePath; + } + /** * The parent of the registry created by this factory bean. */ @@ -124,6 +134,9 @@ class FlowRegistryFactoryBean implements FactoryBean, BeanClassLoaderAware, Init public void afterPropertiesSet() throws Exception { flowResourceFactory = new FlowDefinitionResourceFactory(flowBuilderServices.getApplicationContext()); + if (basePath != null) { + flowResourceFactory.setBasePath(basePath); + } flowRegistry = new DefaultFlowRegistry(); flowRegistry.setParent(parent); registerFlowLocations(); diff --git a/spring-webflow/src/main/java/org/springframework/webflow/config/spring-webflow-config-2.0.xsd b/spring-webflow/src/main/java/org/springframework/webflow/config/spring-webflow-config-2.0.xsd index 856405fb..346aa109 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/config/spring-webflow-config-2.0.xsd +++ b/spring-webflow/src/main/java/org/springframework/webflow/config/spring-webflow-config-2.0.xsd @@ -11,7 +11,7 @@ A XML-based DSL for configuring the Spring Web Flow 2.0 system. ]]> @@ -92,6 +92,17 @@ Optional. For use when one or more custom builder services are required. + + + + + + + diff --git a/spring-webflow/src/test/java/org/springframework/webflow/config/FlowDefinitionResourceFactoryTests.java b/spring-webflow/src/test/java/org/springframework/webflow/config/FlowDefinitionResourceFactoryTests.java new file mode 100644 index 00000000..c9ab3676 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/config/FlowDefinitionResourceFactoryTests.java @@ -0,0 +1,68 @@ +/* + * Copyright 2004-2008 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 junit.framework.TestCase; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.mock.web.MockServletContext; +import org.springframework.web.context.support.ServletContextResourceLoader; + +/** + * Unit tests for {@link FlowDefinitionResourceFactory}. + */ +public class FlowDefinitionResourceFactoryTests extends TestCase { + + private ResourceLoader resourceLoader; + private FlowDefinitionResourceFactory factory; + + protected void setUp() throws Exception { + resourceLoader = new ServletContextResourceLoader(new MockServletContext()); + factory = new FlowDefinitionResourceFactory(); + } + + public void testGetFlowId() { + Resource resource = resourceLoader.getResource("/WEB-INF/hotels/booking/booking-flow.xml"); + assertEquals("booking-flow", factory.getFlowId(resource)); + } + + public void testGetFlowIdCustomBasePath() { + Resource resource = resourceLoader.getResource("/WEB-INF/hotels/booking/booking-flow.xml"); + factory.setBasePath("/WEB-INF"); + assertEquals("hotels/booking", factory.getFlowId(resource)); + } + + public void testGetFlowIdCustomBasePathTrailingSlash() { + Resource resource = resourceLoader.getResource("/WEB-INF/hotels/booking/booking-flow.xml"); + factory.setBasePath("/WEB-INF/"); + assertEquals("hotels/booking", factory.getFlowId(resource)); + } + + public void testGetFlowIdFlowPathIsBasePath() { + Resource resource = resourceLoader.getResource("/WEB-INF/hotels/booking/booking-flow.xml"); + factory.setBasePath("/WEB-INF/hotels/booking"); + assertEquals("booking-flow", factory.getFlowId(resource)); + } + + public void testGetFlowIdClassPathResource() { + Resource resource = new ClassPathResource("org/springframework/webflow/sample/sample-flow.xml"); + factory.setBasePath("classpath:org/springframework/webflow"); + assertEquals("sample", factory.getFlowId(resource)); + } + +}