I started out using @gihan's suggestion, but I ran into some problems with the File Monitors processing the file before the user was finished uploading it on some clients.
Here is the solution I found from poking around in Mina's source code. In spite of the sparse, useless documentation on Apache Mina's site, I think this is the way they intended for developers to use their library.
NOTE: Since your needs are probably different than mine, keep in mind that this may not be a copy-and-paste solution. You will probably need to adapt this code to suit your needs, but I'm fairly confident that this code does provide the key to the solution you're looking for.
Step 1: Implement SftpEventListener
Create your own class that implements org.apache.sshd.server.subsystem.sftp.SftpEventListener
. Here's mine as an example. My implementation is set up to run a series of registered FileUploadCompleteListener
methods whenever a file is newly uploaded or overwritten, and block user attempts to navigate or create directories.
public class SFTPServiceSFTPEventListener implements SftpEventListener {
Logger logger = Logger.getLogger(SFTPServiceSFTPEventListener.class);
SFTPService service;
public SFTPServiceSFTPEventListener(SFTPService service) {
this.service = service;
}
public interface FileUploadCompleteListener {
void onFileReady(File file);
}
private List<FileUploadCompleteListener> fileReadyListeners = new ArrayList<FileUploadCompleteListener>();
public void addFileUploadCompleteListener(FileUploadCompleteListener listener) {
fileReadyListeners.add(listener);
}
public void removeFileUploadCompleteListener(FileUploadCompleteListener listener) {
fileReadyListeners.remove(listener);
}
@Override
public void initialized(ServerSession serverSession, int version) {
}
@Override
public void destroying(ServerSession serverSession) {
}
@Override
public void open(ServerSession serverSession, String remoteHandle, Handle localHandle) {
File openedFile = localHandle.getFile().toFile();
if (openedFile.exists() && openedFile.isFile()) {
}
}
@Override
public void read(ServerSession serverSession, String remoteHandle, DirectoryHandle localHandle, Map<String,Path> entries) {
}
@Override
public void read(ServerSession serverSession, String remoteHandle, FileHandle localHandle, long offset, byte[] data, int dataOffset, int dataLen, int readLen) {
}
@Override
public void write(ServerSession serverSession, String remoteHandle, FileHandle localHandle, long offset, byte[] data, int dataOffset, int dataLen) {
}
@Override
public void blocking(ServerSession serverSession, String remoteHandle, FileHandle localHandle, long offset, long length, int mask) {
}
@Override
public void blocked(ServerSession serverSession, String remoteHandle, FileHandle localHandle, long offset, long length, int mask, Throwable thrown) {
}
@Override
public void unblocking(ServerSession serverSession, String remoteHandle, FileHandle localHandle, long offset, long length) {
}
@Override
public void unblocked(ServerSession serverSession, String remoteHandle, FileHandle localHandle, long offset, long length, Boolean result, Throwable thrown) {
}
@Override
public void close(ServerSession serverSession, String remoteHandle, Handle localHandle) {
File closedFile = localHandle.getFile().toFile();
if (closedFile.exists() && closedFile.isFile()) {
logger.info(String.format("User %s closed file: \"%s\"", serverSession.getUsername(), localHandle.getFile().toAbsolutePath()));
this.service.UserWroteFile(serverSession.getUsername(), localHandle.getFile());
for (FileUploadCompleteListener fileReadyListener : fileReadyListeners) {
fileReadyListener.onFileReady(closedFile);
}
}
}
@Override
public void creating(ServerSession serverSession, Path path, Map<String,?> attrs) throws UnsupportedOperationException {
logger.warn(String.format("Blocked user %s attempt to create a directory \"%s\"", serverSession.getUsername(), path.toString()));
throw new UnsupportedOperationException("Creating sub-directories is not permitted.");
}
@Override
public void created(ServerSession serverSession, Path path, Map<String,?> attrs, Throwable thrown) {
String username = serverSession.getUsername();
logger.info(String.format("User %s created: \"%s\"", username, path.toString()));
service.UserWroteFile(username, path);
}
@Override
public void moving(ServerSession serverSession, Path path, Path path1, Collection<CopyOption> collection) {
}
@Override
public void moved(ServerSession serverSession, Path source, Path destination, Collection<CopyOption> collection, Throwable throwable) {
String username = serverSession.getUsername();
logger.info(String.format("User %s moved: \"%s\" to \"%s\"", username, source.toString(), destination.toString()));
service.UserWroteFile(username, destination);
}
@Override
public void removing(ServerSession serverSession, Path path) {
}
@Override
public void removed(ServerSession serverSession, Path path, Throwable thrown) {
}
@Override
public void linking(ServerSession serverSession, Path source, Path target, boolean symLink) throws UnsupportedOperationException {
logger.warn(String.format("Blocked user %s attempt to create a link to \"%s\" at \"%s\"", serverSession.getUsername(), target.toString(), source.toString()));
throw new UnsupportedOperationException("Creating links is not permitted");
}
@Override
public void linked(ServerSession serverSession, Path source, Path target, boolean symLink, Throwable thrown) {
}
@Override
public void modifyingAttributes(ServerSession serverSession, Path path, Map<String,?> attrs) {
}
@Override
public void modifiedAttributes(ServerSession serverSession, Path path, Map<String,?> attrs, Throwable thrown) {
String username = serverSession.getUsername();
service.UserWroteFile(username, path);
}
}
Step 2: Add an instance of your listener to your server
Once you've implemented your class, all you need to do is instantiate it and add it to your server using an SftpSubsystemFactory
before calling start()
on your server:
// Your SSHD Server
SshServer sshd = SshServer.setUpDefaultServer();
SftpSubsystemFactory sftpSubsystemFactory= new SftpSubsystemFactory();
// This is where to put your implementation of SftpEventListener
SFTPServiceSFTPEventListener sftpEventListener = new SFTPServiceSFTPEventListener(this);
sftpEventListener.addFileUploadCompleteListener(new SFTPServiceSFTPEventListener.FileUploadCompleteListener() {
@Override
public void onFileReady(File file) {
try {
doThingsWithFile(file);
} catch (Exception e) {
logger.warn(String.format("An error occurred while attempting to do things with the file: \"%s\"", file.getName()), e);
}
}
});
sftpSubsystemFactory.addSftpEventListener(sftpEventListener);
List<NamedFactory<Command>> namedFactoryList = new ArrayList<NamedFactory<Command>>();
namedFactoryList.add(sftpSubsystemFactory);
sshd.setSubsystemFactories(namedFactoryList);
// Do your other init stuff...
sshd.start();
Once you've done that, your implementation of SftpEventListener
will start automatically responding to the events you've implemented. Mine basically just responds to when the user closes the file (which occurs when the file upload is complete), but as I said, you can feel free to implement the other methods to respond to other events.