extensions/net.sf.basedb.opengrid/trunk/src/net/sf/basedb/opengrid/AbstractSession.java

Code
Comments
Other
Rev Date Author Line
4278 20 Dec 16 nicklas 1 package net.sf.basedb.opengrid;
4278 20 Dec 16 nicklas 2
4278 20 Dec 16 nicklas 3 import java.io.BufferedInputStream;
4338 07 Feb 17 nicklas 4 import java.io.BufferedOutputStream;
4278 20 Dec 16 nicklas 5 import java.io.ByteArrayOutputStream;
4278 20 Dec 16 nicklas 6 import java.io.Closeable;
4338 07 Feb 17 nicklas 7 import java.io.FilterOutputStream;
4278 20 Dec 16 nicklas 8 import java.io.IOException;
4278 20 Dec 16 nicklas 9 import java.io.InputStream;
4278 20 Dec 16 nicklas 10 import java.io.OutputStream;
6074 20 Nov 20 nicklas 11 import java.nio.charset.StandardCharsets;
4278 20 Dec 16 nicklas 12 import java.util.EnumSet;
4338 07 Feb 17 nicklas 13 import java.util.Set;
4278 20 Dec 16 nicklas 14 import java.util.concurrent.TimeUnit;
6071 20 Nov 20 nicklas 15 import java.util.concurrent.TimeoutException;
4278 20 Dec 16 nicklas 16
4278 20 Dec 16 nicklas 17 import org.slf4j.LoggerFactory;
4278 20 Dec 16 nicklas 18
4278 20 Dec 16 nicklas 19 import net.schmizz.sshj.SSHClient;
6071 20 Nov 20 nicklas 20 import net.schmizz.sshj.connection.ConnectionException;
4278 20 Dec 16 nicklas 21 import net.schmizz.sshj.connection.channel.direct.Session;
4278 20 Dec 16 nicklas 22 import net.schmizz.sshj.connection.channel.direct.Session.Command;
7028 09 Feb 23 nicklas 23 import net.schmizz.sshj.connection.channel.direct.Signal;
4278 20 Dec 16 nicklas 24 import net.schmizz.sshj.sftp.FileAttributes;
4278 20 Dec 16 nicklas 25 import net.schmizz.sshj.sftp.OpenMode;
4278 20 Dec 16 nicklas 26 import net.schmizz.sshj.sftp.RemoteFile;
5997 26 Aug 20 nicklas 27 import net.schmizz.sshj.sftp.Response;
4278 20 Dec 16 nicklas 28 import net.schmizz.sshj.sftp.SFTPClient;
5997 26 Aug 20 nicklas 29 import net.schmizz.sshj.sftp.SFTPException;
4278 20 Dec 16 nicklas 30 import net.schmizz.sshj.xfer.LocalFileFilter;
4278 20 Dec 16 nicklas 31 import net.schmizz.sshj.xfer.LocalSourceFile;
5984 10 Jul 20 nicklas 32 import net.sf.basedb.opengrid.CmdResult.StdioResult;
4278 20 Dec 16 nicklas 33 import net.sf.basedb.opengrid.filetransfer.DownloadTarget;
4278 20 Dec 16 nicklas 34 import net.sf.basedb.opengrid.filetransfer.FileMetaData;
4278 20 Dec 16 nicklas 35 import net.sf.basedb.opengrid.filetransfer.FilePermission;
4278 20 Dec 16 nicklas 36 import net.sf.basedb.opengrid.filetransfer.UploadSource;
7075 27 Mar 23 nicklas 37 import net.sf.basedb.opengrid.service.OpenGridService;
4278 20 Dec 16 nicklas 38 import net.sf.basedb.util.FileCopyRunnable;
4278 20 Dec 16 nicklas 39 import net.sf.basedb.util.FileUtil;
7075 27 Mar 23 nicklas 40 import net.sf.basedb.util.extensions.logging.ExtensionsLog;
7075 27 Mar 23 nicklas 41 import net.sf.basedb.util.extensions.logging.ExtensionsLogger;
4278 20 Dec 16 nicklas 42 import net.sf.basedb.util.uri.CloseResourceInputStream;
4278 20 Dec 16 nicklas 43
4278 20 Dec 16 nicklas 44 /**
4278 20 Dec 16 nicklas 45   Represents an open SSH session to a remote host. It is possible to 
4278 20 Dec 16 nicklas 46   execute commands, upload and download files as long as the session 
4278 20 Dec 16 nicklas 47   is open. Do not forget to {@link #close()} the session after use.
4278 20 Dec 16 nicklas 48   
4278 20 Dec 16 nicklas 49   A session is not thread-safe and need external synchronization if
4278 20 Dec 16 nicklas 50   used with multiple threads.
4278 20 Dec 16 nicklas 51   
4278 20 Dec 16 nicklas 52   @author nicklas
4278 20 Dec 16 nicklas 53   @since 1.0
4278 20 Dec 16 nicklas 54 */
4278 20 Dec 16 nicklas 55 public abstract class AbstractSession<T extends AbstractHost<?>> 
4278 20 Dec 16 nicklas 56   implements Closeable
4278 20 Dec 16 nicklas 57 {
4278 20 Dec 16 nicklas 58   
7075 27 Mar 23 nicklas 59   private static final ExtensionsLogger logger = 
7075 27 Mar 23 nicklas 60     ExtensionsLog.getLogger(OpenGridService.ID, true).wrap(LoggerFactory.getLogger(AbstractSession.class));
4278 20 Dec 16 nicklas 61   
4278 20 Dec 16 nicklas 62   private final T remote;
4278 20 Dec 16 nicklas 63   private final SSHClient ssh;
4278 20 Dec 16 nicklas 64   private SFTPClient sftp;
4278 20 Dec 16 nicklas 65   
4278 20 Dec 16 nicklas 66   /**
4278 20 Dec 16 nicklas 67     Session are typically created from 
4278 20 Dec 16 nicklas 68     {@link RemoteHost#connect(int)}.
4278 20 Dec 16 nicklas 69   */
4278 20 Dec 16 nicklas 70   protected AbstractSession(T remote, SSHClient ssh)
4278 20 Dec 16 nicklas 71   {
4278 20 Dec 16 nicklas 72     this.remote = remote;
4278 20 Dec 16 nicklas 73     this.ssh = ssh;
4278 20 Dec 16 nicklas 74   }
4278 20 Dec 16 nicklas 75   
4278 20 Dec 16 nicklas 76   /**
4278 20 Dec 16 nicklas 77     Check if the session has been closed or not.
4278 20 Dec 16 nicklas 78   */
4278 20 Dec 16 nicklas 79   public boolean isClosed()
4278 20 Dec 16 nicklas 80   {
4278 20 Dec 16 nicklas 81     return !ssh.isConnected();
4278 20 Dec 16 nicklas 82   }
4278 20 Dec 16 nicklas 83
4278 20 Dec 16 nicklas 84   @Override
4278 20 Dec 16 nicklas 85   public void close()
4278 20 Dec 16 nicklas 86     throws IOException
4278 20 Dec 16 nicklas 87   {
4278 20 Dec 16 nicklas 88     OpenGrid.close(sftp);
4278 20 Dec 16 nicklas 89     if (ssh.isConnected()) ssh.close();
4278 20 Dec 16 nicklas 90   }
4278 20 Dec 16 nicklas 91
4278 20 Dec 16 nicklas 92   /**
4278 20 Dec 16 nicklas 93     Get the host that created this session.
4278 20 Dec 16 nicklas 94   */
4278 20 Dec 16 nicklas 95   public T getHost()
4278 20 Dec 16 nicklas 96   {
4278 20 Dec 16 nicklas 97     return remote;
4278 20 Dec 16 nicklas 98   }
4278 20 Dec 16 nicklas 99   
4278 20 Dec 16 nicklas 100   /**
4278 20 Dec 16 nicklas 101     Execute a command on the remote host. This method should normally
4278 20 Dec 16 nicklas 102     not throw any exceptions. The result is returned as a {@link CmdResult}
5984 10 Jul 20 nicklas 103     object where the {@link CmdResult#getResult()} returns either the
5984 10 Jul 20 nicklas 104     stdout or stderr stream (depending on exit status).
4278 20 Dec 16 nicklas 105     
6071 20 Nov 20 nicklas 106     Note! Since 1.4 the timeout is a soft timeout. The command may take up
6071 20 Nov 20 nicklas 107     to 10 times longer as long as it is returning data back in either the
6071 20 Nov 20 nicklas 108     stdout or stderr streams.
6071 20 Nov 20 nicklas 109     
4278 20 Dec 16 nicklas 110     @param cmd The command to execute
4278 20 Dec 16 nicklas 111     @param timeout Timeout in seconds to wait for the command to finish
4278 20 Dec 16 nicklas 112     @return The result of the command
4278 20 Dec 16 nicklas 113   */
4278 20 Dec 16 nicklas 114   public CmdResult<String> executeCmd(String cmd, int timeout)
4278 20 Dec 16 nicklas 115   {
4278 20 Dec 16 nicklas 116     if (logger.isDebugEnabled())
4278 20 Dec 16 nicklas 117     {
4278 20 Dec 16 nicklas 118       logger.debug("Executing command on " + remote + ": " + cmd);
4278 20 Dec 16 nicklas 119     }
5984 10 Jul 20 nicklas 120     CmdResult<String> result = execute(new StdioResult(cmd), timeout);
4278 20 Dec 16 nicklas 121     return result;
4278 20 Dec 16 nicklas 122   }
4278 20 Dec 16 nicklas 123   
5984 10 Jul 20 nicklas 124   /**
5984 10 Jul 20 nicklas 125     Execute a command on the remote host and store the results
5984 10 Jul 20 nicklas 126     in the CmdResult instance. Exit status, stdout and stderr will
5984 10 Jul 20 nicklas 127     be updated on the 'cmd' instance and then 
5984 10 Jul 20 nicklas 128     {@link CmdResult#parseResult()} is called to allow implementations
5984 10 Jul 20 nicklas 129     to parse the output and update the other result information.
5984 10 Jul 20 nicklas 130     
6071 20 Nov 20 nicklas 131     Note! Since 1.4 the timeout is a soft timeout. The command may take up
6071 20 Nov 20 nicklas 132     to 10 times longer as long as it is returning data back in either the
6071 20 Nov 20 nicklas 133     stdout or stderr streams.
6071 20 Nov 20 nicklas 134     
5984 10 Jul 20 nicklas 135     @param cmd The command to execute
5984 10 Jul 20 nicklas 136     @param timeout Timeout in seconds to wait for the command to finish
5984 10 Jul 20 nicklas 137     @return The 'cmd' instance
5984 10 Jul 20 nicklas 138     @since 1.4
5984 10 Jul 20 nicklas 139   */
6071 20 Nov 20 nicklas 140   public <R, C extends CmdResult<R>> C execute(C cmd, final int timeout)
4278 20 Dec 16 nicklas 141   {
4278 20 Dec 16 nicklas 142     Session s = null;
4278 20 Dec 16 nicklas 143     try
4278 20 Dec 16 nicklas 144     {
4278 20 Dec 16 nicklas 145       // Collect stdout and stderr output
4278 20 Dec 16 nicklas 146       ByteArrayOutputStream stdout = new ByteArrayOutputStream();
4278 20 Dec 16 nicklas 147       ByteArrayOutputStream stderr = new ByteArrayOutputStream();
4278 20 Dec 16 nicklas 148       s = ssh.startSession();
5984 10 Jul 20 nicklas 149       Command c = s.exec(cmd.getCmd());
4278 20 Dec 16 nicklas 150
4278 20 Dec 16 nicklas 151       Thread stdoutReader = new Thread(new FileCopyRunnable(c.getInputStream(), stdout));
4278 20 Dec 16 nicklas 152       Thread stderrReader = new Thread(new FileCopyRunnable(c.getErrorStream(), stderr));
4278 20 Dec 16 nicklas 153       stdoutReader.start();
4278 20 Dec 16 nicklas 154       stderrReader.start();
4278 20 Dec 16 nicklas 155       
4278 20 Dec 16 nicklas 156       // Wait for SSH command and reader threads
6071 20 Nov 20 nicklas 157       int bytesReceivedLast = 0;
6071 20 Nov 20 nicklas 158       int numTimeouts = 0;
6071 20 Nov 20 nicklas 159       // Convert timeout to milliseconds since we may want to use a smaller timeout (20%) later
6071 20 Nov 20 nicklas 160       long timeoutM = 1000 * timeout;
6071 20 Nov 20 nicklas 161       // A hard timeout that will cause the command to fail no matter what
6072 20 Nov 20 nicklas 162       long hardTimeout = 1000 * cmd.getHardTimeout(timeout);
6071 20 Nov 20 nicklas 163       long startTime = System.currentTimeMillis();
6071 20 Nov 20 nicklas 164       while (true)
6071 20 Nov 20 nicklas 165       {
6071 20 Nov 20 nicklas 166         try
6071 20 Nov 20 nicklas 167         {
6071 20 Nov 20 nicklas 168           c.join(timeoutM, TimeUnit.MILLISECONDS);
6071 20 Nov 20 nicklas 169           // The command ended successfully -- exit loop
6071 20 Nov 20 nicklas 170           break;
6071 20 Nov 20 nicklas 171         }
6071 20 Nov 20 nicklas 172         catch (ConnectionException ex)
6071 20 Nov 20 nicklas 173         {
6071 20 Nov 20 nicklas 174           if (ex.getCause() instanceof TimeoutException)
6071 20 Nov 20 nicklas 175           {
6071 20 Nov 20 nicklas 176             numTimeouts++;
6071 20 Nov 20 nicklas 177             int bytesReceived = stdout.size()+stderr.size();
6071 20 Nov 20 nicklas 178             long time = System.currentTimeMillis()-startTime;
6071 20 Nov 20 nicklas 179             if (logger.isDebugEnabled())
6071 20 Nov 20 nicklas 180             {
6071 20 Nov 20 nicklas 181               logger.debug(numTimeouts + " timeouts and " + bytesReceived + " bytes received at time " + time);
6071 20 Nov 20 nicklas 182             }
6071 20 Nov 20 nicklas 183             if (bytesReceived > bytesReceivedLast && time < hardTimeout)
6071 20 Nov 20 nicklas 184             {
6071 20 Nov 20 nicklas 185               bytesReceivedLast = bytesReceived;
6071 20 Nov 20 nicklas 186               if (numTimeouts == 1 && timeout > 10) 
6071 20 Nov 20 nicklas 187               {
6071 20 Nov 20 nicklas 188                 // If the original timeout is >10 seconds, decrease timeout to 20% of the
6071 20 Nov 20 nicklas 189                 // original (but not lower than 5 seconds) so that we can check if data is still coming in
6071 20 Nov 20 nicklas 190                 timeoutM = Math.max(5000, timeoutM / 5);
6071 20 Nov 20 nicklas 191               }
6071 20 Nov 20 nicklas 192               continue;
6071 20 Nov 20 nicklas 193             }
6071 20 Nov 20 nicklas 194             if (logger.isDebugEnabled())
6071 20 Nov 20 nicklas 195             {
6071 20 Nov 20 nicklas 196               logger.debug("Giving up after " + numTimeouts + " timeouts and " + bytesReceived + " bytes received at time " + time);
6071 20 Nov 20 nicklas 197             }
6071 20 Nov 20 nicklas 198           }
7028 09 Feb 23 nicklas 199           // Send SIGTERM to the process otherwise the close() may have to wait for the command to finish
7028 09 Feb 23 nicklas 200           c.signal(Signal.TERM);
7028 09 Feb 23 nicklas 201           // Set a short timeout in case the command didn't respond to the TERM signal
7028 09 Feb 23 nicklas 202           ssh.getConnection().setTimeoutMs(500);
7028 09 Feb 23 nicklas 203           OpenGrid.close(c);
6071 20 Nov 20 nicklas 204           throw ex;
6071 20 Nov 20 nicklas 205         }
6071 20 Nov 20 nicklas 206       }
6072 20 Nov 20 nicklas 207       stdoutReader.join(timeoutM);
6072 20 Nov 20 nicklas 208       stderrReader.join(timeoutM);
4278 20 Dec 16 nicklas 209       OpenGrid.close(c);
4278 20 Dec 16 nicklas 210       
5984 10 Jul 20 nicklas 211       cmd.setExitStatus(c.getExitStatus());
6074 20 Nov 20 nicklas 212       cmd.setStdout(stdout.toString(StandardCharsets.UTF_8));
6074 20 Nov 20 nicklas 213       cmd.setStderr(stderr.toString(StandardCharsets.UTF_8));
5984 10 Jul 20 nicklas 214       cmd.parseResult();
7380 18 Oct 23 nicklas 215       if (logger.isTraceEnabled())
4278 20 Dec 16 nicklas 216       {
7380 18 Oct 23 nicklas 217         logger.trace(cmd.toString());
4278 20 Dec 16 nicklas 218       }
4278 20 Dec 16 nicklas 219     }
4278 20 Dec 16 nicklas 220     catch (Exception ex)
4278 20 Dec 16 nicklas 221     {
4278 20 Dec 16 nicklas 222       logger.error("Executing command on " + remote + ": " + cmd, ex);
5984 10 Jul 20 nicklas 223       cmd.setException(ex);
4278 20 Dec 16 nicklas 224     }
4278 20 Dec 16 nicklas 225     finally
4278 20 Dec 16 nicklas 226     {
4278 20 Dec 16 nicklas 227       OpenGrid.close(s);
4278 20 Dec 16 nicklas 228     }
5984 10 Jul 20 nicklas 229     return cmd;
4278 20 Dec 16 nicklas 230   }
4278 20 Dec 16 nicklas 231
4278 20 Dec 16 nicklas 232   
4278 20 Dec 16 nicklas 233   private SFTPClient getSFTPClient()
4278 20 Dec 16 nicklas 234     throws IOException
4278 20 Dec 16 nicklas 235   {
4278 20 Dec 16 nicklas 236     if (sftp == null) sftp = ssh.newSFTPClient();
4278 20 Dec 16 nicklas 237     return sftp;
4278 20 Dec 16 nicklas 238   }
4338 07 Feb 17 nicklas 239   
4338 07 Feb 17 nicklas 240   /**
4338 07 Feb 17 nicklas 241     Create directories on the remote server if they do not already 
4338 07 Feb 17 nicklas 242     exists.
4278 20 Dec 16 nicklas 243     
4338 07 Feb 17 nicklas 244     @param permission The permission to give to directories that
4338 07 Feb 17 nicklas 245       are created. Null to use default permissions. Already
4338 07 Feb 17 nicklas 246       existing directories are not affected.
4338 07 Feb 17 nicklas 247     @param paths The paths to create
4338 07 Feb 17 nicklas 248     @return The result of the command (do not forget to check the exit status)
4338 07 Feb 17 nicklas 249     @since 1.1
4338 07 Feb 17 nicklas 250   */
4338 07 Feb 17 nicklas 251   public CmdResult<String> mkdirs(FilePermission permission, String... paths)
4338 07 Feb 17 nicklas 252   {
4338 07 Feb 17 nicklas 253     ScriptBuilder mkdir = new ScriptBuilder();
4338 07 Feb 17 nicklas 254     mkdir.cmd("set -e");
4338 07 Feb 17 nicklas 255     if (permission != null)
4338 07 Feb 17 nicklas 256     {
4338 07 Feb 17 nicklas 257       mkdir.cmd("umask " + permission.umask());
4338 07 Feb 17 nicklas 258     }
4338 07 Feb 17 nicklas 259     for (String path : paths)
4338 07 Feb 17 nicklas 260     {
4338 07 Feb 17 nicklas 261       if (path != null) mkdir.cmd("mkdir -p " + path);
4338 07 Feb 17 nicklas 262     }
4338 07 Feb 17 nicklas 263     return executeCmd(mkdir.toString(), 5);
4338 07 Feb 17 nicklas 264   }
4338 07 Feb 17 nicklas 265
4278 20 Dec 16 nicklas 266   /**
4278 20 Dec 16 nicklas 267     Upload a file with SFTP. The target directory should 
4278 20 Dec 16 nicklas 268     be a full absolute path and must already exist.
4278 20 Dec 16 nicklas 269   */
4278 20 Dec 16 nicklas 270   public void uploadFile(UploadSource source, String toPath, FilePermission permission)
4278 20 Dec 16 nicklas 271   {
4278 20 Dec 16 nicklas 272     if (logger.isDebugEnabled())
4278 20 Dec 16 nicklas 273     {
4278 20 Dec 16 nicklas 274       logger.debug("Uploading file '" + source.getName() + "' to " + remote + ":" + toPath);
4278 20 Dec 16 nicklas 275     }
4278 20 Dec 16 nicklas 276     try
4278 20 Dec 16 nicklas 277     {
4278 20 Dec 16 nicklas 278       getSFTPClient();
4278 20 Dec 16 nicklas 279       sftp.put(new UploadSourceWrapper(source, permission), toPath);
4278 20 Dec 16 nicklas 280       if (logger.isDebugEnabled())
4278 20 Dec 16 nicklas 281       {
4278 20 Dec 16 nicklas 282         logger.debug("File '" + source.getName() + "' uploaded to " + remote + ":" + toPath);
4278 20 Dec 16 nicklas 283       }
4278 20 Dec 16 nicklas 284     }
4278 20 Dec 16 nicklas 285     catch (IOException ex)
4278 20 Dec 16 nicklas 286     {
4338 07 Feb 17 nicklas 287       logger.error("File upload failed: " + toPath, ex);
4278 20 Dec 16 nicklas 288       throw new RuntimeException(ex);
4278 20 Dec 16 nicklas 289     }
4278 20 Dec 16 nicklas 290     finally
4278 20 Dec 16 nicklas 291     {}
4278 20 Dec 16 nicklas 292   }
4278 20 Dec 16 nicklas 293   
4278 20 Dec 16 nicklas 294   /**
4338 07 Feb 17 nicklas 295     Write to a file on the remote server. If the file doesn't exists it is
4338 07 Feb 17 nicklas 296     created. The target directory should be a full absolute path and 
4338 07 Feb 17 nicklas 297     must already exist.
4338 07 Feb 17 nicklas 298     
4338 07 Feb 17 nicklas 299     @param toPath Absolute path where the uploaded file should be stored
4338 07 Feb 17 nicklas 300     @param overwrite TRUE to overwrite existing files
4338 07 Feb 17 nicklas 301     @param metadata Information about the file (or null if not important)
4338 07 Feb 17 nicklas 302     @param permission Permission to set on the file
4338 07 Feb 17 nicklas 303     @return An output stream to write the file data to
4338 07 Feb 17 nicklas 304     @since 1.1
4338 07 Feb 17 nicklas 305   */
4338 07 Feb 17 nicklas 306   public OutputStream writeFile(String toPath, boolean overwrite, FileMetaData metadata, FilePermission permission)
4338 07 Feb 17 nicklas 307   {
4338 07 Feb 17 nicklas 308     if (logger.isDebugEnabled())
4338 07 Feb 17 nicklas 309     {
4338 07 Feb 17 nicklas 310       logger.debug("Writing file '" + toPath + "' from " + remote);
4338 07 Feb 17 nicklas 311     }
4338 07 Feb 17 nicklas 312     try
4338 07 Feb 17 nicklas 313     {
4338 07 Feb 17 nicklas 314       return getRemoteOutputStream(toPath, overwrite, metadata, permission);
4338 07 Feb 17 nicklas 315     }
4338 07 Feb 17 nicklas 316     catch (IOException ex)
4338 07 Feb 17 nicklas 317     {
4338 07 Feb 17 nicklas 318       logger.error("File upload failed: " + toPath, ex);
4338 07 Feb 17 nicklas 319       throw new RuntimeException(ex);
4338 07 Feb 17 nicklas 320     }
4338 07 Feb 17 nicklas 321     finally
4338 07 Feb 17 nicklas 322     {}
4338 07 Feb 17 nicklas 323   }
4338 07 Feb 17 nicklas 324
4338 07 Feb 17 nicklas 325   
4338 07 Feb 17 nicklas 326   /**
4278 20 Dec 16 nicklas 327     Download a remote file to a local destination.
4278 20 Dec 16 nicklas 328     
4278 20 Dec 16 nicklas 329     @param fromPath The path of the remote file
4278 20 Dec 16 nicklas 330     @param target The local target the remote file is downloaded to
4278 20 Dec 16 nicklas 331   */
4278 20 Dec 16 nicklas 332   public void downloadFile(String fromPath, DownloadTarget target)
4278 20 Dec 16 nicklas 333   {
4278 20 Dec 16 nicklas 334     if (logger.isDebugEnabled())
4278 20 Dec 16 nicklas 335     {
4278 20 Dec 16 nicklas 336       logger.debug("Downloading to '" + target.getName() + "' from " + remote + ": " + fromPath);
4278 20 Dec 16 nicklas 337     }
4278 20 Dec 16 nicklas 338     InputStream in = null;
4278 20 Dec 16 nicklas 339     OutputStream out = null;
4278 20 Dec 16 nicklas 340     try
4278 20 Dec 16 nicklas 341     {
4278 20 Dec 16 nicklas 342       in = getRemoteInputStream(fromPath, target.getMetadata());
5997 26 Aug 20 nicklas 343       if (in != null)
4278 20 Dec 16 nicklas 344       {
5997 26 Aug 20 nicklas 345         out = target.getOutputStream();
5997 26 Aug 20 nicklas 346         FileUtil.copy(in, out);
5997 26 Aug 20 nicklas 347         if (logger.isDebugEnabled())
5997 26 Aug 20 nicklas 348         {
5997 26 Aug 20 nicklas 349           logger.debug("File '" + target.getName() + "' downloaded from " + remote + ": " + fromPath);
5997 26 Aug 20 nicklas 350         }
4278 20 Dec 16 nicklas 351       }
5997 26 Aug 20 nicklas 352       else
5997 26 Aug 20 nicklas 353       {
5997 26 Aug 20 nicklas 354         if (logger.isDebugEnabled())
5997 26 Aug 20 nicklas 355         {
5997 26 Aug 20 nicklas 356           logger.debug("File doesn't exists: " + fromPath);
5997 26 Aug 20 nicklas 357         }
5997 26 Aug 20 nicklas 358       }
4278 20 Dec 16 nicklas 359     }
4278 20 Dec 16 nicklas 360     catch (IOException ex)
4278 20 Dec 16 nicklas 361     {
4338 07 Feb 17 nicklas 362       logger.error("File download failed: " + fromPath, ex);
4278 20 Dec 16 nicklas 363       throw new RuntimeException(ex);
4278 20 Dec 16 nicklas 364     }
4278 20 Dec 16 nicklas 365     finally
4278 20 Dec 16 nicklas 366     {
4278 20 Dec 16 nicklas 367       FileUtil.close(in);
4278 20 Dec 16 nicklas 368       FileUtil.close(out);
4278 20 Dec 16 nicklas 369     }
4278 20 Dec 16 nicklas 370   }
4278 20 Dec 16 nicklas 371
4338 07 Feb 17 nicklas 372   
4278 20 Dec 16 nicklas 373   /**
4278 20 Dec 16 nicklas 374     Read from a file on the remote server. 
4278 20 Dec 16 nicklas 375   
4278 20 Dec 16 nicklas 376     @param fromPath The path of the remote file
4278 20 Dec 16 nicklas 377     @param metadata A metadata instance for collecting metadata
4278 20 Dec 16 nicklas 378       about the remote file or null if not important. The metadata
4278 20 Dec 16 nicklas 379       will be set before the file download is started
4278 20 Dec 16 nicklas 380     @return An InputStream reading from the remote file
4278 20 Dec 16 nicklas 381   */
4278 20 Dec 16 nicklas 382   public InputStream readFile(String fromPath, FileMetaData metadata)
4278 20 Dec 16 nicklas 383   {
4278 20 Dec 16 nicklas 384     if (logger.isDebugEnabled())
4278 20 Dec 16 nicklas 385     {
4278 20 Dec 16 nicklas 386       logger.debug("Reading file '" + fromPath + "' from " + remote);
4278 20 Dec 16 nicklas 387     }
4278 20 Dec 16 nicklas 388     try
4278 20 Dec 16 nicklas 389     {
4278 20 Dec 16 nicklas 390       return getRemoteInputStream(fromPath, metadata);
4278 20 Dec 16 nicklas 391     }
4278 20 Dec 16 nicklas 392     catch (IOException ex)
4278 20 Dec 16 nicklas 393     {
4338 07 Feb 17 nicklas 394       logger.error("File download failed: " + fromPath, ex);
4278 20 Dec 16 nicklas 395       throw new RuntimeException(ex);
4278 20 Dec 16 nicklas 396     }
4278 20 Dec 16 nicklas 397     finally
4278 20 Dec 16 nicklas 398     {}
4278 20 Dec 16 nicklas 399   }
4278 20 Dec 16 nicklas 400   
4278 20 Dec 16 nicklas 401   private InputStream getRemoteInputStream(String fromPath, FileMetaData metadata)
4278 20 Dec 16 nicklas 402     throws IOException
4278 20 Dec 16 nicklas 403   {
4278 20 Dec 16 nicklas 404     getSFTPClient();
5997 26 Aug 20 nicklas 405     RemoteFile rf = null;
5997 26 Aug 20 nicklas 406     try
5997 26 Aug 20 nicklas 407     {
5997 26 Aug 20 nicklas 408       rf = sftp.open(fromPath, EnumSet.of(OpenMode.READ));
5997 26 Aug 20 nicklas 409     }
5997 26 Aug 20 nicklas 410     catch (SFTPException ex)
5997 26 Aug 20 nicklas 411     {
5997 26 Aug 20 nicklas 412       if (ex.getStatusCode() == Response.StatusCode.NO_SUCH_FILE) return null;
5997 26 Aug 20 nicklas 413       throw ex;
5997 26 Aug 20 nicklas 414     }
4278 20 Dec 16 nicklas 415     
4278 20 Dec 16 nicklas 416     if (metadata != null)
4278 20 Dec 16 nicklas 417     {
4278 20 Dec 16 nicklas 418       FileAttributes attr = rf.fetchAttributes();
4278 20 Dec 16 nicklas 419       metadata.setLastAccessedTime(attr.getAtime() * 1000);
4278 20 Dec 16 nicklas 420       metadata.setLastModifiedTime(attr.getMtime() * 1000);
4278 20 Dec 16 nicklas 421       metadata.setSize(attr.getSize());
4278 20 Dec 16 nicklas 422     }
4278 20 Dec 16 nicklas 423     
4278 20 Dec 16 nicklas 424     return new CloseResourceInputStream(
4278 20 Dec 16 nicklas 425       new BufferedInputStream(rf.new RemoteFileInputStream(), 1000000), 
4278 20 Dec 16 nicklas 426       rf);
4278 20 Dec 16 nicklas 427   }
4278 20 Dec 16 nicklas 428   
4338 07 Feb 17 nicklas 429   private OutputStream getRemoteOutputStream(String toPath, boolean overwrite, FileMetaData metadata, FilePermission permission)
4338 07 Feb 17 nicklas 430     throws IOException
4338 07 Feb 17 nicklas 431   {
4338 07 Feb 17 nicklas 432     getSFTPClient();
4338 07 Feb 17 nicklas 433     FileAttributes.Builder builder = new FileAttributes.Builder();
4338 07 Feb 17 nicklas 434     if (metadata != null)
4338 07 Feb 17 nicklas 435     {
4338 07 Feb 17 nicklas 436       if (metadata.getLastAccessedTime() != 0 || metadata.getLastModifiedTime() != 0)
4338 07 Feb 17 nicklas 437       {
4338 07 Feb 17 nicklas 438         builder.withAtimeMtime(metadata.getLastAccessedTime(), metadata.getLastModifiedTime());
4338 07 Feb 17 nicklas 439       }
4338 07 Feb 17 nicklas 440       if (metadata.getSize() != 0) builder.withSize(metadata.getSize());
4338 07 Feb 17 nicklas 441     }
4338 07 Feb 17 nicklas 442     if (permission != null)
4338 07 Feb 17 nicklas 443     {
4338 07 Feb 17 nicklas 444       builder.withPermissions(permission.getPermissions());
4338 07 Feb 17 nicklas 445     }
4338 07 Feb 17 nicklas 446     
4338 07 Feb 17 nicklas 447     Set<OpenMode> openMode = overwrite ? 
4338 07 Feb 17 nicklas 448       EnumSet.of(OpenMode.CREAT, OpenMode.WRITE, OpenMode.TRUNC) :
4338 07 Feb 17 nicklas 449       EnumSet.of(OpenMode.CREAT, OpenMode.WRITE, OpenMode.EXCL);
4338 07 Feb 17 nicklas 450
4338 07 Feb 17 nicklas 451     RemoteFile rf = sftp.open(toPath, openMode, builder.build());
4338 07 Feb 17 nicklas 452     
4338 07 Feb 17 nicklas 453     // NOTE! If the BufferedOutputStream has a larget buffer the bytes 
4338 07 Feb 17 nicklas 454     // are lost somewhere, but I have no idea why or where this happens
4338 07 Feb 17 nicklas 455     // 10k seems to be ok, 1M is not
4338 07 Feb 17 nicklas 456     return new CloseResourceOutputStream(
4338 07 Feb 17 nicklas 457       new BufferedOutputStream(rf.new RemoteFileOutputStream(), 10000), 
4338 07 Feb 17 nicklas 458       rf);
4338 07 Feb 17 nicklas 459   }
4338 07 Feb 17 nicklas 460
4338 07 Feb 17 nicklas 461   
4278 20 Dec 16 nicklas 462   /**
4278 20 Dec 16 nicklas 463     Wraps an UploadSource implementation with a LocalSourceFile
4278 20 Dec 16 nicklas 464     implementation so that the file data can be sent to the remote
4278 20 Dec 16 nicklas 465     server with SSHj. We do it this way so we don't have to expose
4278 20 Dec 16 nicklas 466     SSHj-specific classes to the other extensions.
4278 20 Dec 16 nicklas 467     
4278 20 Dec 16 nicklas 468     @since 1.0
4278 20 Dec 16 nicklas 469   */
4278 20 Dec 16 nicklas 470   static class UploadSourceWrapper
4278 20 Dec 16 nicklas 471     implements LocalSourceFile
4278 20 Dec 16 nicklas 472   {
4278 20 Dec 16 nicklas 473     
4278 20 Dec 16 nicklas 474     private final UploadSource source;
4278 20 Dec 16 nicklas 475     private final FileMetaData metadata;
4278 20 Dec 16 nicklas 476     private final FilePermission permission;
4278 20 Dec 16 nicklas 477     
4278 20 Dec 16 nicklas 478     UploadSourceWrapper(UploadSource source, FilePermission permission) 
4278 20 Dec 16 nicklas 479     {
4278 20 Dec 16 nicklas 480       this.source = source;
4278 20 Dec 16 nicklas 481       this.metadata = source.getMetadata();
4278 20 Dec 16 nicklas 482       this.permission = permission;
4278 20 Dec 16 nicklas 483     }
4278 20 Dec 16 nicklas 484   
4278 20 Dec 16 nicklas 485     @Override
4278 20 Dec 16 nicklas 486     public String getName() 
4278 20 Dec 16 nicklas 487     {
4278 20 Dec 16 nicklas 488       return source.getName();
4278 20 Dec 16 nicklas 489     }
4278 20 Dec 16 nicklas 490   
4278 20 Dec 16 nicklas 491     @Override
4278 20 Dec 16 nicklas 492     public long getLength() 
4278 20 Dec 16 nicklas 493     {
4278 20 Dec 16 nicklas 494       return metadata == null ? 0 : metadata.getSize();
4278 20 Dec 16 nicklas 495     }
4278 20 Dec 16 nicklas 496   
4278 20 Dec 16 nicklas 497     @Override
4278 20 Dec 16 nicklas 498     public InputStream getInputStream() 
4278 20 Dec 16 nicklas 499       throws IOException 
4278 20 Dec 16 nicklas 500     {
4278 20 Dec 16 nicklas 501       return source.getInputStream();
4278 20 Dec 16 nicklas 502     }
4278 20 Dec 16 nicklas 503   
4278 20 Dec 16 nicklas 504     @Override
4278 20 Dec 16 nicklas 505     public int getPermissions()
4278 20 Dec 16 nicklas 506     {
4278 20 Dec 16 nicklas 507       return permission.getPermissions();
4278 20 Dec 16 nicklas 508     }
4278 20 Dec 16 nicklas 509   
4278 20 Dec 16 nicklas 510     /**
4278 20 Dec 16 nicklas 511       We only support files.
4278 20 Dec 16 nicklas 512     */
4278 20 Dec 16 nicklas 513     @Override
4278 20 Dec 16 nicklas 514     public boolean isFile() 
4278 20 Dec 16 nicklas 515     {
4278 20 Dec 16 nicklas 516       return true;
4278 20 Dec 16 nicklas 517     }
4278 20 Dec 16 nicklas 518     @Override
4278 20 Dec 16 nicklas 519     public boolean isDirectory() 
4278 20 Dec 16 nicklas 520     {
4278 20 Dec 16 nicklas 521       return false;
4278 20 Dec 16 nicklas 522     }
4278 20 Dec 16 nicklas 523   
4278 20 Dec 16 nicklas 524     /**
4278 20 Dec 16 nicklas 525       No children.
4278 20 Dec 16 nicklas 526     */
4278 20 Dec 16 nicklas 527     @Override
4278 20 Dec 16 nicklas 528     public Iterable<? extends LocalSourceFile> getChildren(LocalFileFilter filter) 
4278 20 Dec 16 nicklas 529       throws IOException 
4278 20 Dec 16 nicklas 530     {
4278 20 Dec 16 nicklas 531       return null;
4278 20 Dec 16 nicklas 532     }
4278 20 Dec 16 nicklas 533   
4278 20 Dec 16 nicklas 534     @Override
4278 20 Dec 16 nicklas 535     public boolean providesAtimeMtime() 
4278 20 Dec 16 nicklas 536     {
4278 20 Dec 16 nicklas 537       return metadata != null && metadata.getLastAccessedTime() > 0 && metadata.getLastModifiedTime() > 0;
4278 20 Dec 16 nicklas 538     }
4278 20 Dec 16 nicklas 539   
4278 20 Dec 16 nicklas 540     @Override
4278 20 Dec 16 nicklas 541     public long getLastAccessTime() 
4278 20 Dec 16 nicklas 542       throws IOException 
4278 20 Dec 16 nicklas 543     {
4278 20 Dec 16 nicklas 544       return metadata == null ? 0 : metadata.getLastAccessedTime() / 1000;
4278 20 Dec 16 nicklas 545     }
4278 20 Dec 16 nicklas 546   
4278 20 Dec 16 nicklas 547     @Override
4278 20 Dec 16 nicklas 548     public long getLastModifiedTime() 
4278 20 Dec 16 nicklas 549       throws IOException
4278 20 Dec 16 nicklas 550     {
4278 20 Dec 16 nicklas 551       return metadata == null ? 0 : metadata.getLastModifiedTime() / 1000;
4278 20 Dec 16 nicklas 552     }
4278 20 Dec 16 nicklas 553   }
4338 07 Feb 17 nicklas 554   
4338 07 Feb 17 nicklas 555   /**
4338 07 Feb 17 nicklas 556     Wrapper around the outputstream so that we can
4338 07 Feb 17 nicklas 557     close the related resource when the stream is closed.
4338 07 Feb 17 nicklas 558     @since 1.1
4338 07 Feb 17 nicklas 559   */
4338 07 Feb 17 nicklas 560   static class CloseResourceOutputStream
4338 07 Feb 17 nicklas 561     extends FilterOutputStream
4338 07 Feb 17 nicklas 562   {
4338 07 Feb 17 nicklas 563     private final Closeable resource;
4338 07 Feb 17 nicklas 564
4338 07 Feb 17 nicklas 565     CloseResourceOutputStream(OutputStream parent, Closeable resource)
4338 07 Feb 17 nicklas 566     {
4338 07 Feb 17 nicklas 567       super(parent);
4338 07 Feb 17 nicklas 568       this.resource = resource;
4338 07 Feb 17 nicklas 569     }
4278 20 Dec 16 nicklas 570     
4338 07 Feb 17 nicklas 571     @Override
4338 07 Feb 17 nicklas 572     public void close() 
4338 07 Feb 17 nicklas 573       throws IOException 
4338 07 Feb 17 nicklas 574     {
4338 07 Feb 17 nicklas 575       try
4338 07 Feb 17 nicklas 576       {
4338 07 Feb 17 nicklas 577         super.close();
4338 07 Feb 17 nicklas 578       }
4338 07 Feb 17 nicklas 579       finally
4338 07 Feb 17 nicklas 580       {
4338 07 Feb 17 nicklas 581         resource.close();
4338 07 Feb 17 nicklas 582       }
4338 07 Feb 17 nicklas 583     }
4338 07 Feb 17 nicklas 584     
4338 07 Feb 17 nicklas 585   }
4278 20 Dec 16 nicklas 586 }