001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.camel.component.file.remote; 018 019 import java.io.ByteArrayOutputStream; 020 import java.io.File; 021 import java.io.FileOutputStream; 022 import java.io.IOException; 023 import java.io.InputStream; 024 import java.io.OutputStream; 025 import java.util.ArrayList; 026 import java.util.Hashtable; 027 import java.util.List; 028 import java.util.Vector; 029 030 import com.jcraft.jsch.ChannelSftp; 031 import com.jcraft.jsch.JSch; 032 import com.jcraft.jsch.JSchException; 033 import com.jcraft.jsch.Session; 034 import com.jcraft.jsch.SftpException; 035 import com.jcraft.jsch.UIKeyboardInteractive; 036 import com.jcraft.jsch.UserInfo; 037 import org.apache.camel.Exchange; 038 import org.apache.camel.InvalidPayloadException; 039 import org.apache.camel.component.file.FileComponent; 040 import org.apache.camel.component.file.GenericFile; 041 import org.apache.camel.component.file.GenericFileEndpoint; 042 import org.apache.camel.component.file.GenericFileExist; 043 import org.apache.camel.component.file.GenericFileOperationFailedException; 044 import org.apache.camel.util.ExchangeHelper; 045 import org.apache.camel.util.FileUtil; 046 import org.apache.camel.util.IOHelper; 047 import org.apache.camel.util.ObjectHelper; 048 import org.slf4j.Logger; 049 import org.slf4j.LoggerFactory; 050 051 import static org.apache.camel.util.ObjectHelper.isNotEmpty; 052 053 /** 054 * SFTP remote file operations 055 */ 056 public class SftpOperations implements RemoteFileOperations<ChannelSftp.LsEntry> { 057 private static final transient Logger LOG = LoggerFactory.getLogger(SftpOperations.class); 058 private SftpEndpoint endpoint; 059 private ChannelSftp channel; 060 private Session session; 061 062 /** 063 * Extended user info which supports interactive keyboard mode, by entering the password. 064 */ 065 public static interface ExtendedUserInfo extends UserInfo, UIKeyboardInteractive { 066 } 067 068 public void setEndpoint(GenericFileEndpoint endpoint) { 069 this.endpoint = (SftpEndpoint) endpoint; 070 } 071 072 public boolean connect(RemoteFileConfiguration configuration) throws GenericFileOperationFailedException { 073 if (isConnected()) { 074 // already connected 075 return true; 076 } 077 078 boolean connected = false; 079 int attempt = 0; 080 081 while (!connected) { 082 try { 083 if (LOG.isTraceEnabled() && attempt > 0) { 084 LOG.trace("Reconnect attempt #{} connecting to + {}", attempt, configuration.remoteServerInformation()); 085 } 086 087 if (channel == null || !channel.isConnected()) { 088 if (session == null || !session.isConnected()) { 089 LOG.trace("Session isn't connected, trying to recreate and connect."); 090 session = createSession(configuration); 091 if (endpoint.getConfiguration().getConnectTimeout() > 0) { 092 LOG.trace("Connecting use connectTimeout: " + endpoint.getConfiguration().getConnectTimeout() + " ..."); 093 session.connect(endpoint.getConfiguration().getConnectTimeout()); 094 } else { 095 LOG.trace("Connecting ..."); 096 session.connect(); 097 } 098 } 099 100 LOG.trace("Channel isn't connected, trying to recreate and connect."); 101 channel = (ChannelSftp) session.openChannel("sftp"); 102 103 if (endpoint.getConfiguration().getConnectTimeout() > 0) { 104 LOG.trace("Connecting use connectTimeout: " + endpoint.getConfiguration().getConnectTimeout() + " ..."); 105 channel.connect(endpoint.getConfiguration().getConnectTimeout()); 106 } else { 107 LOG.trace("Connecting ..."); 108 channel.connect(); 109 } 110 LOG.info("Connected to " + configuration.remoteServerInformation()); 111 } 112 113 // yes we could connect 114 connected = true; 115 } catch (Exception e) { 116 // check if we are interrupted so we can break out 117 if (Thread.currentThread().isInterrupted()) { 118 throw new GenericFileOperationFailedException("Interrupted during connecting", new InterruptedException("Interrupted during connecting")); 119 } 120 121 GenericFileOperationFailedException failed = new GenericFileOperationFailedException("Cannot connect to " + configuration.remoteServerInformation(), e); 122 LOG.trace("Cannot connect due: {}", failed.getMessage()); 123 attempt++; 124 if (attempt > endpoint.getMaximumReconnectAttempts()) { 125 throw failed; 126 } 127 if (endpoint.getReconnectDelay() > 0) { 128 try { 129 Thread.sleep(endpoint.getReconnectDelay()); 130 } catch (InterruptedException ie) { 131 // we could potentially also be interrupted during sleep 132 Thread.currentThread().interrupt(); 133 throw new GenericFileOperationFailedException("Interrupted during sleeping", ie); 134 } 135 } 136 } 137 } 138 139 return true; 140 } 141 142 protected Session createSession(final RemoteFileConfiguration configuration) throws JSchException { 143 final JSch jsch = new JSch(); 144 JSch.setLogger(new JSchLogger()); 145 146 SftpConfiguration sftpConfig = (SftpConfiguration) configuration; 147 148 if (isNotEmpty(sftpConfig.getCiphers())) { 149 LOG.debug("Using ciphers: {}", sftpConfig.getCiphers()); 150 Hashtable<String, String> ciphers = new Hashtable<String, String>(); 151 ciphers.put("cipher.s2c", sftpConfig.getCiphers()); 152 ciphers.put("cipher.c2s", sftpConfig.getCiphers()); 153 JSch.setConfig(ciphers); 154 } 155 156 if (isNotEmpty(sftpConfig.getPrivateKeyFile())) { 157 LOG.debug("Using private keyfile: {}", sftpConfig.getPrivateKeyFile()); 158 if (isNotEmpty(sftpConfig.getPrivateKeyFilePassphrase())) { 159 jsch.addIdentity(sftpConfig.getPrivateKeyFile(), sftpConfig.getPrivateKeyFilePassphrase()); 160 } else { 161 jsch.addIdentity(sftpConfig.getPrivateKeyFile()); 162 } 163 } 164 165 if (isNotEmpty(sftpConfig.getKnownHostsFile())) { 166 LOG.debug("Using knownhosts file: {}", sftpConfig.getKnownHostsFile()); 167 jsch.setKnownHosts(sftpConfig.getKnownHostsFile()); 168 } 169 170 final Session session = jsch.getSession(configuration.getUsername(), configuration.getHost(), configuration.getPort()); 171 172 if (isNotEmpty(sftpConfig.getStrictHostKeyChecking())) { 173 LOG.debug("Using StrickHostKeyChecking: {}", sftpConfig.getStrictHostKeyChecking()); 174 session.setConfig("StrictHostKeyChecking", sftpConfig.getStrictHostKeyChecking()); 175 } 176 177 session.setServerAliveInterval(sftpConfig.getServerAliveInterval()); 178 session.setServerAliveCountMax(sftpConfig.getServerAliveCountMax()); 179 180 // compression 181 if (sftpConfig.getCompression() > 0) { 182 LOG.debug("Using compression: {}", sftpConfig.getCompression()); 183 session.setConfig("compression.s2c", "[email protected],zlib,none"); 184 session.setConfig("compression.c2s", "[email protected],zlib,none"); 185 session.setConfig("compression_level", Integer.toString(sftpConfig.getCompression())); 186 } 187 188 // set user information 189 session.setUserInfo(new ExtendedUserInfo() { 190 public String getPassphrase() { 191 return null; 192 } 193 194 public String getPassword() { 195 return configuration.getPassword(); 196 } 197 198 public boolean promptPassword(String s) { 199 return true; 200 } 201 202 public boolean promptPassphrase(String s) { 203 return true; 204 } 205 206 public boolean promptYesNo(String s) { 207 LOG.warn("Server asks for confirmation (yes|no): " + s + ". Camel will answer no."); 208 // Return 'false' indicating modification of the hosts file is disabled. 209 return false; 210 } 211 212 public void showMessage(String s) { 213 LOG.trace("Message received from Server: " + s); 214 } 215 216 public String[] promptKeyboardInteractive(String destination, String name, 217 String instruction, String[] prompt, boolean[] echo) { 218 return new String[]{configuration.getPassword()}; 219 } 220 221 }); 222 return session; 223 } 224 225 private static final class JSchLogger implements com.jcraft.jsch.Logger { 226 227 public boolean isEnabled(int level) { 228 switch (level) { 229 case FATAL: 230 // use ERROR as FATAL 231 return LOG.isErrorEnabled(); 232 case ERROR: 233 return LOG.isErrorEnabled(); 234 case WARN: 235 return LOG.isWarnEnabled(); 236 case INFO: 237 return LOG.isInfoEnabled(); 238 default: 239 return LOG.isDebugEnabled(); 240 } 241 } 242 243 public void log(int level, String message) { 244 switch (level) { 245 case FATAL: 246 // use ERROR as FATAL 247 LOG.error("JSCH -> " + message); 248 break; 249 case ERROR: 250 LOG.error("JSCH -> " + message); 251 break; 252 case WARN: 253 LOG.warn("JSCH -> " + message); 254 break; 255 case INFO: 256 LOG.info("JSCH -> " + message); 257 break; 258 default: 259 LOG.debug("JSCH -> " + message); 260 break; 261 } 262 } 263 } 264 265 public boolean isConnected() throws GenericFileOperationFailedException { 266 return session != null && session.isConnected() && channel != null && channel.isConnected(); 267 } 268 269 public void disconnect() throws GenericFileOperationFailedException { 270 if (session != null && session.isConnected()) { 271 session.disconnect(); 272 } 273 if (channel != null && channel.isConnected()) { 274 channel.disconnect(); 275 } 276 } 277 278 public boolean deleteFile(String name) throws GenericFileOperationFailedException { 279 LOG.debug("Deleting file: {}", name); 280 try { 281 channel.rm(name); 282 return true; 283 } catch (SftpException e) { 284 throw new GenericFileOperationFailedException("Cannot delete file: " + name, e); 285 } 286 } 287 288 public boolean renameFile(String from, String to) throws GenericFileOperationFailedException { 289 LOG.debug("Renaming file: {} to: {}", from, to); 290 try { 291 channel.rename(from, to); 292 return true; 293 } catch (SftpException e) { 294 throw new GenericFileOperationFailedException("Cannot rename file from: " + from + " to: " + to, e); 295 } 296 } 297 298 public boolean buildDirectory(String directory, boolean absolute) throws GenericFileOperationFailedException { 299 // must normalize directory first 300 directory = endpoint.getConfiguration().normalizePath(directory); 301 302 LOG.trace("buildDirectory({},{})", directory, absolute); 303 // ignore absolute as all dirs are relative with FTP 304 boolean success = false; 305 306 String originalDirectory = getCurrentDirectory(); 307 try { 308 // maybe the full directory already exists 309 try { 310 channel.cd(directory); 311 success = true; 312 } catch (SftpException e) { 313 // ignore, we could not change directory so try to create it instead 314 } 315 316 if (!success) { 317 LOG.debug("Trying to build remote directory: {}", directory); 318 319 try { 320 channel.mkdir(directory); 321 success = true; 322 } catch (SftpException e) { 323 // we are here if the server side doesn't create intermediate folders 324 // so create the folder one by one 325 success = buildDirectoryChunks(directory); 326 } 327 } 328 } catch (IOException e) { 329 throw new GenericFileOperationFailedException("Cannot build directory: " + directory, e); 330 } catch (SftpException e) { 331 throw new GenericFileOperationFailedException("Cannot build directory: " + directory, e); 332 } finally { 333 // change back to original directory 334 if (originalDirectory != null) { 335 changeCurrentDirectory(originalDirectory); 336 } 337 } 338 339 return success; 340 } 341 342 private boolean buildDirectoryChunks(String dirName) throws IOException, SftpException { 343 final StringBuilder sb = new StringBuilder(dirName.length()); 344 final String[] dirs = dirName.split("/|\\\\"); 345 346 boolean success = false; 347 for (String dir : dirs) { 348 sb.append(dir).append('/'); 349 // must normalize the directory name 350 String directory = endpoint.getConfiguration().normalizePath(sb.toString()); 351 352 // do not try to build root folder (/ or \) 353 if (!(directory.equals("/") || directory.equals("\\"))) { 354 try { 355 LOG.trace("Trying to build remote directory by chunk: {}", directory); 356 357 channel.mkdir(directory); 358 success = true; 359 } catch (SftpException e) { 360 // ignore keep trying to create the rest of the path 361 } 362 } 363 } 364 365 return success; 366 } 367 368 public String getCurrentDirectory() throws GenericFileOperationFailedException { 369 LOG.trace("getCurrentDirectory()"); 370 try { 371 return channel.pwd(); 372 } catch (SftpException e) { 373 throw new GenericFileOperationFailedException("Cannot get current directory", e); 374 } 375 } 376 377 public void changeCurrentDirectory(String path) throws GenericFileOperationFailedException { 378 LOG.trace("changeCurrentDirectory({})", path); 379 if (ObjectHelper.isEmpty(path)) { 380 return; 381 } 382 383 // must compact path so FTP server can traverse correctly 384 path = FileUtil.compactPath(path); 385 386 // not stepwise should change directory in one operation 387 if (!endpoint.getConfiguration().isStepwise()) { 388 doChangeDirectory(path); 389 return; 390 } 391 392 // if it starts with the root path then a little special handling for that 393 if (FileUtil.hasLeadingSeparator(path)) { 394 // change to root path 395 doChangeDirectory(path.substring(0, 1)); 396 path = path.substring(1); 397 } 398 399 // split into multiple dirs 400 final String[] dirs = path.split("/|\\\\"); 401 402 if (dirs == null || dirs.length == 0) { 403 // path was just a relative single path 404 doChangeDirectory(path); 405 return; 406 } 407 408 // there are multiple dirs so do this in chunks 409 for (String dir : dirs) { 410 doChangeDirectory(dir); 411 } 412 } 413 414 private void doChangeDirectory(String path) { 415 if (path == null || ".".equals(path) || ObjectHelper.isEmpty(path)) { 416 return; 417 } 418 419 LOG.trace("Changing directory: {}", path); 420 try { 421 channel.cd(path); 422 } catch (SftpException e) { 423 throw new GenericFileOperationFailedException("Cannot change directory to: " + path, e); 424 } 425 } 426 427 public void changeToParentDirectory() throws GenericFileOperationFailedException { 428 LOG.trace("changeToParentDirectory()"); 429 String current = getCurrentDirectory(); 430 431 String parent = FileUtil.compactPath(current + "/.."); 432 // must start with absolute 433 if (!parent.startsWith("/")) { 434 parent = "/" + parent; 435 } 436 437 changeCurrentDirectory(parent); 438 } 439 440 public List<ChannelSftp.LsEntry> listFiles() throws GenericFileOperationFailedException { 441 return listFiles("."); 442 } 443 444 public List<ChannelSftp.LsEntry> listFiles(String path) throws GenericFileOperationFailedException { 445 LOG.trace("listFiles({})", path); 446 if (ObjectHelper.isEmpty(path)) { 447 // list current directory if file path is not given 448 path = "."; 449 } 450 451 try { 452 final List<ChannelSftp.LsEntry> list = new ArrayList<ChannelSftp.LsEntry>(); 453 Vector files = channel.ls(path); 454 // can return either null or an empty list depending on FTP servers 455 if (files != null) { 456 for (Object file : files) { 457 list.add((ChannelSftp.LsEntry) file); 458 } 459 } 460 return list; 461 } catch (SftpException e) { 462 throw new GenericFileOperationFailedException("Cannot list directory: " + path, e); 463 } 464 } 465 466 public boolean retrieveFile(String name, Exchange exchange) throws GenericFileOperationFailedException { 467 LOG.trace("retrieveFile({})", name); 468 if (ObjectHelper.isNotEmpty(endpoint.getLocalWorkDirectory())) { 469 // local work directory is configured so we should store file content as files in this local directory 470 return retrieveFileToFileInLocalWorkDirectory(name, exchange); 471 } else { 472 // store file content directory as stream on the body 473 return retrieveFileToStreamInBody(name, exchange); 474 } 475 } 476 477 @SuppressWarnings("unchecked") 478 private boolean retrieveFileToStreamInBody(String name, Exchange exchange) throws GenericFileOperationFailedException { 479 OutputStream os = null; 480 try { 481 os = new ByteArrayOutputStream(); 482 GenericFile<ChannelSftp.LsEntry> target = 483 (GenericFile<ChannelSftp.LsEntry>) exchange.getProperty(FileComponent.FILE_EXCHANGE_FILE); 484 ObjectHelper.notNull(target, "Exchange should have the " + FileComponent.FILE_EXCHANGE_FILE + " set"); 485 target.setBody(os); 486 487 String remoteName = name; 488 String currentDir = null; 489 if (endpoint.getConfiguration().isStepwise()) { 490 // remember current directory 491 currentDir = getCurrentDirectory(); 492 493 // change directory to path where the file is to be retrieved 494 // (must do this as some FTP servers cannot retrieve using absolute path) 495 String path = FileUtil.onlyPath(name); 496 if (path != null) { 497 changeCurrentDirectory(path); 498 } 499 // remote name is now only the file name as we just changed directory 500 remoteName = FileUtil.stripPath(name); 501 } 502 503 // use input stream which works with Apache SSHD used for testing 504 InputStream is = channel.get(remoteName); 505 IOHelper.copyAndCloseInput(is, os); 506 507 // change back to current directory 508 if (endpoint.getConfiguration().isStepwise()) { 509 changeCurrentDirectory(currentDir); 510 } 511 512 return true; 513 } catch (IOException e) { 514 throw new GenericFileOperationFailedException("Cannot retrieve file: " + name, e); 515 } catch (SftpException e) { 516 throw new GenericFileOperationFailedException("Cannot retrieve file: " + name, e); 517 } finally { 518 IOHelper.close(os, "retrieve: " + name, LOG); 519 } 520 } 521 522 @SuppressWarnings("unchecked") 523 private boolean retrieveFileToFileInLocalWorkDirectory(String name, Exchange exchange) throws GenericFileOperationFailedException { 524 File temp; 525 File local = new File(endpoint.getLocalWorkDirectory()); 526 OutputStream os; 527 GenericFile<ChannelSftp.LsEntry> file = 528 (GenericFile<ChannelSftp.LsEntry>) exchange.getProperty(FileComponent.FILE_EXCHANGE_FILE); 529 ObjectHelper.notNull(file, "Exchange should have the " + FileComponent.FILE_EXCHANGE_FILE + " set"); 530 try { 531 // use relative filename in local work directory 532 String relativeName = file.getRelativeFilePath(); 533 534 temp = new File(local, relativeName + ".inprogress"); 535 local = new File(local, relativeName); 536 537 // create directory to local work file 538 local.mkdirs(); 539 540 // delete any existing files 541 if (temp.exists()) { 542 if (!FileUtil.deleteFile(temp)) { 543 throw new GenericFileOperationFailedException("Cannot delete existing local work file: " + temp); 544 } 545 } 546 if (local.exists()) { 547 if (!FileUtil.deleteFile(local)) { 548 throw new GenericFileOperationFailedException("Cannot delete existing local work file: " + local); 549 } 550 } 551 552 // create new temp local work file 553 if (!temp.createNewFile()) { 554 throw new GenericFileOperationFailedException("Cannot create new local work file: " + temp); 555 } 556 557 // store content as a file in the local work directory in the temp handle 558 os = new FileOutputStream(temp); 559 560 // set header with the path to the local work file 561 exchange.getIn().setHeader(Exchange.FILE_LOCAL_WORK_PATH, local.getPath()); 562 } catch (Exception e) { 563 throw new GenericFileOperationFailedException("Cannot create new local work file: " + local); 564 } 565 566 try { 567 // store the java.io.File handle as the body 568 file.setBody(local); 569 570 String remoteName = name; 571 String currentDir = null; 572 if (endpoint.getConfiguration().isStepwise()) { 573 // remember current directory 574 currentDir = getCurrentDirectory(); 575 576 // change directory to path where the file is to be retrieved 577 // (must do this as some FTP servers cannot retrieve using absolute path) 578 String path = FileUtil.onlyPath(name); 579 if (path != null) { 580 changeCurrentDirectory(path); 581 } 582 // remote name is now only the file name as we just changed directory 583 remoteName = FileUtil.stripPath(name); 584 } 585 586 channel.get(remoteName, os); 587 588 // change back to current directory 589 if (endpoint.getConfiguration().isStepwise()) { 590 changeCurrentDirectory(currentDir); 591 } 592 593 } catch (SftpException e) { 594 LOG.trace("Error occurred during retrieving file: {} to local directory. Deleting local work file: {}", name, temp); 595 // failed to retrieve the file so we need to close streams and delete in progress file 596 // must close stream before deleting file 597 IOHelper.close(os, "retrieve: " + name, LOG); 598 boolean deleted = FileUtil.deleteFile(temp); 599 if (!deleted) { 600 LOG.warn("Error occurred during retrieving file: " + name + " to local directory. Cannot delete local work file: " + temp); 601 } 602 throw new GenericFileOperationFailedException("Cannot retrieve file: " + name, e); 603 } finally { 604 IOHelper.close(os, "retrieve: " + name, LOG); 605 } 606 607 LOG.debug("Retrieve file to local work file result: true"); 608 609 // operation went okay so rename temp to local after we have retrieved the data 610 LOG.trace("Renaming local in progress file from: {} to: {}", temp, local); 611 try { 612 if (!FileUtil.renameFile(temp, local, false)) { 613 throw new GenericFileOperationFailedException("Cannot rename local work file from: " + temp + " to: " + local); 614 } 615 } catch (IOException e) { 616 throw new GenericFileOperationFailedException("Cannot rename local work file from: " + temp + " to: " + local, e); 617 } 618 619 return true; 620 } 621 622 public boolean storeFile(String name, Exchange exchange) throws GenericFileOperationFailedException { 623 // must normalize name first 624 name = endpoint.getConfiguration().normalizePath(name); 625 626 LOG.trace("storeFile({})", name); 627 628 boolean answer = false; 629 String currentDir = null; 630 String path = FileUtil.onlyPath(name); 631 String targetName = name; 632 633 try { 634 if (path != null && endpoint.getConfiguration().isStepwise()) { 635 // must remember current dir so we stay in that directory after the write 636 currentDir = getCurrentDirectory(); 637 638 // change to path of name 639 changeCurrentDirectory(path); 640 641 // the target name should be without path, as we have changed directory 642 targetName = FileUtil.stripPath(name); 643 } 644 645 // store the file 646 answer = doStoreFile(name, targetName, exchange); 647 } finally { 648 // change back to current directory if we changed directory 649 if (currentDir != null) { 650 changeCurrentDirectory(currentDir); 651 } 652 } 653 654 return answer; 655 } 656 657 private boolean doStoreFile(String name, String targetName, Exchange exchange) throws GenericFileOperationFailedException { 658 LOG.trace("doStoreFile({})", targetName); 659 660 // if an existing file already exists what should we do? 661 if (endpoint.getFileExist() == GenericFileExist.Ignore || endpoint.getFileExist() == GenericFileExist.Fail) { 662 boolean existFile = existsFile(targetName); 663 if (existFile && endpoint.getFileExist() == GenericFileExist.Ignore) { 664 // ignore but indicate that the file was written 665 LOG.trace("An existing file already exists: {}. Ignore and do not override it.", name); 666 return true; 667 } else if (existFile && endpoint.getFileExist() == GenericFileExist.Fail) { 668 throw new GenericFileOperationFailedException("File already exist: " + name + ". Cannot write new file."); 669 } 670 } 671 672 InputStream is = null; 673 try { 674 is = ExchangeHelper.getMandatoryInBody(exchange, InputStream.class); 675 if (endpoint.getFileExist() == GenericFileExist.Append) { 676 channel.put(is, targetName, ChannelSftp.APPEND); 677 } else { 678 // override is default 679 channel.put(is, targetName); 680 } 681 682 // after storing file, we may set chmod on the file 683 String mode = endpoint.getConfiguration().getChmod(); 684 if (ObjectHelper.isNotEmpty(mode)) { 685 // parse to int using 8bit mode 686 int permissions = Integer.parseInt(mode, 8); 687 LOG.trace("Setting chmod: {} on file: ", mode, targetName); 688 channel.chmod(permissions, targetName); 689 } 690 691 return true; 692 693 } catch (SftpException e) { 694 throw new GenericFileOperationFailedException("Cannot store file: " + name, e); 695 } catch (InvalidPayloadException e) { 696 throw new GenericFileOperationFailedException("Cannot store file: " + name, e); 697 } finally { 698 IOHelper.close(is, "store: " + name, LOG); 699 } 700 } 701 702 public boolean existsFile(String name) throws GenericFileOperationFailedException { 703 LOG.trace("existsFile({})", name); 704 if (endpoint.isFastExistsCheck()) { 705 return fastExistsFile(name); 706 } 707 // check whether a file already exists 708 String directory = FileUtil.onlyPath(name); 709 if (directory == null) { 710 // assume current dir if no path could be extracted 711 directory = "."; 712 } 713 String onlyName = FileUtil.stripPath(name); 714 715 try { 716 Vector files = channel.ls(directory); 717 // can return either null or an empty list depending on FTP servers 718 if (files == null) { 719 return false; 720 } 721 for (Object file : files) { 722 ChannelSftp.LsEntry entry = (ChannelSftp.LsEntry) file; 723 String existing = entry.getFilename(); 724 LOG.trace("Existing file: {}, target file: {}", existing, name); 725 existing = FileUtil.stripPath(existing); 726 if (existing != null && existing.equals(onlyName)) { 727 return true; 728 } 729 } 730 return false; 731 } catch (SftpException e) { 732 // or an exception can be thrown with id 2 which means file does not exists 733 if (ChannelSftp.SSH_FX_NO_SUCH_FILE == e.id) { 734 return false; 735 } 736 // otherwise its a more serious error so rethrow 737 throw new GenericFileOperationFailedException(e.getMessage(), e); 738 } 739 } 740 741 protected boolean fastExistsFile(String name) throws GenericFileOperationFailedException { 742 LOG.trace("fastExistsFile({})", name); 743 try { 744 Vector files = channel.ls(name); 745 if (files == null) { 746 return false; 747 } 748 return files.size() >= 1; 749 } catch (SftpException e) { 750 // or an exception can be thrown with id 2 which means file does not exists 751 if (ChannelSftp.SSH_FX_NO_SUCH_FILE == e.id) { 752 return false; 753 } 754 // otherwise its a more serious error so rethrow 755 throw new GenericFileOperationFailedException(e.getMessage(), e); 756 } 757 758 } 759 760 public boolean sendNoop() throws GenericFileOperationFailedException { 761 // is not implemented 762 return true; 763 } 764 765 public boolean sendSiteCommand(String command) throws GenericFileOperationFailedException { 766 // is not implemented 767 return true; 768 } 769 }