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