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.apache.commons.logging.Log; 047 import org.apache.commons.logging.LogFactory; 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 Log LOG = LogFactory.getLog(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 return LOG.isFatalEnabled(); 200 case ERROR: 201 return LOG.isErrorEnabled(); 202 case WARN: 203 return LOG.isWarnEnabled(); 204 case INFO: 205 return LOG.isInfoEnabled(); 206 default: 207 return LOG.isDebugEnabled(); 208 } 209 } 210 211 public void log(int level, String message) { 212 switch (level) { 213 case FATAL: 214 LOG.fatal("JSCH -> " + message); 215 break; 216 case ERROR: 217 LOG.error("JSCH -> " + message); 218 break; 219 case WARN: 220 LOG.warn("JSCH -> " + message); 221 break; 222 case INFO: 223 LOG.info("JSCH -> " + message); 224 break; 225 default: 226 LOG.debug("JSCH -> " + message); 227 break; 228 } 229 } 230 } 231 232 public boolean isConnected() throws GenericFileOperationFailedException { 233 return session != null && session.isConnected() && channel != null && channel.isConnected(); 234 } 235 236 public void disconnect() throws GenericFileOperationFailedException { 237 if (session != null && session.isConnected()) { 238 session.disconnect(); 239 } 240 if (channel != null && channel.isConnected()) { 241 channel.disconnect(); 242 } 243 } 244 245 public boolean deleteFile(String name) throws GenericFileOperationFailedException { 246 if (LOG.isDebugEnabled()) { 247 LOG.debug("Deleting file: " + name); 248 } 249 try { 250 channel.rm(name); 251 return true; 252 } catch (SftpException e) { 253 throw new GenericFileOperationFailedException("Cannot delete file: " + name, e); 254 } 255 } 256 257 public boolean renameFile(String from, String to) throws GenericFileOperationFailedException { 258 if (LOG.isDebugEnabled()) { 259 LOG.debug("Renaming file: " + from + " to: " + to); 260 } 261 try { 262 channel.rename(from, to); 263 return true; 264 } catch (SftpException e) { 265 throw new GenericFileOperationFailedException("Cannot rename file from: " + from + " to: " + to, e); 266 } 267 } 268 269 public boolean buildDirectory(String directory, boolean absolute) throws GenericFileOperationFailedException { 270 // must normalize directory first 271 directory = endpoint.getConfiguration().normalizePath(directory); 272 273 if (LOG.isTraceEnabled()) { 274 LOG.trace("buildDirectory(" + directory + "," + absolute + ")"); 275 } 276 // ignore absolute as all dirs are relative with FTP 277 boolean success = false; 278 279 String originalDirectory = getCurrentDirectory(); 280 try { 281 // maybe the full directory already exists 282 try { 283 channel.cd(directory); 284 success = true; 285 } catch (SftpException e) { 286 // ignore, we could not change directory so try to create it instead 287 } 288 289 if (!success) { 290 if (LOG.isDebugEnabled()) { 291 LOG.debug("Trying to build remote directory: " + directory); 292 } 293 294 try { 295 channel.mkdir(directory); 296 success = true; 297 } catch (SftpException e) { 298 // we are here if the server side doesn't create intermediate folders 299 // so create the folder one by one 300 success = buildDirectoryChunks(directory); 301 } 302 } 303 } catch (IOException e) { 304 throw new GenericFileOperationFailedException("Cannot build directory: " + directory, e); 305 } catch (SftpException e) { 306 throw new GenericFileOperationFailedException("Cannot build directory: " + directory, e); 307 } finally { 308 // change back to original directory 309 if (originalDirectory != null) { 310 changeCurrentDirectory(originalDirectory); 311 } 312 } 313 314 return success; 315 } 316 317 private boolean buildDirectoryChunks(String dirName) throws IOException, SftpException { 318 final StringBuilder sb = new StringBuilder(dirName.length()); 319 final String[] dirs = dirName.split("/|\\\\"); 320 321 boolean success = false; 322 for (String dir : dirs) { 323 sb.append(dir).append('/'); 324 // must normalize the directory name 325 String directory = endpoint.getConfiguration().normalizePath(sb.toString()); 326 327 // do not try to build root folder (/ or \) 328 if (!(directory.equals("/") || directory.equals("\\"))) { 329 try { 330 if (LOG.isTraceEnabled()) { 331 LOG.trace("Trying to build remote directory by chunk: " + directory); 332 } 333 334 channel.mkdir(directory); 335 success = true; 336 } catch (SftpException e) { 337 // ignore keep trying to create the rest of the path 338 } 339 } 340 } 341 342 return success; 343 } 344 345 public String getCurrentDirectory() throws GenericFileOperationFailedException { 346 if (LOG.isTraceEnabled()) { 347 LOG.trace("getCurrentDirectory()"); 348 } 349 try { 350 return channel.pwd(); 351 } catch (SftpException e) { 352 throw new GenericFileOperationFailedException("Cannot get current directory", e); 353 } 354 } 355 356 public void changeCurrentDirectory(String path) throws GenericFileOperationFailedException { 357 if (LOG.isTraceEnabled()) { 358 LOG.trace("changeCurrentDirectory(" + path + ")"); 359 } 360 if (ObjectHelper.isEmpty(path)) { 361 return; 362 } 363 364 // must compact path so FTP server can traverse correctly 365 path = FileUtil.compactPath(path); 366 367 // not stepwise should change directory in one operation 368 if (!endpoint.getConfiguration().isStepwise()) { 369 doChangeDirectory(path); 370 return; 371 } 372 373 // if it starts with the root path then a little special handling for that 374 if (FileUtil.hasLeadingSeparator(path)) { 375 // change to root path 376 doChangeDirectory(path.substring(0, 1)); 377 path = path.substring(1); 378 } 379 380 // split into multiple dirs 381 final String[] dirs = path.split("/|\\\\"); 382 383 if (dirs == null || dirs.length == 0) { 384 // path was just a relative single path 385 doChangeDirectory(path); 386 return; 387 } 388 389 // there are multiple dirs so do this in chunks 390 for (String dir : dirs) { 391 doChangeDirectory(dir); 392 } 393 } 394 395 private void doChangeDirectory(String path) { 396 if (path == null || ".".equals(path) || ObjectHelper.isEmpty(path)) { 397 return; 398 } 399 400 if (LOG.isTraceEnabled()) { 401 LOG.trace("Changing directory: " + path); 402 } 403 try { 404 channel.cd(path); 405 } catch (SftpException e) { 406 throw new GenericFileOperationFailedException("Cannot change directory to: " + path, e); 407 } 408 } 409 410 public void changeToParentDirectory() throws GenericFileOperationFailedException { 411 if (LOG.isTraceEnabled()) { 412 LOG.trace("changeToParentDirectory()"); 413 } 414 String current = getCurrentDirectory(); 415 416 String parent = FileUtil.compactPath(current + "/.."); 417 // must start with absolute 418 if (!parent.startsWith("/")) { 419 parent = "/" + parent; 420 } 421 422 changeCurrentDirectory(parent); 423 } 424 425 public List<ChannelSftp.LsEntry> listFiles() throws GenericFileOperationFailedException { 426 return listFiles("."); 427 } 428 429 public List<ChannelSftp.LsEntry> listFiles(String path) throws GenericFileOperationFailedException { 430 if (LOG.isTraceEnabled()) { 431 LOG.trace("listFiles(" + path + ")"); 432 } 433 if (ObjectHelper.isEmpty(path)) { 434 // list current directory if file path is not given 435 path = "."; 436 } 437 438 try { 439 final List<ChannelSftp.LsEntry> list = new ArrayList<ChannelSftp.LsEntry>(); 440 Vector files = channel.ls(path); 441 // can return either null or an empty list depending on FTP servers 442 if (files != null) { 443 for (Object file : files) { 444 list.add((ChannelSftp.LsEntry) file); 445 } 446 } 447 return list; 448 } catch (SftpException e) { 449 throw new GenericFileOperationFailedException("Cannot list directory: " + path, e); 450 } 451 } 452 453 public boolean retrieveFile(String name, Exchange exchange) throws GenericFileOperationFailedException { 454 if (LOG.isTraceEnabled()) { 455 LOG.trace("retrieveFile(" + name + ")"); 456 } 457 if (ObjectHelper.isNotEmpty(endpoint.getLocalWorkDirectory())) { 458 // local work directory is configured so we should store file content as files in this local directory 459 return retrieveFileToFileInLocalWorkDirectory(name, exchange); 460 } else { 461 // store file content directory as stream on the body 462 return retrieveFileToStreamInBody(name, exchange); 463 } 464 } 465 466 @SuppressWarnings("unchecked") 467 private boolean retrieveFileToStreamInBody(String name, Exchange exchange) throws GenericFileOperationFailedException { 468 OutputStream os = null; 469 try { 470 os = new ByteArrayOutputStream(); 471 GenericFile<ChannelSftp.LsEntry> target = 472 (GenericFile<ChannelSftp.LsEntry>) exchange.getProperty(FileComponent.FILE_EXCHANGE_FILE); 473 ObjectHelper.notNull(target, "Exchange should have the " + FileComponent.FILE_EXCHANGE_FILE + " set"); 474 target.setBody(os); 475 476 String remoteName = name; 477 String currentDir = null; 478 if (endpoint.getConfiguration().isStepwise()) { 479 // remember current directory 480 currentDir = getCurrentDirectory(); 481 482 // change directory to path where the file is to be retrieved 483 // (must do this as some FTP servers cannot retrieve using absolute path) 484 String path = FileUtil.onlyPath(name); 485 if (path != null) { 486 changeCurrentDirectory(path); 487 } 488 // remote name is now only the file name as we just changed directory 489 remoteName = FileUtil.stripPath(name); 490 } 491 492 // use input stream which works with Apache SSHD used for testing 493 InputStream is = channel.get(remoteName); 494 IOHelper.copyAndCloseInput(is, os); 495 496 // change back to current directory 497 if (endpoint.getConfiguration().isStepwise()) { 498 changeCurrentDirectory(currentDir); 499 } 500 501 return true; 502 } catch (IOException e) { 503 throw new GenericFileOperationFailedException("Cannot retrieve file: " + name, e); 504 } catch (SftpException e) { 505 throw new GenericFileOperationFailedException("Cannot retrieve file: " + name, e); 506 } finally { 507 IOHelper.close(os, "retrieve: " + name, LOG); 508 } 509 } 510 511 @SuppressWarnings("unchecked") 512 private boolean retrieveFileToFileInLocalWorkDirectory(String name, Exchange exchange) throws GenericFileOperationFailedException { 513 File temp; 514 File local = new File(endpoint.getLocalWorkDirectory()); 515 OutputStream os; 516 GenericFile<ChannelSftp.LsEntry> file = 517 (GenericFile<ChannelSftp.LsEntry>) exchange.getProperty(FileComponent.FILE_EXCHANGE_FILE); 518 ObjectHelper.notNull(file, "Exchange should have the " + FileComponent.FILE_EXCHANGE_FILE + " set"); 519 try { 520 // use relative filename in local work directory 521 String relativeName = file.getRelativeFilePath(); 522 523 temp = new File(local, relativeName + ".inprogress"); 524 local = new File(local, relativeName); 525 526 // create directory to local work file 527 local.mkdirs(); 528 529 // delete any existing files 530 if (temp.exists()) { 531 if (!FileUtil.deleteFile(temp)) { 532 throw new GenericFileOperationFailedException("Cannot delete existing local work file: " + temp); 533 } 534 } 535 if (local.exists()) { 536 if (!FileUtil.deleteFile(local)) { 537 throw new GenericFileOperationFailedException("Cannot delete existing local work file: " + local); 538 } 539 } 540 541 // create new temp local work file 542 if (!temp.createNewFile()) { 543 throw new GenericFileOperationFailedException("Cannot create new local work file: " + temp); 544 } 545 546 // store content as a file in the local work directory in the temp handle 547 os = new FileOutputStream(temp); 548 549 // set header with the path to the local work file 550 exchange.getIn().setHeader(Exchange.FILE_LOCAL_WORK_PATH, local.getPath()); 551 } catch (Exception e) { 552 throw new GenericFileOperationFailedException("Cannot create new local work file: " + local); 553 } 554 555 try { 556 // store the java.io.File handle as the body 557 file.setBody(local); 558 559 String remoteName = name; 560 String currentDir = null; 561 if (endpoint.getConfiguration().isStepwise()) { 562 // remember current directory 563 currentDir = getCurrentDirectory(); 564 565 // change directory to path where the file is to be retrieved 566 // (must do this as some FTP servers cannot retrieve using absolute path) 567 String path = FileUtil.onlyPath(name); 568 if (path != null) { 569 changeCurrentDirectory(path); 570 } 571 // remote name is now only the file name as we just changed directory 572 remoteName = FileUtil.stripPath(name); 573 } 574 575 channel.get(remoteName, os); 576 577 // change back to current directory 578 if (endpoint.getConfiguration().isStepwise()) { 579 changeCurrentDirectory(currentDir); 580 } 581 582 } catch (SftpException e) { 583 if (LOG.isTraceEnabled()) { 584 LOG.trace("Error occurred during retrieving file: " + name + " to local directory. Deleting local work file: " + temp); 585 } 586 // failed to retrieve the file so we need to close streams and delete in progress file 587 // must close stream before deleting file 588 IOHelper.close(os, "retrieve: " + name, LOG); 589 boolean deleted = FileUtil.deleteFile(temp); 590 if (!deleted) { 591 LOG.warn("Error occurred during retrieving file: " + name + " to local directory. Cannot delete local work file: " + temp); 592 } 593 throw new GenericFileOperationFailedException("Cannot retrieve file: " + name, e); 594 } finally { 595 IOHelper.close(os, "retrieve: " + name, LOG); 596 } 597 598 if (LOG.isDebugEnabled()) { 599 LOG.debug("Retrieve file to local work file result: true"); 600 } 601 602 // operation went okay so rename temp to local after we have retrieved the data 603 if (LOG.isTraceEnabled()) { 604 LOG.trace("Renaming local in progress file from: " + temp + " to: " + local); 605 } 606 if (!FileUtil.renameFile(temp, local)) { 607 throw new GenericFileOperationFailedException("Cannot rename local work file from: " + temp + " to: " + local); 608 } 609 610 return true; 611 } 612 613 public boolean storeFile(String name, Exchange exchange) throws GenericFileOperationFailedException { 614 // must normalize name first 615 name = endpoint.getConfiguration().normalizePath(name); 616 617 if (LOG.isTraceEnabled()) { 618 LOG.trace("storeFile(" + name + ")"); 619 } 620 621 boolean answer = false; 622 String currentDir = null; 623 String path = FileUtil.onlyPath(name); 624 String targetName = name; 625 626 try { 627 if (path != null && endpoint.getConfiguration().isStepwise()) { 628 // must remember current dir so we stay in that directory after the write 629 currentDir = getCurrentDirectory(); 630 631 // change to path of name 632 changeCurrentDirectory(path); 633 634 // the target name should be without path, as we have changed directory 635 targetName = FileUtil.stripPath(name); 636 } 637 638 // store the file 639 answer = doStoreFile(name, targetName, exchange); 640 } finally { 641 // change back to current directory if we changed directory 642 if (currentDir != null) { 643 changeCurrentDirectory(currentDir); 644 } 645 } 646 647 return answer; 648 } 649 650 private boolean doStoreFile(String name, String targetName, Exchange exchange) throws GenericFileOperationFailedException { 651 if (LOG.isTraceEnabled()) { 652 LOG.trace("doStoreFile(" + targetName + ")"); 653 } 654 655 // if an existing file already exists what should we do? 656 if (endpoint.getFileExist() == GenericFileExist.Ignore || endpoint.getFileExist() == GenericFileExist.Fail) { 657 boolean existFile = existsFile(targetName); 658 if (existFile && endpoint.getFileExist() == GenericFileExist.Ignore) { 659 // ignore but indicate that the file was written 660 if (LOG.isTraceEnabled()) { 661 LOG.trace("An existing file already exists: " + name + ". Ignore and do not override it."); 662 } 663 return true; 664 } else if (existFile && endpoint.getFileExist() == GenericFileExist.Fail) { 665 throw new GenericFileOperationFailedException("File already exist: " + name + ". Cannot write new file."); 666 } 667 } 668 669 InputStream is = null; 670 try { 671 is = ExchangeHelper.getMandatoryInBody(exchange, InputStream.class); 672 if (endpoint.getFileExist() == GenericFileExist.Append) { 673 channel.put(is, targetName, ChannelSftp.APPEND); 674 } else { 675 // override is default 676 channel.put(is, targetName); 677 } 678 return true; 679 } catch (SftpException e) { 680 throw new GenericFileOperationFailedException("Cannot store file: " + name, e); 681 } catch (InvalidPayloadException e) { 682 throw new GenericFileOperationFailedException("Cannot store file: " + name, e); 683 } finally { 684 IOHelper.close(is, "store: " + name, LOG); 685 } 686 } 687 688 public boolean existsFile(String name) throws GenericFileOperationFailedException { 689 if (LOG.isTraceEnabled()) { 690 LOG.trace("existsFile(" + name + ")"); 691 } 692 693 // check whether a file already exists 694 String directory = FileUtil.onlyPath(name); 695 if (directory == null) { 696 // assume current dir if no path could be extracted 697 directory = ""; 698 } 699 String onlyName = FileUtil.stripPath(name); 700 701 try { 702 Vector files = channel.ls(directory); 703 // can return either null or an empty list depending on FTP servers 704 if (files == null) { 705 return false; 706 } 707 for (Object file : files) { 708 ChannelSftp.LsEntry entry = (ChannelSftp.LsEntry) file; 709 if (entry.getFilename().equals(onlyName)) { 710 return true; 711 } 712 } 713 return false; 714 } catch (SftpException e) { 715 // or an exception can be thrown with id 2 which means file does not exists 716 if (ChannelSftp.SSH_FX_NO_SUCH_FILE == e.id) { 717 return false; 718 } 719 // otherwise its a more serious error so rethrow 720 throw new GenericFileOperationFailedException(e.getMessage(), e); 721 } 722 } 723 724 public boolean sendNoop() throws GenericFileOperationFailedException { 725 // is not implemented 726 return true; 727 } 728 729 public boolean sendSiteCommand(String command) throws GenericFileOperationFailedException { 730 // is not implemented 731 return true; 732 } 733 }