/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.ftp.session;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.springframework.integration.file.remote.session.Session;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* Implementation of {@link Session} for FTP.
*
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
* @since 2.0
*/
public class FtpSession implements Session<FTPFile> {
private final Log logger = LogFactory.getLog(this.getClass());
private final FTPClient client;
private final AtomicBoolean readingRaw = new AtomicBoolean();
public FtpSession(FTPClient client) {
Assert.notNull(client, "client must not be null");
this.client = client;
}
@Override
public boolean remove(String path) throws IOException {
Assert.hasText(path, "path must not be null");
if (!this.client.deleteFile(path)) {
throw new IOException("Failed to delete '" + path + "'. Server replied with: " + this.client.getReplyString());
}
else {
return true;
}
}
@Override
public FTPFile[] list(String path) throws IOException {
return this.client.listFiles(path);
}
@Override
public String[] listNames(String path) throws IOException {
return this.client.listNames(path);
}
@Override
public void read(String path, OutputStream fos) throws IOException {
Assert.hasText(path, "path must not be null");
Assert.notNull(fos, "outputStream must not be null");
boolean completed = this.client.retrieveFile(path, fos);
if (!completed) {
throw new IOException("Failed to copy '" + path +
"'. Server replied with: " + this.client.getReplyString());
}
this.logger.info("File has been successfully transferred from: " + path);
}
@Override
public InputStream readRaw(String source) throws IOException {
if (!this.readingRaw.compareAndSet(false, true)) {
throw new IOException("Previous raw read was not finalized");
}
InputStream inputStream = this.client.retrieveFileStream(source);
if (inputStream == null) {
throw new IOException("Failed to obtain InputStream for remote file " + source + ": "
+ this.client.getReplyCode());
}
return inputStream;
}
@Override
public boolean finalizeRaw() throws IOException {
if (!this.readingRaw.compareAndSet(true, false)) {
throw new IOException("Raw read is not in process");
}
if (this.client.completePendingCommand()) {
int replyCode = this.client.getReplyCode();
if (this.logger.isDebugEnabled()) {
this.logger.debug(this + " finalizeRaw - reply code: " + replyCode);
}
return FTPReply.isPositiveCompletion(replyCode);
}
throw new IOException("completePendingCommandFailed");
}
@Override
public void write(InputStream inputStream, String path) throws IOException {
Assert.notNull(inputStream, "inputStream must not be null");
Assert.hasText(path, "path must not be null or empty");
boolean completed = this.client.storeFile(path, inputStream);
if (!completed) {
throw new IOException("Failed to write to '" + path
+ "'. Server replied with: " + this.client.getReplyString());
}
if (this.logger.isInfoEnabled()) {
this.logger.info("File has been successfully transferred to: " + path);
}
}
@Override
public void append(InputStream inputStream, String path) throws IOException {
Assert.notNull(inputStream, "inputStream must not be null");
Assert.hasText(path, "path must not be null or empty");
boolean completed = this.client.appendFile(path, inputStream);
if (!completed) {
throw new IOException("Failed to append to '" + path
+ "'. Server replied with: " + this.client.getReplyString());
}
if (this.logger.isInfoEnabled()) {
this.logger.info("File has been successfully appended to: " + path);
}
}
@Override
public void close() {
try {
if (this.readingRaw.get()) {
if (!finalizeRaw()) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Finalize on readRaw() returned false for " + this);
}
}
}
this.client.disconnect();
}
catch (Exception e) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("failed to disconnect FTPClient", e);
}
}
}
@Override
public boolean isOpen() {
try {
this.client.noop();
}
catch (Exception e) {
return false;
}
return true;
}
@Override
public void rename(String pathFrom, String pathTo) throws IOException {
this.client.deleteFile(pathTo);
boolean completed = this.client.rename(pathFrom, pathTo);
if (!completed) {
throw new IOException("Failed to rename '" + pathFrom +
"' to " + pathTo + "'. Server replied with: " + this.client.getReplyString());
}
if (this.logger.isInfoEnabled()) {
this.logger.info("File has been successfully renamed from: " + pathFrom + " to " + pathTo);
}
}
@Override
public boolean mkdir(String remoteDirectory) throws IOException {
return this.client.makeDirectory(remoteDirectory);
}
@Override
public boolean rmdir(String directory) throws IOException {
return this.client.removeDirectory(directory);
}
@Override
public boolean exists(String path) throws IOException {
Assert.hasText(path, "'path' must not be empty");
String[] names = this.client.listNames(path);
boolean exists = !ObjectUtils.isEmpty(names);
if (!exists) {
String currentWorkingPath = this.client.printWorkingDirectory();
Assert.state(currentWorkingPath != null,
"working directory cannot be determined; exists check can not be completed");
try {
exists = this.client.changeWorkingDirectory(path);
}
finally {
this.client.changeWorkingDirectory(currentWorkingPath);
}
}
return exists;
}
@Override
public FTPClient getClientInstance() {
return this.client;
}
}