Commit ad1768c4 authored by Matija Obreza's avatar Matija Obreza
Browse files

File repository with FTP

parent 56757fa1
......@@ -80,4 +80,13 @@ public interface RepositoryFilePersistence extends JpaRepository<RepositoryFile,
*/
RepositoryFile findByPathAndOriginalFilename(String path, String originalFilename);
/**
* Count files with matching path
*
* @param path Repository path
* @return Number of files at specified path
*/
@Query("select count(rf) from RepositoryFile rf where rf.path = ?1")
int countByPath(String path);
}
......@@ -17,6 +17,7 @@
package org.genesys.filerepository.service;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.UUID;
......@@ -47,8 +48,8 @@ public interface RepositoryService {
* @throws InvalidRepositoryFileDataException the invalid repository file data exception
* @throws IOException when things go wrong on bytes storage level
*/
RepositoryFile addFile(String repositoryPath, String originalFilename, String contentType, byte[] bytes, RepositoryFile metaData)
throws InvalidRepositoryPathException, InvalidRepositoryFileDataException, IOException;
RepositoryFile addFile(String repositoryPath, String originalFilename, String contentType, byte[] bytes, RepositoryFile metaData) throws InvalidRepositoryPathException,
InvalidRepositoryFileDataException, IOException;
/**
* Add a new image to the file repository.
......@@ -63,8 +64,8 @@ public interface RepositoryService {
* @throws InvalidRepositoryFileDataException the invalid repository file data exception
* @throws IOException when things go wrong on bytes storage level
*/
RepositoryImage addImage(String repositoryPath, String originalFilename, String contentType, byte[] bytes, RepositoryImage metaData)
throws InvalidRepositoryPathException, InvalidRepositoryFileDataException, IOException;
RepositoryImage addImage(String repositoryPath, String originalFilename, String contentType, byte[] bytes, RepositoryImage metaData) throws InvalidRepositoryPathException,
InvalidRepositoryFileDataException, IOException;
/**
* Get repository file by its UUID.
......@@ -167,6 +168,17 @@ public interface RepositoryService {
*/
RepositoryFile moveFile(RepositoryFile repositoryFile, String newPath) throws NoSuchRepositoryFileException, InvalidRepositoryPathException;
/**
* Update path and originalFilename of repository file
*
* @param repositoryFile
* @param fullPath
* @return
* @throws InvalidRepositoryPathException
* @throws InvalidRepositoryFileDataException
*/
RepositoryFile moveAndRenameFile(RepositoryFile repositoryFile, String fullPath) throws InvalidRepositoryPathException, InvalidRepositoryFileDataException;
/**
* List all {@link RepositoryImage} entries at specified path.
*
......@@ -181,16 +193,18 @@ public interface RepositoryService {
* @param prefix The prefix
* @param pageRequest Pagination
* @return Distinct list of paths in repository starting with prefix
* @throws InvalidRepositoryPathException
*/
List<String> listPaths(String prefix, Pageable pageRequest);
List<String> listPaths(String prefix, Pageable pageRequest) throws InvalidRepositoryPathException;
/**
* List all paths
* List all paths within the prefix.
*
* @param prefix
* @return
* @throws InvalidRepositoryPathException
*/
List<String> listPaths(String prefix);
List<String> listPaths(String prefix) throws InvalidRepositoryPathException;
/**
* Update image metadata. The update is based on the record UUID.
......@@ -212,4 +226,13 @@ public interface RepositoryService {
*/
RepositoryImage removeImage(RepositoryImage repositoryImage) throws NoSuchRepositoryFileException, IOException;
/**
* Test if repository contains the path
*
* @param path
* @return true if at least one entry exists at specified path
* @throws InvalidRepositoryPathException
*/
boolean hasPath(Path path) throws InvalidRepositoryPathException;
}
......@@ -33,7 +33,6 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
// TODO: Auto-generated Javadoc
/**
* Handles magical updates to {@link ImageGallery} when files are added to {@link RepositoryImagePersistence}.
*
......
......@@ -21,6 +21,8 @@ import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
......@@ -95,9 +97,8 @@ public class RepositoryServiceImpl implements RepositoryService, InitializingBea
/*
* (non-Javadoc)
*
* @see org.genesys.filerepository.service.RepositoryService#addFile(java .lang.String, java.lang.String,
* java.lang.String, byte[], org.genesys.filerepository.model.RepositoryFile)
* @see org.genesys.filerepository.service.RepositoryService#addFile(java .lang.String, java.lang.String, java.lang.String, byte[],
* org.genesys.filerepository.model.RepositoryFile)
*/
@Override
@Transactional
......@@ -147,9 +148,8 @@ public class RepositoryServiceImpl implements RepositoryService, InitializingBea
/*
* (non-Javadoc)
*
* @see org.genesys.filerepository.service.RepositoryService#addImage( java.lang.String, java.lang.String,
* java.lang.String, byte[], org.genesys.filerepository.model.RepositoryImage)
* @see org.genesys.filerepository.service.RepositoryService#addImage( java.lang.String, java.lang.String, java.lang.String, byte[],
* org.genesys.filerepository.model.RepositoryImage)
*/
@Override
@Transactional
......@@ -215,7 +215,6 @@ public class RepositoryServiceImpl implements RepositoryService, InitializingBea
/*
* (non-Javadoc)
*
* @see org.genesys.filerepository.service.RepositoryService#getFile(java .util.UUID)
*/
@Override
......@@ -244,7 +243,6 @@ public class RepositoryServiceImpl implements RepositoryService, InitializingBea
/*
* (non-Javadoc)
*
* @see org.genesys.filerepository.service.RepositoryService#getFileBytes (java.lang.String, java.lang.String)
*/
@Override
......@@ -267,7 +265,6 @@ public class RepositoryServiceImpl implements RepositoryService, InitializingBea
/*
* (non-Javadoc)
*
* @see org.genesys.filerepository.service.RepositoryService#getFiles( java.lang.String)
*/
@Override
......@@ -280,9 +277,7 @@ public class RepositoryServiceImpl implements RepositoryService, InitializingBea
/*
* (non-Javadoc)
*
* @see org.genesys.filerepository.service.RepositoryService#updateFile( org.genesys.filerepository.model
* .RepositoryFile)
* @see org.genesys.filerepository.service.RepositoryService#updateFile( org.genesys.filerepository.model .RepositoryFile)
*/
@Override
@Transactional
......@@ -303,9 +298,7 @@ public class RepositoryServiceImpl implements RepositoryService, InitializingBea
/*
* (non-Javadoc)
*
* @see org.genesys.filerepository.service.RepositoryService#updateMetadata(org.genesys.filerepository.
* model.RepositoryImage)
* @see org.genesys.filerepository.service.RepositoryService#updateMetadata(org.genesys.filerepository. model.RepositoryImage)
*/
@Override
@Transactional
......@@ -323,9 +316,7 @@ public class RepositoryServiceImpl implements RepositoryService, InitializingBea
/*
* (non-Javadoc)
*
* @see org.genesys.filerepository.service.RepositoryService#updateBytes( org.genesys.filerepository.model
* .RepositoryFile, java.lang.String, byte[])
* @see org.genesys.filerepository.service.RepositoryService#updateBytes( org.genesys.filerepository.model .RepositoryFile, java.lang.String, byte[])
*/
@Override
@Transactional
......@@ -354,9 +345,7 @@ public class RepositoryServiceImpl implements RepositoryService, InitializingBea
/*
* (non-Javadoc)
*
* @see org.genesys.filerepository.service.RepositoryService#updateBytes( org.genesys.filerepository.model
* .RepositoryImage, java.lang.String, byte[])
* @see org.genesys.filerepository.service.RepositoryService#updateBytes( org.genesys.filerepository.model .RepositoryImage, java.lang.String, byte[])
*/
@Override
@Transactional
......@@ -394,9 +383,7 @@ public class RepositoryServiceImpl implements RepositoryService, InitializingBea
/*
* (non-Javadoc)
*
* @see org.genesys.filerepository.service.RepositoryService#removeFile( org.genesys.filerepository.model
* .RepositoryFile)
* @see org.genesys.filerepository.service.RepositoryService#removeFile( org.genesys.filerepository.model .RepositoryFile)
*/
@Override
@Transactional
......@@ -428,9 +415,7 @@ public class RepositoryServiceImpl implements RepositoryService, InitializingBea
/*
* (non-Javadoc)
*
* @see org.genesys.filerepository.service.RepositoryService#moveFile(org .genesys2.server.filerepository.model
* .RepositoryFile, java.lang.String)
* @see org.genesys.filerepository.service.RepositoryService#moveFile(org .genesys2.server.filerepository.model .RepositoryFile, java.lang.String)
*/
@Override
@Transactional
......@@ -454,6 +439,32 @@ public class RepositoryServiceImpl implements RepositoryService, InitializingBea
return file;
}
@Override
@Transactional
public RepositoryFile moveAndRenameFile(final RepositoryFile repositoryFile, final String fullPath) throws InvalidRepositoryPathException, InvalidRepositoryFileDataException {
if (fullPath == null) {
throw new NullPointerException("Full path cannot be null");
}
LOG.info("Moving path={} name={} to {}", repositoryFile.getPath(), repositoryFile.getFilename(), fullPath);
Path path = Paths.get(fullPath).normalize().toAbsolutePath();
PathValidator.checkValidPath(path.getParent().toString());
if (path.getFileName().toString().trim().length() == 0) {
throw new InvalidRepositoryFileDataException("File name cannot be blank");
}
repositoryFile.setPath(path.getParent().toString());
repositoryFile.setOriginalFilename(path.getFileName().toString());
if (repositoryFile instanceof RepositoryImage) {
return repositoryImagePersistence.save((RepositoryImage) repositoryFile);
} else {
return repositoryFilePersistence.save(repositoryFile);
}
}
/**
* Copy meta data.
*
......@@ -480,7 +491,6 @@ public class RepositoryServiceImpl implements RepositoryService, InitializingBea
/*
* (non-Javadoc)
*
* @see org.genesys.filerepository.service.RepositoryService#listImages(java.lang.String)
*/
@Override
......@@ -488,22 +498,35 @@ public class RepositoryServiceImpl implements RepositoryService, InitializingBea
return repositoryImagePersistence.findByPath(repositoryPath);
}
@Override
public boolean hasPath(Path path) throws InvalidRepositoryPathException {
String p = path.normalize().toAbsolutePath().toString();
PathValidator.checkValidPath(p);
LOG.trace("Is directory path={}", p);
return repositoryFilePersistence.countByPath(p) > 0;
}
/*
* (non-Javadoc)
*
* @see org.genesys.filerepository.service.RepositoryService#listPaths(java.lang.String)
*/
@Override
public List<String> listPaths(final String prefix, final Pageable pageable) {
public List<String> listPaths(final String prefix, final Pageable pageable) throws InvalidRepositoryPathException {
PathValidator.checkValidPath(prefix);
LOG.debug("Listing paths under prefix={} page={}", prefix, pageable);
final List<String> paths = repositoryFilePersistence.listDistinctPaths(prefix, pageable);
paths.remove(prefix);
LOG.trace("Paths under prefix={}: {}", prefix, paths);
return paths;
}
@Override
public List<String> listPaths(String prefix) {
public List<String> listPaths(String prefix) throws InvalidRepositoryPathException {
PathValidator.checkValidPath(prefix);
LOG.debug("Listing paths under prefix={}", prefix);
final List<String> paths = repositoryFilePersistence.listDistinctPaths(prefix);
paths.remove(prefix);
LOG.trace("Paths under prefix={}: {}", prefix, paths);
return paths;
}
......
......@@ -32,6 +32,8 @@ import org.apache.ftpserver.ftplet.FileSystemView;
import org.apache.ftpserver.ftplet.FtpException;
import org.apache.ftpserver.ftplet.FtpFile;
import org.apache.ftpserver.ftplet.User;
import org.genesys.filerepository.InvalidRepositoryFileDataException;
import org.genesys.filerepository.InvalidRepositoryPathException;
import org.genesys.filerepository.NoSuchRepositoryFileException;
import org.genesys.filerepository.model.RepositoryFile;
import org.genesys.filerepository.service.RepositoryService;
......@@ -89,7 +91,13 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
@Override
public boolean move(FtpFile destination) {
LOG.info("Move file={} to dest={}", this.getAbsolutePath(), destination.getAbsolutePath());
return false;
try {
repositoryService.moveAndRenameFile(repositoryFile, destination.getAbsolutePath());
return true;
} catch (InvalidRepositoryPathException | InvalidRepositoryFileDataException e) {
LOG.warn("Error moving file: {}", e.getMessage());
return false;
}
}
@Override
......@@ -129,30 +137,36 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
@Override
public List<? extends FtpFile> listFiles() {
LOG.debug("Listing files in path={}", this.getAbsolutePath());
final String currentPath = getAbsolutePath();
LOG.debug("Listing files in path={}", currentPath);
ArrayList<FtpFile> all = new ArrayList<>();
final Path root = Paths.get(getAbsolutePath());
all.addAll(session.temporaryDirs.stream().filter(path -> path.startsWith(getAbsolutePath()) && !path.equals(getAbsolutePath()))
// we have full paths as string, filter the ones that are direct children
.map(path -> {
Path relativized = root.relativize(Paths.get(path));
LOG.trace("Rel={} rel[0]={} root.resolve={}", relativized.toString(), relativized.getName(0), root.resolve(relativized.getName(0)).normalize().toString());
return root.resolve(relativized.getName(0)).normalize().toString();
})
// unique
.distinct().peek(p -> {
LOG.debug("Temporary folder={}", p);
})
// map to RepositoryFtpDirectory
.map(path -> directory(path, session)).collect(Collectors.toList()));
all.addAll(repositoryService.getFiles(this.getAbsolutePath()).stream().map(rf -> file(rf)).collect(Collectors.toList()));
all.addAll(repositoryService.listPaths(getAbsolutePath()).stream().map(path -> directory(path, session)).collect(Collectors.toList()));
all.sort((a, b) -> {
final Path root = Paths.get(currentPath);
all.addAll(session.temporaryDirs.stream().filter(path -> path.startsWith(currentPath) && !path.equals(currentPath))
// we have full paths as string, filter the ones that are direct children
.map(path -> {
Path relativized = root.relativize(Paths.get(path));
LOG.trace("Rel={} rel[0]={} root.resolve={}", relativized.toString(), relativized.getName(0), root.resolve(relativized.getName(0)).normalize().toString());
return root.resolve(relativized.getName(0)).normalize().toString();
})
// unique
.distinct().peek(p -> {
LOG.debug("Temporary folder={}", p);
})
// map to RepositoryFtpDirectory
.map(path -> directory(path, session)).collect(Collectors.toList()));
all.addAll(repositoryService.getFiles(currentPath).stream().map(rf -> file(rf)).collect(Collectors.toList()));
try {
all.addAll(repositoryService.listPaths(currentPath).stream().map(path -> directory(path, session)).collect(Collectors.toList()));
} catch (InvalidRepositoryPathException e) {
LOG.warn("Error listing paths for {}: {}", currentPath, e.getMessage());
}
// Distinct sorted list of everything
return Collections.unmodifiableList(all.stream().distinct().sorted((a, b) -> {
return a.getName().compareTo(b.getName());
});
return Collections.unmodifiableList(all);
}).collect(Collectors.toList()));
}
@Override
......@@ -203,6 +217,7 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
LOG.debug("getFile file={} for user={}", file, username);
Path path = Paths.get(cwd.getAbsolutePath(), file).normalize();
LOG.trace("Resolved normalized={}", path.toString());
LOG.trace("Temporary dirs: {}", temporaryDirs);
if (temporaryDirs.stream().filter(longpath -> longpath.startsWith(path.toString())).findFirst().isPresent()) {
LOG.trace("dir={} is a temporary session-bound directory", path);
......@@ -210,7 +225,7 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
}
try {
return path.endsWith("/") ? directory(path.toString(), this) : file(repositoryService.getFile(path.getParent().toString(), path.getFileName().toString()));
return isDirectory(path) ? directory(path.toString(), this) : file(repositoryService.getFile(path.getParent().toString(), path.getFileName().toString()));
} catch (NoSuchRepositoryFileException e) {
LOG.debug("Making new CanBeAnythingFile path={} name={}", path.getParent().toString(), path.getFileName().toString());
......@@ -240,6 +255,15 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
}
}
private boolean isDirectory(Path path) {
try {
return repositoryService.hasPath(path) || temporaryDirs.stream().filter(longpath -> longpath.equals(path.toString())).findFirst().isPresent();
} catch (InvalidRepositoryPathException e) {
LOG.debug("Invalid repository path {}: {}", path, e.getMessage());
return temporaryDirs.stream().filter(longpath -> longpath.equals(path.toString())).findFirst().isPresent();
}
}
};
}
......
......@@ -145,4 +145,28 @@ public abstract class RepositoryFtpDirectory implements FtpFile {
public abstract boolean changeWorkingDirectory(String dir);
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((path == null) ? 0 : path.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
RepositoryFtpDirectory other = (RepositoryFtpDirectory) obj;
if (path == null) {
if (other.path != null)
return false;
} else if (!path.equals(other.path))
return false;
return true;
}
}
......@@ -64,9 +64,9 @@ public class RepositoryFtpServer implements InitializingBean, DisposableBean {
private String keystorePsw;
private String passivePorts;
private String passivePorts;
private String externalAddress;
private String externalAddress;
private FtpServer server = null;
......@@ -76,18 +76,21 @@ public class RepositoryFtpServer implements InitializingBean, DisposableBean {
/**
* Set the path to Java keystore
*
* <pre>keytool -genkey -alias testdomain -keyalg RSA -keystore ftpserver.jks -keysize 4096</pre>
* <pre>
* keytool -genkey -alias testdomain -keyalg RSA -keystore ftpserver.jks -keysize 4096
* </pre>
*
* @param keystorePath
*/
public void setExternalAddress(final String externalAddress) {
this.externalAddress = externalAddress;
}
public void setExternalAddress(final String externalAddress) {
this.externalAddress = externalAddress;
}
public void setPassivePorts(final String passivePorts) {
this.passivePorts = passivePorts;
}
public void setPassivePorts(final String passivePorts) {
this.passivePorts = passivePorts;
}
public void setKeystorePath(final String keystorePath) {
public void setKeystorePath(final String keystorePath) {
this.keystorePath = keystorePath;
}
......@@ -144,7 +147,7 @@ public class RepositoryFtpServer implements InitializingBean, DisposableBean {
factory.setSslConfiguration(ssl.createSslConfiguration());
factory.setImplicitSsl(true);
// define Data Connection configuration
// define Data Connection configuration
DataConnectionConfigurationFactory dccf = new DataConnectionConfigurationFactory();
dccf.setPassivePorts(passivePorts);
if (externalAddress != null) {
......
......@@ -67,7 +67,8 @@ public class TemporaryBytesManager {
public OutputStream newFile(Path path) throws IOException {
final String parent = path.getParent().toString();
final String filename = path.getFileName().toString();
LOG.info("Creating new parent={} filename={}", path, filename);
LOG.info("Creating new path={} parent={} filename={}", path, parent, filename);
File tempFile = File.createTempFile("ftp-", ".data");
......@@ -84,7 +85,7 @@ public class TemporaryBytesManager {
try {
repositoryService.addFile(parent, filename, null, bytes, null);
} catch (InvalidRepositoryPathException | InvalidRepositoryFileDataException e) {
LOG.warn("Error synchronizing new file with repository: {}", e.getMessage());
LOG.warn("Error synchronizing new file parent={} filename={} with repository: {}", parent, filename, e.getMessage());
throw new IOException(e);
}
LOG.info("Synchronized file={} with repository path={} originalFilename={}", tempFile.getAbsolutePath(), parent, filename);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment