/*
* 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.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.UrlResource;
import org.springframework.core.io.VfsResource;
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 FILESYSTEM_SCHEME = "file:";
private static final String SLASH = "/";
private ResourceLoader resourceLoader;
private String basePath;
/**
* Creates a new flow definition resource factory using a default resource loader.
*/
public FlowDefinitionResourceFactory() {
this.resourceLoader = new DefaultResourceLoader();
}
/**
* Creates a new flow definition resource factory using the specified resource loader.
* @param resourceLoader the resource loader
*/
public FlowDefinitionResourceFactory(ResourceLoader resourceLoader) {
Assert.notNull(resourceLoader, "The resource loader cannot be null");
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.
* @return the flow definition resource
*/
public FlowDefinitionResource createResource(String path) {
return createResource(path, null, null);
}
/**
* Create a flow definition resource from the path location provided. The returned resource will be configured with
* the provided attributes.
* @param path the encoded {@link Resource} path.
* @param attributes the flow definition meta attributes to configure
* @return the flow definition resource
*/
public FlowDefinitionResource createResource(String path, AttributeMap<Object> attributes) {
return createResource(path, attributes, null);
}
/**
* Create a flow definition resource from the path location provided. The returned resource will be configured with
* the provided attributes and flow id.
* @param path the encoded {@link Resource} path.
* @param attributes the flow definition meta attributes to configure
* @param flowId the flow definition id to configure
* @return the flow definition resource
*/
public FlowDefinitionResource createResource(String path, AttributeMap<Object> attributes, String flowId) {
Resource resource;
if (basePath == null) {
resource = resourceLoader.getResource(path);
} else {
try {
String basePath = this.basePath;
if (!basePath.endsWith(SLASH)) {
// the basePath must end with a slash to create a relative resource
basePath = basePath + SLASH;
}
resource = resourceLoader.getResource(basePath).createRelative(path);
} catch (IOException e) {
throw new IllegalStateException("The base path cannot be resolved from '" + basePath + "': "
+ e.getMessage());
}
}
if (flowId == null || flowId.length() == 0) {
flowId = getFlowId(resource);
}
return new FlowDefinitionResource(flowId, resource, attributes);
}
/**
* Create an array of flow definition resources from the path pattern location provided.
* @param pattern the encoded {@link Resource} path pattern.
* @param attributes meta attributes to apply to each flow definition resource
* @return the flow definition resources
*/
public FlowDefinitionResource[] createResources(String pattern, AttributeMap<Object> attributes) throws IOException {
if (resourceLoader instanceof ResourcePatternResolver) {
ResourcePatternResolver resolver = (ResourcePatternResolver) resourceLoader;
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];
flowResources[i] = new FlowDefinitionResource(getFlowId(resource), resource, attributes);
}
return flowResources;
} else {
throw new IllegalStateException(
"Cannot create flow definition resources from patterns without a ResourceLoader configured that is a ResourcePatternResolver");
}
}
/**
* Create a file-based based resource from the file path provided.
* @param path the {@link FileSystemResource} path
* @return the file-based flow definition resource
*/
public FlowDefinitionResource createFileResource(String path) {
Resource resource = new FileSystemResource(new File(path));
return new FlowDefinitionResource(getFlowId(resource), resource, null);
}
/**
* Create a classpath-based resource from the path provided.
* @param path the {@link ClassPathResource} path
* @param clazz to specify if the path should be relative to another class
* @return the classpath-based flow definition resource
*/
public FlowDefinitionResource createClassPathResource(String path, Class<?> clazz) {
Resource resource = new ClassPathResource(path, clazz);
return new FlowDefinitionResource(getFlowId(resource), resource, null);
}
// subclassing hooks
/**
* 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) {
if (basePath == null) {
return getFlowIdFromFileName(flowResource);
}
String basePath = removeScheme(this.basePath);
String filePath;
if (flowResource instanceof ContextResource) {
filePath = ((ContextResource) flowResource).getPathWithinContext();
} else if (flowResource instanceof ClassPathResource) {
filePath = ((ClassPathResource) flowResource).getPath();
} else if (flowResource instanceof FileSystemResource) {
filePath = truncateFilePath(((FileSystemResource) flowResource).getPath(), basePath);
} else if (flowResource instanceof UrlResource || flowResource instanceof VfsResource) {
try {
filePath = truncateFilePath(flowResource.getURL().getPath(), basePath);
} catch (IOException e) {
throw new IllegalArgumentException("Unable to obtain path: " + e.getMessage());
}
} else {
// default to the filename
return getFlowIdFromFileName(flowResource);
}
int beginIndex = 0;
int endIndex = filePath.length();
if (filePath.startsWith(basePath)) {
beginIndex = basePath.length();
} else if (filePath.startsWith(SLASH + basePath)) {
beginIndex = basePath.length() + 1;
}
if (filePath.startsWith(SLASH, beginIndex)) {
// ignore a leading slash
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());
}
private String truncateFilePath(String filePath, String basePath) {
int basePathIndex = filePath.lastIndexOf(basePath);
if (basePathIndex != -1) {
return filePath.substring(basePathIndex);
} else {
return filePath;
}
}
private String removeScheme(String basePath) {
if (basePath.startsWith(CLASSPATH_SCHEME)) {
return basePath.substring(CLASSPATH_SCHEME.length());
} else if (basePath.startsWith(FILESYSTEM_SCHEME)) {
return basePath.substring(FILESYSTEM_SCHEME.length());
} else if (basePath.startsWith(CLASSPATH_STAR_SCHEME)) {
return basePath.substring(CLASSPATH_STAR_SCHEME.length());
} else {
return basePath;
}
}
}