/*
* Copyright 2009-2010 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.batch.admin.service;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ResourceLoaderAware;
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.core.io.support.ResourcePatternUtils;
import org.springframework.util.Assert;
/**
* An implementation of {@link FileService} that deals with files only in the
* local file system. Files and triggers are created in subdirectories of the
* Java temporary directory.
*
* @author Dave Syer
*
*/
public class LocalFileService implements FileService, InitializingBean, ResourceLoaderAware {
private File outputDir = new File(System.getProperty("java.io.tmpdir", "/tmp"), "batch/files");
private static final Log logger = LogFactory.getLog(LocalFileService.class);
private ResourceLoader resourceLoader = new DefaultResourceLoader();
private FileSender fileSender;
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
public void setFileSender(FileSender fileSender) {
this.fileSender = fileSender;
}
/**
* The output directory to store new files. Defaults to
* <code>${java.io.tmpdir}/batch/files</code>.
* @param outputDir the output directory to set
*/
public void setOutputDir(File outputDir) {
this.outputDir = outputDir;
}
public void afterPropertiesSet() throws Exception {
Assert.state(fileSender != null, "A FileSender must be provided");
if (!outputDir.exists()) {
Assert.state(outputDir.mkdirs(), "Cannot create output directory " + outputDir);
}
Assert.state(outputDir.exists(), "Output directory does not exist " + outputDir);
Assert.state(outputDir.isDirectory(), "Output file is not a directory " + outputDir);
}
public FileInfo createFile(String path) throws IOException {
path = sanitize(path);
Assert.hasText(path, "The file path must not be empty");
String name = path.substring(path.lastIndexOf("/") + 1);
String parent = path.substring(0, path.lastIndexOf(name));
if (parent.endsWith("/")) {
parent = parent.substring(0, parent.length() - 1);
}
File directory = new File(outputDir, parent);
try {
if(!new URI(directory.getAbsolutePath()).normalize().getPath().startsWith(this.outputDir.getAbsolutePath())) {
throw new IllegalArgumentException("Can not write to directory: " + directory.getAbsolutePath());
}
}
catch (URISyntaxException e) {
throw new IOException(e);
}
directory.mkdirs();
Assert.state(directory.exists() && directory.isDirectory(), "Could not create directory: " + directory);
FileInfo result = new FileInfo(path);
File dest = new File(outputDir, result.getFileName());
dest.createNewFile();
return result;
}
/**
* @param target the target file
* @return the path to the file from the base output directory
*/
private String extractPath(File target) {
String outputPath = outputDir.getAbsolutePath();
return target.getAbsolutePath().substring(outputPath.length() + 1).replace("\\", "/");
}
public boolean publish(FileInfo dest) throws IOException {
String path = dest.getPath();
fileSender.send(getResource(path).getFile());
return true;
}
public int countFiles() {
ResourcePatternResolver resolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
Resource[] resources;
try {
resources = resolver.getResources("file:///" + outputDir.getAbsolutePath() + "/**");
}
catch (IOException e) {
throw new IllegalStateException("Unexpected problem resolving files", e);
}
return resources.length;
}
public List<FileInfo> getFiles(int startFile, int pageSize) throws IOException {
List<FileInfo> files = getFiles("**");
String path = "";
int count = 0;
for (FileInfo info : files) {
FileInfo shortInfo = info.shortPath();
if (!path.equals(shortInfo.getPath())) {
files.set(count, shortInfo);
path = shortInfo.getPath();
}
count++;
}
return new ArrayList<FileInfo>(files.subList(startFile, Math.min(startFile + pageSize, files.size())));
}
private List<FileInfo> getFiles(String pattern) {
ResourcePatternResolver resolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
List<Resource> resources = new ArrayList<Resource>();
if (!pattern.startsWith("/")) {
pattern = "/" + outputDir.getAbsolutePath() + "/" + pattern;
}
if (!pattern.startsWith("file:")) {
pattern = "file:///" + pattern;
}
try {
resources = Arrays.asList(resolver.getResources(pattern));
}
catch (IOException e) {
logger.debug("Cannot locate files " + pattern, e);
return new ArrayList<FileInfo>();
}
List<FileInfo> files = new ArrayList<FileInfo>();
for (Resource resource : resources) {
File file;
try {
file = resource.getFile();
if (file.isFile()) {
FileInfo info = new FileInfo(extractPath(file));
files.add(info);
}
}
catch (IOException e) {
logger.debug("Cannot locate file " + resource, e);
}
}
Collections.sort(files);
return new ArrayList<FileInfo>(files);
}
public int delete(String pattern) throws IOException {
ResourcePatternResolver resolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
if (!pattern.startsWith("/")) {
pattern = "/" + outputDir.getAbsolutePath() + "/" + pattern;
}
if (!pattern.startsWith("file:")) {
pattern = "file:///" + pattern;
}
Resource[] resources = resolver.getResources(pattern);
int count = 0;
for (Resource resource : resources) {
File file = resource.getFile();
if (file.isFile()) {
count++;
FileUtils.deleteQuietly(file);
}
}
return count;
}
public Resource getResource(String path) {
path = sanitize(path);
FileInfo pattern = new FileInfo(path);
List<FileInfo> files = getFiles(pattern.getPattern());
FileInfo info = files.isEmpty() ? pattern : files.get(0);
File file = new File(outputDir, info.getFileName());
return new FileServiceResource(file, path);
}
public File getUploadDirectory() {
return outputDir;
}
/**
* Normalize file separators to "/" and strip leading prefix and separators
* to create a simple relative path.
*
* @param path the raw path
* @return a sanitized version
*/
private String sanitize(String path) {
path = path.replace("\\", "/");
if (path.startsWith("files:")) {
path = path.substring("files:".length());
while (path.startsWith("/")) {
path = path.substring(1);
}
}
return path;
}
private static class FileServiceResource extends FileSystemResource implements ContextResource {
private final String path;
public FileServiceResource(File file, String path) {
super(file);
this.path = path;
}
public String getPathWithinContext() {
return path;
}
}
}