RepositoryFileSystemFactory.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.ftp;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.ftpserver.ftplet.AuthenticationFailedException;
import org.apache.ftpserver.ftplet.FileSystemFactory;
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.FileRepositoryException;
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.model.RepositoryFolder;
import org.genesys.filerepository.service.RepositoryService;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
/**
* A factory for creating RepositoryFileSystem objects.
*/
@Component
@Slf4j
public class RepositoryFileSystemFactory implements FileSystemFactory, InitializingBean {
/** The repository service. */
@Autowired(required = true)
private RepositoryService repositoryService;
/** The bytes manager. */
@Autowired(required = true)
private TemporaryBytesManager bytesManager;
/**
* File.
*
* @param repositoryFile the repository file
* @return the repository ftp file
*/
private RepositoryFtpFile file(final RepositoryFile repositoryFile, final RepositoryFileSystemView session) {
log.trace("Making RepositoryFtpFile repositoryFile={}", repositoryFile);
return new RepositoryFtpFile(repositoryFile) {
@Override
public String getOwnerName() {
// TODO Auto-generated method stub
return "root";
}
@Override
public String getGroupName() {
// TODO Auto-generated method stub
return "wheel";
}
@Override
public boolean mkdir() {
log.debug("MKDIR on file not possible");
return false;
}
@Override
public boolean delete() {
return FtpRunAs.asFtpUser(session.user, () -> {
log.info("Delete file={}", this.getAbsolutePath());
try {
repositoryService.removeFile(repositoryFile);
return true;
} catch (NoSuchRepositoryFileException | IOException e) {
log.warn(e.getMessage());
return false;
}
});
}
@Override
public boolean move(final FtpFile destination) {
return FtpRunAs.asFtpUser(session.user, () -> {
log.info("Move file={} to dest={}", this.getAbsolutePath(), Paths.get(destination.getAbsolutePath()));
try {
repositoryService.moveAndRenameFile(repositoryFile, Paths.get(destination.getAbsolutePath()));
return true;
} catch (InvalidRepositoryPathException | InvalidRepositoryFileDataException e) {
log.warn("Error moving file: {}", e.getMessage());
return false;
}
});
}
@Override
public OutputStream createOutputStream(final long offset) throws IOException {
return FtpRunAs.asFtpUser(session.user, () -> {
log.info("Creating output stream for file={} at offset={}", getAbsolutePath(), offset);
return bytesManager.createOutputStream(session.user, repositoryFile, offset);
});
}
@Override
public InputStream createInputStream(final long offset) throws IOException {
return FtpRunAs.asFtpUser(session.user, () -> {
log.info("Creating input stream for file={} at offset={}", getAbsolutePath(), offset);
return bytesManager.createInputStream(repositoryFile, offset);
});
}
};
}
/**
* Directory.
*
* @param path the path
* @param session the session
* @return the repository ftp directory
*/
private RepositoryFtpDirectory directory(final Path path, final RepositoryFileSystemView session) {
log.trace("Viewing RepositoryFtpDirectory path={}", path);
return new RepositoryFtpDirectory(path) {
@Override
public boolean move(final FtpFile destination) {
return FtpRunAs.asFtpUser(session.user, () -> {
log.info("Move directory={} to dest={}", this.getAbsolutePath(), destination.getAbsolutePath());
try {
repositoryService.renamePath(Paths.get(this.getAbsolutePath()), Paths.get(destination.getAbsolutePath()));
return true;
} catch (final InvalidRepositoryPathException e) {
log.error("Failed to rename directory", e);
return false;
}
});
}
@Override
public boolean mkdir() {
log.info("Mkdir directory={}", this.getAbsolutePath());
try {
return FtpRunAs.asFtpUser(session.user, () -> {
repositoryService.ensureFolder(path);
return true;
});
} catch (InvalidRepositoryPathException e) {
log.error("{}", e.getMessage(), e);
return false;
}
}
@Override
public List<? extends FtpFile> listFiles() {
try {
return FtpRunAs.asFtpUser(session.user, this::_listFiles);
} catch (FileRepositoryException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
private List<? extends FtpFile> _listFiles() throws InvalidRepositoryPathException {
final Path root = path.normalize().toAbsolutePath();
log.debug("Listing files in path={}", root);
final ArrayList<FtpFile> all = new ArrayList<>();
all.addAll(repositoryService.getFolders(root, RepositoryFolder.DEFAULT_SORT).stream().peek(rf -> {
// System.err.println("repoFolder " + rf.getPath());
}).map(rf -> directory(rf.getFolderPath(), session)).collect(Collectors.toList()));
all.addAll(repositoryService.getFiles(root, RepositoryFile.DEFAULT_SORT).stream().peek(rf -> {
// System.err.println("repoFile " + rf.getStorageFullPath());
}).map(rf -> file(rf, session)).collect(Collectors.toList()));
// Distinct sorted list of everything
return all.stream().distinct().sorted(Comparator.comparing(FtpFile::getName)).peek(path1 -> {
// System.err.println("listFiles " + path.getName());
}).collect(Collectors.toUnmodifiableList());
}
@Override
public String getOwnerName() {
// TODO Auto-generated method stub
return "root";
}
@Override
public String getGroupName() {
// TODO Auto-generated method stub
return "wheel";
}
@Override
public boolean delete() {
return FtpRunAs.asFtpUser(session.user, () -> {
log.info("Delete this={}", path);
try {
repositoryService.deleteFolder(path);
return true;
} catch (FileRepositoryException e) {
return false;
}
});
}
@Override
public boolean changeWorkingDirectory(final String dir) {
final Path normalized = Paths.get(getAbsolutePath()).resolve(dir).normalize().toAbsolutePath();
log.info("CWD this={} dir={} normalized={}", getAbsolutePath(), dir, normalized);
if ("/".equals(normalized.toString())) {
this.cwd(normalized);
return true;
}
// Check if such path exists?
try {
RepositoryFolder folder = repositoryService.getFolder(normalized);
if (folder != null) {
this.cwd(normalized);
return true;
} else {
log.warn("CWD to non-existent folder {} normalized={}", dir, normalized);
return false;
}
} catch (InvalidRepositoryPathException e) {
log.warn("CWD to non-existent folder {} normalized={}", dir, normalized, e);
return false;
}
}
};
}
/*
* (non-Javadoc)
* @see
* org.apache.ftpserver.ftplet.FileSystemFactory#createFileSystemView(org.apache
* .ftpserver.ftplet.User)
*/
@Override
public FileSystemView createFileSystemView(final User user) {
log.info("Creating new repository view for {}", user.getName());
return new RepositoryFileSystemView((FtpUser) user) {
@Override
public FtpFile getFile(final String file) throws FtpException {
log.debug("getFile file={} for user={}", file, username);
final Path path = file.startsWith("/") ? Paths.get(file).normalize() : Paths.get(cwd.getAbsolutePath(), file).normalize();
log.trace("Resolved normalized={}", path);
try {
return isDirectory(path) ?
// directory
directory("/".equals(path.toString()) ? path : FtpRunAs.asFtpUser(user, () -> repositoryService.getFolder(path).getFolderPath()), this)
// or file
: file(FtpRunAs.asFtpUser(user, () -> repositoryService.getFile(path.getParent(), path.getFileName().toString())), this);
} catch (final AuthenticationException e) {
log.warn("Authentication problem {}", e.getMessage(), e);
throw new AuthenticationFailedException(e.getMessage(), e);
} catch (NoSuchRepositoryFileException e) {
log.debug("No such file {}: {}", file, e.getMessage());
assert path.getFileName() != null;
return new CanBeAnythingFile(path.getParent(), path.getFileName().toString()) {
@Override
public boolean mkdir() {
log.debug("MKDIR {}", path);
try {
RepositoryFolder repositoryFolder = FtpRunAs.asFtpUser(user, () -> repositoryService.ensureFolder(path));
return repositoryFolder != null;
} catch (InvalidRepositoryPathException e) {
log.warn("{}", e.getMessage(), e);
return false;
}
}
@Override
public boolean delete() {
return true;
}
@Override
public OutputStream createOutputStream(long offset) throws IOException {
log.debug("STOR {}", path);
if (path.getParent() != null) {
log.debug("MKDIR {}", path.getParent());
try {
RepositoryFolder repositoryFolder = FtpRunAs.asFtpUser(user, () -> repositoryService.ensureFolder(path.getParent()));
if (repositoryFolder == null) {
throw new InvalidRepositoryPathException("Folder not created " + path.getParent());
}
return bytesManager.newFile(user, path);
} catch (InvalidRepositoryPathException e) {
log.warn("{}", e.getMessage(), e);
throw new IOException(e.getMessage(), e);
}
} else {
throw new IOException("Cannot store files to /");
}
}
};
} catch (FileRepositoryException e) {
throw new FtpException(e.getMessage(), e);
}
}
private boolean isDirectory(final Path path) throws FtpException {
if ("/".equals(path.toString())) {
return true;
}
try {
log.trace("isDirectory {}", path);
return repositoryService.hasPath(path);
} catch (final InvalidRepositoryPathException e) {
log.debug("Invalid repository path {}: {}", path, e.getMessage());
throw new FtpException(e.getMessage(), e);
}
}
};
// AspectJProxyFactory factory = new AspectJProxyFactory(userView);
// factory.addAspect(new FtpSpringSecurityAspect((FtpUser) user));
// return factory.getProxy();
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() throws Exception {
assert (this.repositoryService != null);
log.warn("Initialized RFSF with service={}", this.repositoryService);
}
/**
* The Class RepositoryFileSystemView.
*/
private abstract class RepositoryFileSystemView implements FileSystemView {
/** The user. */
protected FtpUser user;
/** The username. */
protected String username;
/** The cwd. */
protected RepositoryFtpDirectory cwd = directory(Paths.get("/"), this);
/** The home dir. */
protected RepositoryFtpDirectory homeDir = directory(Paths.get("/"), this);
/**
* Instantiates a new repository file system view.
*
* @param user the user
*/
public RepositoryFileSystemView(final FtpUser user) {
this.user = user;
username = user.getName();
}
/*
* (non-Javadoc)
* @see org.apache.ftpserver.ftplet.FileSystemView#isRandomAccessible()
*/
@Override
public boolean isRandomAccessible() throws FtpException {
// TODO Auto-generated method stub
return false;
}
/*
* (non-Javadoc)
* @see org.apache.ftpserver.ftplet.FileSystemView#getWorkingDirectory()
*/
@Override
public FtpFile getWorkingDirectory() throws FtpException {
log.debug("getWorkingDirectory for user={}", username);
return this.cwd;
}
/*
* (non-Javadoc)
* @see org.apache.ftpserver.ftplet.FileSystemView#getHomeDirectory()
*/
@Override
public FtpFile getHomeDirectory() throws FtpException {
log.debug("getHomeDirectory for user={}", username);
return this.homeDir;
}
/*
* (non-Javadoc)
* @see org.apache.ftpserver.ftplet.FileSystemView#dispose()
*/
@Override
public void dispose() {
log.info("Disposing repository view for user={}", username);
}
/*
* (non-Javadoc)
* @see
* org.apache.ftpserver.ftplet.FileSystemView#changeWorkingDirectory(java.lang.
* String)
*/
@Override
public boolean changeWorkingDirectory(final String dir) throws FtpException {
log.debug("CWD dir={} for user={}", dir, username);
return this.cwd.changeWorkingDirectory(dir);
}
}
}