Coverage Report - org.mule.transport.sftp.SftpClient
 
Classes in this File Line Coverage Branch Coverage Complexity
SftpClient
0%
0/215
0%
0/90
0
 
 1  
 /*
 2  
  * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
 3  
  * The software in this package is published under the terms of the CPAL v1.0
 4  
  * license, a copy of which has been included with this distribution in the
 5  
  * LICENSE.txt file.
 6  
  */
 7  
 package org.mule.transport.sftp;
 8  
 
 9  
 import org.mule.api.endpoint.ImmutableEndpoint;
 10  
 import org.mule.transport.sftp.notification.SftpNotifier;
 11  
 
 12  
 import com.jcraft.jsch.Channel;
 13  
 import com.jcraft.jsch.ChannelSftp;
 14  
 import com.jcraft.jsch.ChannelSftp.LsEntry;
 15  
 import com.jcraft.jsch.JSch;
 16  
 import com.jcraft.jsch.JSchException;
 17  
 import com.jcraft.jsch.Session;
 18  
 import com.jcraft.jsch.SftpATTRS;
 19  
 import com.jcraft.jsch.SftpException;
 20  
 
 21  
 import java.io.File;
 22  
 import java.io.IOException;
 23  
 import java.io.InputStream;
 24  
 import java.util.ArrayList;
 25  
 import java.util.List;
 26  
 import java.util.Properties;
 27  
 import java.util.Vector;
 28  
 
 29  
 import org.apache.commons.lang.NotImplementedException;
 30  
 import org.apache.commons.logging.Log;
 31  
 import org.apache.commons.logging.LogFactory;
 32  
 
 33  
 import static org.mule.transport.sftp.notification.SftpTransportNotification.SFTP_DELETE_ACTION;
 34  
 import static org.mule.transport.sftp.notification.SftpTransportNotification.SFTP_GET_ACTION;
 35  
 import static org.mule.transport.sftp.notification.SftpTransportNotification.SFTP_PUT_ACTION;
 36  
 import static org.mule.transport.sftp.notification.SftpTransportNotification.SFTP_RENAME_ACTION;
 37  
 
 38  
 /**
 39  
  * <code>SftpClient</code> Wrapper around jsch sftp library. Provides access to basic
 40  
  * sftp commands.
 41  
  */
 42  
 
 43  
 public class SftpClient
 44  
 {
 45  0
     private Log logger = LogFactory.getLog(getClass());
 46  
 
 47  
     public static final String CHANNEL_SFTP = "sftp";
 48  
 
 49  
     public static final String STRICT_HOST_KEY_CHECKING = "StrictHostKeyChecking";
 50  
 
 51  
     private ChannelSftp channelSftp;
 52  
 
 53  
     private JSch jsch;
 54  
     private SftpNotifier notifier;
 55  
 
 56  
     private Session session;
 57  
 
 58  
     private final String host;
 59  
 
 60  0
     private int port = 22;
 61  
 
 62  
     private String home;
 63  
 
 64  
     // Keep track of the current working directory for improved logging.
 65  0
     private String currentDirectory = "";
 66  
 
 67  0
     private static final Object lock = new Object();
 68  
 
 69  
     public SftpClient(String host)
 70  
     {
 71  0
         this(host, null);
 72  0
     }
 73  
 
 74  
     public SftpClient(String host, SftpNotifier notifier)
 75  0
     {
 76  0
         this.host = host;
 77  0
         this.notifier = notifier;
 78  
 
 79  0
         jsch = new JSch();
 80  0
     }
 81  
 
 82  
     public void changeWorkingDirectory(String wd) throws IOException
 83  
     {
 84  0
         currentDirectory = wd;
 85  
 
 86  
         try
 87  
         {
 88  0
             wd = getAbsolutePath(wd);
 89  0
             if (logger.isDebugEnabled())
 90  
             {
 91  0
                 logger.debug("Attempting to cwd to: " + wd);
 92  
             }
 93  0
             channelSftp.cd(wd);
 94  
         }
 95  0
         catch (SftpException e)
 96  
         {
 97  0
             String message = "Error '" + e.getMessage() + "' occurred when trying to CDW to '" + wd + "'.";
 98  0
             logger.error(message);
 99  0
             throw new IOException(message);
 100  0
         }
 101  0
     }
 102  
 
 103  
     /**
 104  
      * Converts a relative path to an absolute path according to
 105  
      * http://tools.ietf.org/html/draft-ietf-secsh-scp-sftp-ssh-uri-04.
 106  
      *
 107  
      * @param path relative path
 108  
      * @return Absolute path
 109  
      */
 110  
     public String getAbsolutePath(String path)
 111  
     {
 112  0
         if (path.startsWith("/~"))
 113  
         {
 114  0
             return home + path.substring(2, path.length());
 115  
         }
 116  
 
 117  
         // Already absolute!
 118  0
         return path;
 119  
     }
 120  
 
 121  
     public void login(String user, String password) throws IOException
 122  
     {
 123  
         try
 124  
         {
 125  0
             Properties hash = new Properties();
 126  0
             hash.put(STRICT_HOST_KEY_CHECKING, "no");
 127  
 
 128  0
             session = jsch.getSession(user, host);
 129  0
             session.setConfig(hash);
 130  0
             session.setPort(port);
 131  0
             session.setPassword(password);
 132  0
             session.connect();
 133  
 
 134  0
             Channel channel = session.openChannel(CHANNEL_SFTP);
 135  0
             channel.connect();
 136  
 
 137  0
             channelSftp = (ChannelSftp) channel;
 138  0
             setHome(channelSftp.pwd());
 139  
         }
 140  0
         catch (JSchException e)
 141  
         {
 142  0
             logAndThrowLoginError(user, e);
 143  
         }
 144  0
         catch (SftpException e)
 145  
         {
 146  0
             logAndThrowLoginError(user, e);
 147  0
         }
 148  0
     }
 149  
 
 150  
     public void login(String user, String identityFile, String passphrase) throws IOException
 151  
     {
 152  
         // Lets first check that the identityFile exist
 153  0
         if (!new File(identityFile).exists())
 154  
         {
 155  0
             throw new IOException("IdentityFile '" + identityFile + "' not found");
 156  
         }
 157  
 
 158  
         try
 159  
         {
 160  0
             if (passphrase == null || "".equals(passphrase))
 161  
             {
 162  0
                 jsch.addIdentity(new File(identityFile).getAbsolutePath());
 163  
             }
 164  
             else
 165  
             {
 166  0
                 jsch.addIdentity(new File(identityFile).getAbsolutePath(), passphrase);
 167  
             }
 168  
 
 169  0
             Properties hash = new Properties();
 170  0
             hash.put(STRICT_HOST_KEY_CHECKING, "no");
 171  
 
 172  0
             session = jsch.getSession(user, host);
 173  0
             session.setConfig(hash);
 174  0
             session.setPort(port);
 175  0
             session.connect();
 176  
 
 177  0
             Channel channel = session.openChannel(CHANNEL_SFTP);
 178  0
             channel.connect();
 179  
 
 180  0
             channelSftp = (ChannelSftp) channel;
 181  0
             setHome(channelSftp.pwd());
 182  
         }
 183  0
         catch (JSchException e)
 184  
         {
 185  0
             logAndThrowLoginError(user, e);
 186  
         }
 187  0
         catch (SftpException e)
 188  
         {
 189  0
             logAndThrowLoginError(user, e);
 190  0
         }
 191  0
     }
 192  
 
 193  
     private void logAndThrowLoginError(String user, Exception e) throws IOException
 194  
     {
 195  0
         logger.error("Error during login to " + user + "@" + host, e);
 196  0
         throw new IOException("Error during login to " + user + "@" + host + ": " + e.getMessage());
 197  
     }
 198  
 
 199  
     public void setPort(int port)
 200  
     {
 201  0
         this.port = port;
 202  0
     }
 203  
 
 204  
     public void rename(String filename, String dest) throws IOException
 205  
     {
 206  
         // Notify sftp rename file action
 207  0
         if (notifier != null)
 208  
         {
 209  0
             notifier.notify(SFTP_RENAME_ACTION, "from: " + currentDirectory + "/" + filename + " - to: "
 210  
                                                 + dest);
 211  
         }
 212  
 
 213  0
         String absolutePath = getAbsolutePath(dest);
 214  
         try
 215  
         {
 216  0
             if (logger.isDebugEnabled())
 217  
             {
 218  0
                 logger.debug("Will try to rename " + currentDirectory + "/" + filename + " to "
 219  
                              + absolutePath);
 220  
             }
 221  0
             channelSftp.rename(filename, absolutePath);
 222  
         }
 223  0
         catch (SftpException e)
 224  
         {
 225  0
             throw new IOException(e.getMessage());
 226  
             // throw new IOException("Error occured when renaming " +
 227  
             // currentDirectory + "/" + filename + " to " + absolutePath +
 228  
             // ". Error Message=" + e.getMessage());
 229  0
         }
 230  0
     }
 231  
 
 232  
     public void deleteFile(String fileName) throws IOException
 233  
     {
 234  
         // Notify sftp delete file action
 235  0
         if (notifier != null)
 236  
         {
 237  0
             notifier.notify(SFTP_DELETE_ACTION, currentDirectory + "/" + fileName);
 238  
         }
 239  
 
 240  
         try
 241  
         {
 242  0
             if (logger.isDebugEnabled())
 243  
             {
 244  0
                 logger.debug("Will try to delete " + fileName);
 245  
             }
 246  0
             channelSftp.rm(fileName);
 247  
         }
 248  0
         catch (SftpException e)
 249  
         {
 250  0
             throw new IOException(e.getMessage());
 251  0
         }
 252  0
     }
 253  
 
 254  
     public void disconnect()
 255  
     {
 256  0
         if (channelSftp != null)
 257  
         {
 258  0
             channelSftp.disconnect();
 259  
         }
 260  0
         if ((session != null) && session.isConnected())
 261  
         {
 262  0
             session.disconnect();
 263  
         }
 264  0
     }
 265  
 
 266  
     public boolean isConnected()
 267  
     {
 268  0
         return (channelSftp != null) && channelSftp.isConnected() && !channelSftp.isClosed()
 269  
                && (session != null) && session.isConnected();
 270  
     }
 271  
 
 272  
     public String[] listFiles() throws IOException
 273  
     {
 274  0
         return listFiles(".");
 275  
     }
 276  
 
 277  
     public String[] listFiles(String path) throws IOException
 278  
     {
 279  0
         return listDirectory(path, true, false);
 280  
     }
 281  
 
 282  
     public String[] listDirectories() throws IOException
 283  
     {
 284  0
         return listDirectory(".", false, true);
 285  
     }
 286  
 
 287  
     public String[] listDirectories(String path) throws IOException
 288  
     {
 289  0
         return listDirectory(path, false, true);
 290  
     }
 291  
 
 292  
     private String[] listDirectory(String path, boolean includeFiles, boolean includeDirectories)
 293  
         throws IOException
 294  
     {
 295  
         try
 296  
         {
 297  0
             Vector vv = channelSftp.ls(path);
 298  0
             if (vv != null)
 299  
             {
 300  0
                 List<String> ret = new ArrayList<String>();
 301  0
                 for (int i = 0; i < vv.size(); i++)
 302  
                 {
 303  0
                     Object obj = vv.elementAt(i);
 304  0
                     if (obj instanceof com.jcraft.jsch.ChannelSftp.LsEntry)
 305  
                     {
 306  0
                         LsEntry entry = (LsEntry) obj;
 307  0
                         if (includeFiles && !entry.getAttrs().isDir())
 308  
                         {
 309  0
                             ret.add(entry.getFilename());
 310  
                         }
 311  0
                         if (includeDirectories && entry.getAttrs().isDir())
 312  
                         {
 313  0
                             if (!entry.getFilename().equals(".") && !entry.getFilename().equals(".."))
 314  
                             {
 315  0
                                 ret.add(entry.getFilename());
 316  
                             }
 317  
                         }
 318  
                     }
 319  
                 }
 320  0
                 return ret.toArray(new String[ret.size()]);
 321  
             }
 322  
         }
 323  0
         catch (SftpException e)
 324  
         {
 325  0
             throw new IOException(e.getMessage());
 326  0
         }
 327  0
         return null;
 328  
     }
 329  
 
 330  
     // public boolean logout()
 331  
     // {
 332  
     // return true;
 333  
     // }
 334  
 
 335  
     public InputStream retrieveFile(String fileName) throws IOException
 336  
     {
 337  
         // Notify sftp get file action
 338  0
         long size = getSize(fileName);
 339  0
         if (notifier != null)
 340  
         {
 341  0
             notifier.notify(SFTP_GET_ACTION, currentDirectory + "/" + fileName, size);
 342  
         }
 343  
 
 344  
         try
 345  
         {
 346  0
             return channelSftp.get(fileName);
 347  
         }
 348  0
         catch (SftpException e)
 349  
         {
 350  0
             throw new IOException(e.getMessage() + ".  Filename is " + fileName);
 351  
         }
 352  
     }
 353  
 
 354  
     // public OutputStream storeFileStream(String fileName) throws IOException
 355  
     // {
 356  
     // try
 357  
     // {
 358  
     // return channelSftp.put(fileName);
 359  
     // } catch (SftpException e)
 360  
     // {
 361  
     // throw new IOException(e.getMessage());
 362  
     // }
 363  
     // }
 364  
 
 365  
     public void storeFile(String fileName, InputStream stream) throws IOException
 366  
     {
 367  
         try
 368  
         {
 369  
 
 370  
             // Notify sftp put file action
 371  0
             if (notifier != null)
 372  
             {
 373  0
                 notifier.notify(SFTP_PUT_ACTION, currentDirectory + "/" + fileName);
 374  
             }
 375  
 
 376  0
             if (logger.isDebugEnabled())
 377  
             {
 378  0
                 logger.debug("Sending to SFTP service: Stream = " + stream + " , filename = " + fileName);
 379  
             }
 380  
 
 381  0
             channelSftp.put(stream, fileName);
 382  
         }
 383  0
         catch (SftpException e)
 384  
         {
 385  0
             logger.error("Error writing data over SFTP service, error was: " + e.getMessage(), e);
 386  0
             throw new IOException(e.getMessage());
 387  0
         }
 388  0
     }
 389  
 
 390  
     public void storeFile(String fileNameLocal, String fileNameRemote) throws IOException
 391  
     {
 392  
         try
 393  
         {
 394  0
             channelSftp.put(fileNameLocal, fileNameRemote);
 395  
         }
 396  0
         catch (SftpException e)
 397  
         {
 398  0
             throw new IOException(e.getMessage());
 399  0
         }
 400  0
     }
 401  
 
 402  
     public long getSize(String filename) throws IOException
 403  
     {
 404  
         try
 405  
         {
 406  0
             return channelSftp.stat(filename).getSize();
 407  
         }
 408  0
         catch (SftpException e)
 409  
         {
 410  0
             throw new IOException(e.getMessage() + " (" + currentDirectory + "/" + filename + ")");
 411  
         }
 412  
     }
 413  
 
 414  
     /**
 415  
      * @param filename File name
 416  
      * @return Number of seconds since the file was written to
 417  
      * @throws IOException If an error occurs
 418  
      */
 419  
     public long getLastModifiedTime(String filename) throws IOException
 420  
     {
 421  
         try
 422  
         {
 423  0
             SftpATTRS attrs = channelSftp.stat("./" + filename);
 424  0
             return attrs.getMTime() * 1000L;
 425  
         }
 426  0
         catch (SftpException e)
 427  
         {
 428  0
             throw new IOException(e.getMessage());
 429  
         }
 430  
     }
 431  
 
 432  
     /**
 433  
      * Creates a directory
 434  
      *
 435  
      * @param directoryName The directory name
 436  
      * @throws IOException If an error occurs
 437  
      */
 438  
     public void mkdir(String directoryName) throws IOException
 439  
     {
 440  
         try
 441  
         {
 442  0
             if (logger.isDebugEnabled())
 443  
             {
 444  0
                 logger.debug("Will try to create directory " + directoryName);
 445  
             }
 446  0
             channelSftp.mkdir(directoryName);
 447  
         }
 448  0
         catch (SftpException e)
 449  
         {
 450  
             // Don't throw e.getmessage since we only get "2: No such file"..
 451  0
             throw new IOException("Could not create the directory '" + directoryName + "', caused by: "
 452  
                                   + e.getMessage());
 453  
             // throw new IOException("Could not create the directory '" +
 454  
             // directoryName + "' in '" + currentDirectory + "', caused by: " +
 455  
             // e.getMessage());
 456  0
         }
 457  0
     }
 458  
 
 459  
     public void deleteDirectory(String path) throws IOException
 460  
     {
 461  0
         path = getAbsolutePath(path);
 462  
         try
 463  
         {
 464  0
             if (logger.isDebugEnabled())
 465  
             {
 466  0
                 logger.debug("Will try to delete directory " + path);
 467  
             }
 468  0
             channelSftp.rmdir(path);
 469  
         }
 470  0
         catch (SftpException e)
 471  
         {
 472  0
             throw new IOException(e.getMessage());
 473  0
         }
 474  0
     }
 475  
 
 476  
     /**
 477  
      * Setter for 'home'
 478  
      *
 479  
      * @param home The path to home
 480  
      */
 481  
     void setHome(String home)
 482  
     {
 483  0
         this.home = home;
 484  0
     }
 485  
 
 486  
     /**
 487  
      * @return the ChannelSftp - useful for some tests
 488  
      */
 489  
     public ChannelSftp getChannelSftp()
 490  
     {
 491  0
         return channelSftp;
 492  
     }
 493  
 
 494  
     /**
 495  
      * Creates the directory if it not already exists. TODO: check if the SftpUtil &
 496  
      * SftpClient methods can be merged Note, this method is synchronized because it
 497  
      * in rare cases can be called from two threads at the same time and thus cause
 498  
      * an error.
 499  
      *
 500  
      * @param endpoint
 501  
      * @param newDir
 502  
      * @throws IOException
 503  
      */
 504  
     public void createSftpDirIfNotExists(ImmutableEndpoint endpoint, String newDir) throws IOException
 505  
     {
 506  0
         String newDirAbs = endpoint.getEndpointURI().getPath() + "/" + newDir;
 507  
 
 508  0
         String currDir = currentDirectory;
 509  
 
 510  0
         if (logger.isDebugEnabled())
 511  
         {
 512  0
             logger.debug("CHANGE DIR FROM " + currentDirectory + " TO " + newDirAbs);
 513  
         }
 514  
 
 515  
         // We need to have a synchronized block if two++ threads tries to
 516  
         // create the same directory at the same time
 517  0
         synchronized (lock)
 518  
         {
 519  
             // Try to change directory to the new dir, if it fails - create it
 520  
             try
 521  
             {
 522  
                 // This method will throw an exception if the directory does not
 523  
                 // exist.
 524  0
                 changeWorkingDirectory(newDirAbs);
 525  
             }
 526  0
             catch (IOException e)
 527  
             {
 528  0
                 logger.info("Got an exception when trying to change the working directory to the new dir. "
 529  
                             + "Will try to create the directory " + newDirAbs);
 530  0
                 changeWorkingDirectory(endpoint.getEndpointURI().getPath());
 531  0
                 mkdir(newDir);
 532  
 
 533  
                 // Now it should exist!
 534  0
                 changeWorkingDirectory(newDirAbs);
 535  
             }
 536  
             finally
 537  
             {
 538  0
                 changeWorkingDirectory(currDir);
 539  0
                 if (logger.isDebugEnabled())
 540  
                 {
 541  0
                     logger.debug("DIR IS NOW BACK TO " + currentDirectory);
 542  
                 }
 543  
             }
 544  0
         }
 545  0
     }
 546  
 
 547  
     public String duplicateHandling(String destDir, String filename, String duplicateHandling)
 548  
         throws IOException
 549  
     {
 550  0
         if (duplicateHandling.equals(SftpConnector.PROPERTY_DUPLICATE_HANDLING_ASS_SEQ_NO))
 551  
         {
 552  0
             filename = createUniqueName(destDir, filename);
 553  
 
 554  
         }
 555  0
         else if (duplicateHandling.equals(SftpConnector.PROPERTY_DUPLICATE_HANDLING_OVERWRITE))
 556  
         {
 557  
             // TODO. ML FIX. Implement this!
 558  0
             throw new NotImplementedException("Strategy "
 559  
                                               + SftpConnector.PROPERTY_DUPLICATE_HANDLING_OVERWRITE
 560  
                                               + " is not yet implemented");
 561  
 
 562  
         }
 563  
         else
 564  
         {
 565  
             // Nothing to do in the case of
 566  
             // PROPERTY_DUPLICATE_HANDLING_THROW_EXCEPTION, if the file already
 567  
             // exists then an error will be throwed...
 568  
         }
 569  
 
 570  0
         return filename;
 571  
     }
 572  
 
 573  
     private String createUniqueName(String dir, String path) throws IOException
 574  
     {
 575  0
         int fileIdx = 1;
 576  
 
 577  
         String filename;
 578  
         String fileType;
 579  0
         int fileTypeIdx = path.lastIndexOf('.');
 580  0
         if (fileTypeIdx == -1)
 581  
         {
 582  
             // No file type/extension found
 583  0
             filename = path;
 584  0
             fileType = "";
 585  
         }
 586  
         else
 587  
         {
 588  0
             fileType = path.substring(fileTypeIdx); // Let the fileType include the
 589  
             // leading '.'
 590  0
             filename = path.substring(0, fileTypeIdx);
 591  
         }
 592  
 
 593  0
         if (logger.isDebugEnabled())
 594  
         {
 595  0
             logger.debug("Create a unique name for: " + path + " (" + dir + " - " + filename + " - "
 596  
                          + fileType + ")");
 597  
         }
 598  
 
 599  0
         String uniqueFilename = filename;
 600  0
         String[] existingFiles = listFiles(getAbsolutePath(dir));
 601  
 
 602  0
         while (existsFile(existingFiles, uniqueFilename, fileType))
 603  
         {
 604  0
             uniqueFilename = filename + '_' + fileIdx++;
 605  
         }
 606  
 
 607  0
         uniqueFilename = uniqueFilename + fileType;
 608  0
         if (!path.equals(uniqueFilename) && logger.isInfoEnabled())
 609  
         {
 610  0
             logger.info("A file with the original filename (" + dir + "/" + path
 611  
                         + ") already exists, new name: " + uniqueFilename);
 612  
         }
 613  0
         if (logger.isDebugEnabled())
 614  
         {
 615  0
             logger.debug("Unique name returned: " + uniqueFilename);
 616  
         }
 617  0
         return uniqueFilename;
 618  
     }
 619  
 
 620  
     private boolean existsFile(String[] files, String filename, String fileType)
 621  
     {
 622  0
         boolean existsFile = false;
 623  0
         filename += fileType;
 624  0
         for (String file : files)
 625  
         {
 626  0
             if (file.equals(filename))
 627  
             {
 628  0
                 if (logger.isDebugEnabled())
 629  
                 {
 630  0
                     logger.debug("Found existing file: " + file);
 631  
                 }
 632  0
                 existsFile = true;
 633  
             }
 634  
         }
 635  0
         return existsFile;
 636  
     }
 637  
 
 638  
     public void chmod(String path, int permissions) throws SftpException
 639  
     {
 640  0
         path = getAbsolutePath(path);
 641  0
         if (logger.isDebugEnabled())
 642  
         {
 643  0
             logger.debug("Will try to chmod directory '" + path + "' to permission " + permissions);
 644  
         }
 645  0
         channelSftp.chmod(permissions, path);
 646  0
     }
 647  
 
 648  
     public void setNotifier(SftpNotifier notifier)
 649  
     {
 650  0
         this.notifier = notifier;
 651  0
     }
 652  
 
 653  
     public String getHost()
 654  
     {
 655  0
         return host;
 656  
     }
 657  
 }