1

I am trying to copy a file from an SFTP server using Apache Commons VFS Utility.

While copying, if there is any type of network failure, it is expected that I will get an IOException. But I am unable to find any exception thrown (checked my logs). Hence, I find a file that has been half copied. (Tried with text file.) Below is the code snippet:

public class SFTPFileHandler implements IFileSystemHandler {

    private String hostName;
    private String userName;
    private String password;
    private String knownHost;
    private String privateKey;
    private FileSystemOptions fileSystemOptions;
    private StandardFileSystemManager fileSystemManager;
    private FileObject remoteRootDirectory;
    private boolean initialized = false;
    private FileType fileType;

    //code to initialize stuff
    ....

    /**
     * Method to Connect to the Server
     * 
     * @throws URISyntaxException
     * @throws FileSystemException
     * @throws FileHandlerInitializationException
     */
    private void connect() throws URISyntaxException, FileSystemException, FileHandlerInitializationException {
        createDefaultOptions();
        String connectionUrl = buildConnectionUrl();
        remoteRootDirectory = fileSystemManager.resolveFile(connectionUrl,fileSystemOptions);
    }



    /**
     * Method to copy a from the local file system to SFTP server
     */
    public void localToRemoteCopy(String srcPath, String destPath) throws FileSystemException {
        LocalFile localFileObject = null;
        FileObject remoteFileObject = null;
        try {
            localFileObject = (LocalFile) fileSystemManager
                    .resolveFile(srcPath);
            remoteFileObject = remoteRootDirectory.resolveFile(destPath);
            remoteFileObject.copyFrom(localFileObject, new AllFileSelector());
        } finally {
            if(null != localFileObject ){
            localFileObject.close();
            }
            if(null != remoteFileObject ){
            remoteFileObject.close();
            }
        }
    }

    // other code

}

And if I look at the source, it does throw an exception.

/**
     * Copies another file to this file.
     * @param file The FileObject to copy.
     * @param selector The FileSelector.
     * @throws FileSystemException if an error occurs.
     */
    public void copyFrom(final FileObject file, final FileSelector selector)
        throws FileSystemException
    {
        if (!file.exists())
        {
            throw new FileSystemException("vfs.provider/copy-missing-file.error", file);
        }
        /* we do not alway know if a file is writeable
        if (!isWriteable())
        {
            throw new FileSystemException("vfs.provider/copy-read-only.error", new Object[]{file.getType(),
            file.getName(), this}, null);
        }
        */

        // Locate the files to copy across
        final ArrayList<FileObject> files = new ArrayList<FileObject>();
        file.findFiles(selector, false, files);

        // Copy everything across
        final int count = files.size();
        for (int i = 0; i < count; i++)
        {
            final FileObject srcFile = files.get(i);

            // Determine the destination file
            final String relPath = file.getName().getRelativeName(srcFile.getName());
            final FileObject destFile = resolveFile(relPath, NameScope.DESCENDENT_OR_SELF);

            // Clean up the destination file, if necessary
            if (destFile.exists() && destFile.getType() != srcFile.getType())
            {
                // The destination file exists, and is not of the same type,
                // so delete it
                // TODO - add a pluggable policy for deleting and overwriting existing files
                destFile.delete(Selectors.SELECT_ALL);
            }

            // Copy across
            try
            {
                if (srcFile.getType().hasContent())
                {
                    FileUtil.copyContent(srcFile, destFile);
                }
                else if (srcFile.getType().hasChildren())
                {
                    destFile.createFolder();
                }
            }
            catch (final IOException e)
            {
                throw new FileSystemException("vfs.provider/copy-file.error", new Object[]{srcFile, destFile}, e);
            }
        }
    }

and I am using the below dependencies:

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-vfs2</artifactId>
            <version>2.0</version>
        </dependency>
        <dependency>
            <groupId>com.jcraft</groupId>
            <artifactId>jsch</artifactId>
            <version>0.1.53</version>
        </dependency>
Hash
  • 4,647
  • 5
  • 21
  • 39
arnabkaycee
  • 1,634
  • 13
  • 26
  • Looking at your code, I see some issues, the `try-catch` does not have any catch clause. The methods you declare are throwing exception, but where are they caught? – Adonis Jun 21 '17 at 14:28
  • I am catching them in my client code. – arnabkaycee Jun 21 '17 at 15:43
  • Is it systematic on a specific file? Or does that happen from time to time? Or was this a one-time accident? – Hugues M. Jun 21 '17 at 22:42
  • No. This happens whenever both (Target and SFTP) servers lose connection due to network issues. I don't get an exception caught in my client code. That's where the problem is. I get a half copied file. – arnabkaycee Jun 22 '17 at 03:29
  • 2
    Hmm, strange, I tried that yesterday: start a ssh-to-local file copy with commons-vfs (tried 2.0 and 2.1, with jsch 0.1.53 and 0.1.54), disable wifi in the middle, I get a partially copied file, and a stack trace. Are you confident in your logging configuration? Maybe try 2.1, I think logging was a bit more chatty. – Hugues M. Jun 22 '17 at 11:49
  • Couldn't you debug the code and step through `copyFrom()` method? – Florian S. Jun 24 '17 at 11:53
  • I tried attaching the source and debug, I could not find exception being thrown. I only land up with a half copied file. – arnabkaycee Jun 26 '17 at 07:37
  • I'd recommend to add a temporary `catch(Exception)` block to your `localToRemoteCopy` and rethrow any exceptions there. Set a breakpoint to this catch-block and debug your program again. If there is any exception thrown, you will reach this breakpoint. – dpr Jun 26 '17 at 08:03

2 Answers2

0

I think you may be a classic example of an issue in your finally masking the true exception. I suspect if your dropping network connectivity then you are problem having clean up issues in your finally block as well. Can you try modifying your finally block so you actually catch exceptions if they are occurring in your finally block?

Change

} finally {
        if(null != localFileObject ){
        localFileObject.close();
        }
        if(null != remoteFileObject ){
        remoteFileObject.close();
        }
}

to

} finally {
        if(null != localFileObject ){
          try { 
            localFileObject.close();
          } catch (Exception ex) { //ignore or add just a logging call }
        }
        if(null != remoteFileObject ){
          try {
           remoteFileObject.close();
          } catch (Exception ex) { //ignore or add just a logging call }
        }
}

Remember an exception raised in a finally block will hide an exception thrown in your main block.

M. Rizzo
  • 1,611
  • 12
  • 24
  • I am already throwing FileSystemException from my method localToRemoteCopy. Catching the Exception prematurely in the method would cause my client code to malfunction. Had the API code thrown an Exception then my client code would have definitely caught it. This is a case where the API is not throwing an exception with a half copied file. – arnabkaycee Jun 27 '17 at 08:39
  • It does look like VFS has a logged issue where you are right the API does not throw an exception during a network interruption when using FileUtil.copyContent https://issues.apache.org/jira/browse/VFS-446 . You can also see in the ticket that originator switched to utilizing Java futures and their own looping structure. – M. Rizzo Jun 27 '17 at 12:04
0

I was thinking of solving this problem with a manual workaround, by doing a checksum match of the source and the destination file and then throwing an exception if the checksum differed.

arnabkaycee
  • 1,634
  • 13
  • 26