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