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
This commit is contained in:
Scott Andrews
2008-10-14 22:22:20 +00:00
parent 43bb9d215a
commit 4fb4c65280
5 changed files with 189 additions and 10 deletions

View File

@@ -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.
* <p>
* '/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.
* <p>
* 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());
}
}

View File

@@ -37,6 +37,7 @@ import org.w3c.dom.Element;
* {@link BeanDefinitionParser} for the flow <code>&lt;flow-registry&gt;</code> 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));

View File

@@ -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();

View File

@@ -11,7 +11,7 @@
<xsd:documentation>
<![CDATA[
Spring Web Flow Configuration Schema
Authors: Keith Donald, Jeremy Grelle
Authors: Keith Donald, Jeremy Grelle, Scott Andrews
<br>
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.
<![CDATA[
A reference to this registry's parent. Registries can be organized in a hierarchy. If a child registry does not contain a flow,
its parent registry will be queried.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="base-path" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The base path where flow definitions are found. All flow locations must be referenced relative to the base path. The default
flow id is constructed based on the path to the flow minus the base path. The remaining directory path will be used as the flow
id. If flow definition is found directly on the base path, the file name, without extension, is used as the flow id
]]>
</xsd:documentation>
</xsd:annotation>

View File

@@ -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));
}
}