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
This commit is contained in:
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import org.w3c.dom.Element;
|
||||
* {@link BeanDefinitionParser} for the flow <code><flow-registry></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));
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user