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