FilesystemStorageServiceImpl.java
/*
* Copyright 2018 Global Crop Diversity Trust
*
* 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.genesys.filerepository.service.impl;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.apache.commons.io.IOUtils;
import org.genesys.filerepository.BytesStorageException;
import org.genesys.filerepository.service.BytesStorageService;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
/**
* The Class FilesystemStorageServiceImpl.
*
* @author mobreza
*/
@Service("fileSystemStorage")
@Slf4j
public class FilesystemStorageServiceImpl implements InitializingBean, BytesStorageService {
/** Repository base directory. */
private File repoDir;
private Path repoPath;
/**
* Sets the repository base directory.
*
* @param repoDir the new repository base directory
*/
public void setRepositoryBaseDirectory(final File repoDir) {
this.repoDir = repoDir;
}
/**
* After properties set.
*
* @throws BytesStorageException the bytes storage exception
*/
@Override
public void afterPropertiesSet() throws BytesStorageException {
sanityCheck();
this.repoPath = Paths.get(repoDir.getAbsolutePath());
}
/**
* Sanity check.
*
* @throws BytesStorageException the bytes storage exception
*/
protected void sanityCheck() throws BytesStorageException {
if (!repoDir.isDirectory()) {
throw new BytesStorageException("Base path " + repoDir + " is not a directory");
}
}
/*
* (non-Javadoc)
* @see org.genesys.filerepository.service.BytesStorageService#upsert(
* java.lang.String, java.lang.String, byte[])
*/
@Override
public void upsert(Path bytesFile, final byte[] data) throws IOException {
final Path normalPath = bytesFile.normalize().toAbsolutePath();
final Path destinationFilePath = getDestPath(normalPath);
log.trace("Trying to upsert path={}", normalPath);
assert (destinationFilePath != null && destinationFilePath.getParent() != null);
final File destinationDir = destinationFilePath.getParent().toFile();
if (!destinationDir.getCanonicalPath().startsWith(repoDir.getCanonicalPath())) {
throw new IOException("Not within repository path " + destinationDir.getAbsolutePath());
} else if (!destinationDir.exists()) {
if (!destinationDir.mkdirs()) {
throw new IOException("Destination folder could not be created " + destinationDir.getAbsolutePath());
}
} else if (!destinationDir.isDirectory()) {
throw new IOException("Exists, not a directory " + destinationDir.getAbsolutePath());
}
final File destinationFile = destinationFilePath.toFile();
if (!destinationFile.getCanonicalPath().startsWith(repoDir.getCanonicalPath())) {
throw new IOException("Not within repository path " + destinationFile.getAbsolutePath());
}
try (BufferedOutputStream output = new BufferedOutputStream(Files.newOutputStream(destinationFile.toPath(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))) {
IOUtils.write(data, output);
}
}
/*
* (non-Javadoc)
* @see org.genesys.filerepository.service.BytesStorageService#upsert(
* java.lang.String, java.lang.String, byte[])
*/
@Override
public void upsert(Path bytesFile, final File fileWithData) throws IOException {
if (fileWithData == null || !fileWithData.exists()) {
throw new IOException("File is null or does not exist.");
}
final Path normalPath = bytesFile.normalize().toAbsolutePath();
final Path destinationFilePath = getDestPath(normalPath);
log.trace("Trying to upsert path={}", normalPath);
assert (destinationFilePath != null && destinationFilePath.getParent() != null);
final File destinationDir = destinationFilePath.getParent().toFile();
if (!destinationDir.getCanonicalPath().startsWith(repoDir.getCanonicalPath())) {
throw new IOException("Not within repository path " + destinationDir.getAbsolutePath());
} else if (!destinationDir.exists()) {
if (!destinationDir.mkdirs()) {
throw new IOException("Destination folder could not be created " + destinationDir.getAbsolutePath());
}
} else if (!destinationDir.isDirectory()) {
throw new IOException("Exists, not a directory " + destinationDir.getAbsolutePath());
}
final File destinationFile = destinationFilePath.toFile();
if (!destinationFile.getCanonicalPath().startsWith(repoDir.getCanonicalPath())) {
throw new IOException("Not within repository path " + destinationFile.getAbsolutePath());
}
try (FileInputStream source = new FileInputStream(fileWithData)) {
try (FileOutputStream target = new FileOutputStream(destinationFile, false)) {
long bytesTransferred = target.getChannel().transferFrom(source.getChannel(), 0, Long.MAX_VALUE);
log.debug("Transferred {}b from {} to {}", bytesTransferred, fileWithData.toString(), bytesFile.toString());
}
}
}
/**
* Gets the path where subPath a sub-folder of {@link #repoDir}.
*
* @param subPath the normal path
* @return the dest path
*/
public Path getDestPath(Path subPath) {
return Paths.get(repoPath.toString(), subPath.toString()).normalize().toAbsolutePath();
}
/*
* (non-Javadoc)
* @see org.genesys.filerepository.service.BytesStorageService#remove(
* java.lang.String, java.lang.String)
*/
@Override
public void remove(Path bytesFile) throws IOException {
final Path normalPath = bytesFile.normalize().toAbsolutePath();
final Path destinationFilePath = getDestPath(normalPath);
assert (destinationFilePath != null && destinationFilePath.getParent() != null);
final File destinationDir = destinationFilePath.getParent().toFile();
final File destinationFile = destinationFilePath.toFile();
if (!destinationFile.getCanonicalPath().startsWith(repoDir.getCanonicalPath())) {
throw new IOException("Not within repository path: " + destinationFile.getAbsolutePath());
}
if (destinationFile.exists()) {
if (!destinationFile.delete()) {
throw new IOException("Repository butes could not be removed at " + destinationFile.getAbsolutePath());
}
}
// Delete empty dir
if (destinationDir.exists() && destinationDir.isDirectory()) {
final String[] dirContents = destinationDir.list();
if ((dirContents != null) && (dirContents.length == 0)) {
tryDelete(destinationDir);
}
}
}
/*
* (non-Javadoc)
* @see org.genesys.filerepository.service.BytesStorageService#get(java.
* lang.String, java.lang.String)
*/
@Override
public byte[] get(Path bytesFile) throws IOException {
final Path normalPath = bytesFile.normalize().toAbsolutePath();
final Path destinationFilePath = getDestPath(normalPath);
log.trace("Retrieving bytes of {}", normalPath);
final File destinationFile = destinationFilePath.toFile();
if (!destinationFile.getCanonicalPath().startsWith(repoDir.getCanonicalPath())) {
throw new IOException("Not within repository path: " + destinationFile.getAbsolutePath());
}
byte[] data = null;
if (destinationFile.exists()) {
try (InputStream inputStream = Files.newInputStream(destinationFile.toPath())) {
data = IOUtils.toByteArray(inputStream);
}
} else {
log.warn("Repository bytes not found at {}", destinationFile.getAbsolutePath());
}
return data;
}
@Override
public void get(Path bytesFile, Consumer<InputStream> consumerOfStream) throws IOException {
final Path normalPath = bytesFile.normalize().toAbsolutePath();
final Path destinationFilePath = getDestPath(normalPath);
log.trace("Retrieving bytes of {}", normalPath);
final File destinationFile = destinationFilePath.toFile();
if (!destinationFile.getCanonicalPath().startsWith(repoDir.getCanonicalPath())) {
throw new IOException("Not within repository path: " + destinationFile.getAbsolutePath());
}
if (destinationFile.exists()) {
try (InputStream inputStream = Files.newInputStream(destinationFile.toPath())) {
consumerOfStream.accept(inputStream);
}
} else {
throw new IOException("Repository bytes not found at " + destinationFile.getAbsolutePath());
}
}
/*
* (non-Javadoc)
* @see org.genesys.filerepository.service.BytesStorageService#exists(java.lang.
* String, java.lang.String)
*/
@Override
public boolean exists(final Path bytesFile) {
return getDestPath(bytesFile).toFile().exists();
}
/*
* (non-Javadoc)
* @see
* org.genesys.filerepository.service.BytesStorageService#listFiles(java.lang.
* String)
*/
@Override
public List<String> listFiles(final Path path) {
log.debug("Listing filenames at path={}", path);
final File destinationDir = getDestPath(path).toFile();
if (!destinationDir.exists() || !destinationDir.isDirectory()) {
log.info("Returning empty files list for nonexistent dir={}", destinationDir.getAbsolutePath());
return Collections.emptyList();
}
log.debug("Listing filenames in dir={}", destinationDir.getAbsolutePath());
final File[] files = destinationDir.listFiles();
if (files == null) {
return Collections.emptyList();
}
return Arrays.stream(files).filter(file -> !file.isDirectory()).map(File::getName).collect(Collectors.toList());
}
private static void tryDelete(File file) {
log.debug("DELETING {}", file.getAbsolutePath());
try {
if (!file.delete()) {
log.debug("{} is not deleted", file.getAbsolutePath());
}
} catch (Exception e) {
log.debug("{} is not deleted", file.getAbsolutePath());
}
}
}