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    }