/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.ivy.plugins.repository.sftp;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.apache.ivy.plugins.repository.BasicResource;
import org.apache.ivy.plugins.repository.Resource;
import org.apache.ivy.plugins.repository.TransferEvent;
import org.apache.ivy.plugins.repository.ssh.AbstractSshBasedRepository;
import org.apache.ivy.plugins.repository.ssh.SshCache;
import org.apache.ivy.util.Message;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpATTRS;
import com.jcraft.jsch.SftpException;
import com.jcraft.jsch.SftpProgressMonitor;
import com.jcraft.jsch.ChannelSftp.LsEntry;
/**
* SFTP Repository, allow to use a repository accessed by sftp protocol. It supports all operations:
* get, put and list. It relies on jsch for sftp handling, and thus is compatible with sftp version
* 0, 1, 2 and 3
*/
public class SFTPRepository extends AbstractSshBasedRepository {
// this must be a long to ensure the multiplication done below uses longs
// instead of ints which are not big enough to hold the result
private static final long MILLIS_PER_SECOND = 1000;
private final class MyProgressMonitor implements SftpProgressMonitor {
private long totalLength;
public void init(int op, String src, String dest, long max) {
totalLength = max;
fireTransferStarted(max);
}
public void end() {
fireTransferCompleted(totalLength);
}
public boolean count(long count) {
fireTransferProgress(count);
return true;
}
}
public SFTPRepository() {
}
public Resource getResource(String source) {
return new SFTPResource(this, source);
}
/**
* This method is similar to getResource, except that the returned resource is fully initialized
* (resolved in the sftp repository), and that the given string is a full remote path
*
* @param path
* the full remote path in the repository of the resource
* @return a fully initialized resource, able to answer to all its methods without needing any
* further connection
*/
public Resource resolveResource(String path) {
try {
ChannelSftp c = getSftpChannel(path);
Collection r = c.ls(getPath(path));
if (r != null) {
for (Iterator iter = r.iterator(); iter.hasNext();) {
Object obj = iter.next();
if (obj instanceof LsEntry) {
LsEntry entry = (LsEntry) obj;
SftpATTRS attrs = entry.getAttrs();
return new BasicResource(path, true, attrs.getSize(), attrs.getMTime()
* MILLIS_PER_SECOND, false);
}
}
}
} catch (Exception e) {
Message.debug("reolving resource error: " + e.getMessage());
// silent fail, return unexisting resource
}
return new BasicResource(path, false, 0, 0, false);
}
public InputStream openStream(SFTPResource resource) throws IOException {
ChannelSftp c = getSftpChannel(resource.getName());
try {
String path = getPath(resource.getName());
return c.get(path);
} catch (SftpException e) {
IOException ex = new IOException("impossible to open stream for " + resource + " on "
+ getHost() + (e.getMessage() != null ? ": " + e.getMessage() : ""));
ex.initCause(e);
throw ex;
} catch (URISyntaxException e) {
IOException ex = new IOException("impossible to open stream for " + resource + " on "
+ getHost() + (e.getMessage() != null ? ": " + e.getMessage() : ""));
ex.initCause(e);
throw ex;
}
}
public void get(String source, File destination) throws IOException {
fireTransferInitiated(getResource(source), TransferEvent.REQUEST_GET);
ChannelSftp c = getSftpChannel(source);
try {
String path = getPath(source);
c.get(path, destination.getAbsolutePath(), new MyProgressMonitor());
} catch (SftpException e) {
IOException ex = new IOException("impossible to get " + source + " on " + getHost()
+ (e.getMessage() != null ? ": " + e.getMessage() : ""));
ex.initCause(e);
throw ex;
} catch (URISyntaxException e) {
IOException ex = new IOException("impossible to get " + source + " on " + getHost()
+ (e.getMessage() != null ? ": " + e.getMessage() : ""));
ex.initCause(e);
throw ex;
}
}
public void put(File source, String destination, boolean overwrite) throws IOException {
fireTransferInitiated(getResource(destination), TransferEvent.REQUEST_PUT);
ChannelSftp c = getSftpChannel(destination);
try {
String path = getPath(destination);
if (!overwrite && checkExistence(path, c)) {
throw new IOException("destination file exists and overwrite == false");
}
if (path.indexOf('/') != -1) {
mkdirs(path.substring(0, path.lastIndexOf('/')), c);
}
c.put(source.getAbsolutePath(), path, new MyProgressMonitor());
} catch (SftpException e) {
IOException ex = new IOException(e.getMessage());
ex.initCause(e);
throw ex;
} catch (URISyntaxException e) {
IOException ex = new IOException(e.getMessage());
ex.initCause(e);
throw ex;
}
}
private void mkdirs(String directory, ChannelSftp c) throws IOException, SftpException {
try {
SftpATTRS att = c.stat(directory);
if (att != null) {
if (att.isDir()) {
return;
}
}
} catch (SftpException ex) {
if (directory.indexOf('/') != -1) {
mkdirs(directory.substring(0, directory.lastIndexOf('/')), c);
}
c.mkdir(directory);
}
}
private String getPath(String sftpURI) throws URISyntaxException {
String result = null;
URI uri = new URI(sftpURI);
result = uri.getPath();
if (result == null) {
throw new URISyntaxException(sftpURI, "Missing path in URI.");
}
return result;
}
public List list(String parent) throws IOException {
try {
ChannelSftp c = getSftpChannel(parent);
String path = getPath(parent);
Collection r = c.ls(path);
if (r != null) {
if (!path.endsWith("/")) {
path = parent + "/";
}
List result = new ArrayList();
for (Iterator iter = r.iterator(); iter.hasNext();) {
Object obj = iter.next();
if (obj instanceof LsEntry) {
LsEntry entry = (LsEntry) obj;
if (".".equals(entry.getFilename()) || "..".equals(entry.getFilename())) {
continue;
}
result.add(path + entry.getFilename());
}
}
return result;
}
} catch (SftpException e) {
IOException ex = new IOException("Failed to return a listing for '" + parent + "'");
ex.initCause(e);
throw ex;
} catch (URISyntaxException usex) {
IOException ex = new IOException("Failed to return a listing for '" + parent + "'");
ex.initCause(usex);
throw ex;
}
return null;
}
/**
* Checks the existence for a remote file
*
* @param file
* to check
* @param channel
* to use
* @returns true if file exists, false otherwise
* @throws IOException
* @throws SftpException
*/
private boolean checkExistence(String file, ChannelSftp channel) throws IOException,
SftpException {
try {
return channel.stat(file) != null;
} catch (SftpException ex) {
return false;
}
}
/**
* Establish the connection to the server if not yet connected, and listen to ivy events for
* closing connection when resolve is finished. Not meant to be used in multi threaded
* environment.
*
* @return the ChannelSftp with which a connection is established
* @throws IOException
* if any connection problem occurs
*/
private ChannelSftp getSftpChannel(String pathOrUri) throws IOException {
Session session = getSession(pathOrUri);
String host = session.getHost();
ChannelSftp channel = SshCache.getInstance().getChannelSftp(session);
if (channel == null) {
try {
channel = (ChannelSftp) session.openChannel("sftp");
channel.connect();
Message.verbose(":: SFTP :: connected to " + host + "!");
SshCache.getInstance().attachChannelSftp(session, channel);
} catch (JSchException e) {
IOException ex = new IOException(e.getMessage());
ex.initCause(e);
throw ex;
}
}
return channel;
}
protected String getRepositoryScheme() {
// use the Resolver type name here?
// would be nice if it would be static, so we could use SFTPResolver.getTypeName()
return "sftp";
}
}