/* * Copyright 2014 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.gradle.internal.resource.transport.sftp; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ListMultimap; import com.google.common.collect.Lists; import com.jcraft.jsch.*; import net.jcip.annotations.ThreadSafe; import org.gradle.api.artifacts.repositories.PasswordCredentials; import org.gradle.api.resources.ResourceException; import org.gradle.internal.concurrent.CompositeStoppable; import org.gradle.internal.concurrent.Stoppable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.URI; import java.util.List; @ThreadSafe public class SftpClientFactory implements Stoppable { private static final Logger LOGGER = LoggerFactory.getLogger(SftpClientFactory.class); private SftpClientCreator sftpClientCreator = new SftpClientCreator(); private final Object lock = new Object(); private final List<LockableSftpClient> allClients = Lists.newArrayList(); private final ListMultimap<SftpHost, LockableSftpClient> idleClients = ArrayListMultimap.create(); public LockableSftpClient createSftpClient(URI uri, PasswordCredentials credentials) { synchronized (lock) { SftpHost sftpHost = new SftpHost(uri, credentials); return acquireClient(sftpHost); } } private LockableSftpClient acquireClient(SftpHost sftpHost) { return idleClients.containsKey(sftpHost) ? reuseExistingOrCreateNewClient(sftpHost) : createNewClient(sftpHost); } private LockableSftpClient reuseExistingOrCreateNewClient(SftpHost sftpHost) { List<LockableSftpClient> clientsByHost = idleClients.get(sftpHost); LockableSftpClient client = null; if (clientsByHost.isEmpty()) { LOGGER.debug("No existing sftp clients. Creating a new one."); client = createNewClient(sftpHost); } else { client = clientsByHost.remove(0); if (!client.isConnected()) { LOGGER.info("Tried to reuse an existing sftp client, but unexpectedly found it disconnected. Discarding and trying again."); discard(client); client = reuseExistingOrCreateNewClient(sftpHost); } else { LOGGER.debug("Reusing an existing sftp client."); } } return client; } private LockableSftpClient createNewClient(SftpHost sftpHost) { LockableSftpClient client = sftpClientCreator.createNewClient(sftpHost); allClients.add(client); return client; } private void discard(LockableSftpClient client) { try { client.stop(); } finally { allClients.remove(client); } } private static class SftpClientCreator { private JSch jsch; public LockableSftpClient createNewClient(SftpHost sftpHost) { try { Session session = createJsch().getSession(sftpHost.getUsername(), sftpHost.getHostname(), sftpHost.getPort()); session.setPassword(sftpHost.getPassword()); session.connect(); Channel channel = session.openChannel("sftp"); channel.connect(); return new DefaultLockableSftpClient(sftpHost, (ChannelSftp) channel, session); } catch (JSchException e) { URI serverUri = URI.create(String.format("sftp://%s:%d", sftpHost.getHostname(), sftpHost.getPort())); if (e.getMessage().equals("Auth fail")) { throw new ResourceException(serverUri, String.format("Password authentication not supported or invalid credentials for SFTP server at %s", serverUri), e); } throw new ResourceException(serverUri, String.format("Could not connect to SFTP server at %s", serverUri), e); } } private JSch createJsch() { if (jsch == null) { JSch.setConfig("PreferredAuthentications", "password"); JSch.setConfig("MaxAuthTries", "1"); jsch = new JSch(); if(LOGGER.isDebugEnabled()) { JSch.setLogger(new com.jcraft.jsch.Logger() { public boolean isEnabled(int level) { return true; } public void log(int level, String message) { LOGGER.debug(message); } }); } jsch.setHostKeyRepository(new HostKeyRepository() { public int check(String host, byte[] key) { return HostKeyRepository.OK; } public void add(HostKey hostkey, UserInfo ui) { } public void remove(String host, String type) { } public void remove(String host, String type, byte[] key) { } public String getKnownHostsRepositoryID() { return "allow-everything"; } public HostKey[] getHostKey() { throw new UnsupportedOperationException(); } public HostKey[] getHostKey(String host, String type) { return new HostKey[0]; } }); } return jsch; } } public void releaseSftpClient(LockableSftpClient sftpClient) { synchronized (lock) { idleClients.put(sftpClient.getHost(), sftpClient); } } public void stop() { synchronized (lock) { try { CompositeStoppable.stoppable(allClients).stop(); } finally { allClients.clear(); idleClients.clear(); } } } private static class DefaultLockableSftpClient implements LockableSftpClient { final SftpHost host; final ChannelSftp channelSftp; final Session session; private DefaultLockableSftpClient(SftpHost host, ChannelSftp channelSftp, Session session) { this.host = host; this.channelSftp = channelSftp; this.session = session; } public void stop() { channelSftp.disconnect(); session.disconnect(); } public SftpHost getHost() { return host; } public ChannelSftp getSftpClient() { return channelSftp; } @Override public boolean isConnected() { return channelSftp.isConnected(); } } }