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    }