blob: 932c232f97b6cb5e0e9e3ca2773c2df57fc095c9 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009 xored software, Inc.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* xored software, Inc. - initial API and Implementation (Alex Panchenko)
*******************************************************************************/
package org.eclipse.dltk.ssh.internal.core;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.jsch.core.IJSchService;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.UIKeyboardInteractive;
import com.jcraft.jsch.UserInfo;
public class ChannelPool {
private final long inactivityTimeout;
private final String userName;
private final int port;
private final String hostName;
private String password;
private Session session;
private final List<ChannelSftp> freeChannels = new ArrayList<ChannelSftp>();
private final Map<ChannelSftp, ChannelUsageInfo> usedChannels = new IdentityHashMap<ChannelSftp, ChannelUsageInfo>();
private static class ChannelUsageInfo {
final Object context;
final long timestamp;
public ChannelUsageInfo(Object context) {
this.context = context;
this.timestamp = System.currentTimeMillis();
}
}
/**
* @param userName
* @param hostName
* @param port
*/
public ChannelPool(String userName, String hostName, int port,
long inactivityTimeout) {
this.userName = userName;
this.hostName = hostName;
this.port = port;
this.inactivityTimeout = inactivityTimeout;
}
public void setPassword(String password) {
this.password = password;
}
public String getPassword() {
return password;
}
private final class LocalUserInfo implements UserInfo,
UIKeyboardInteractive {
public void showMessage(String arg0) {
}
public boolean promptYesNo(String arg0) {
return false;
}
public boolean promptPassword(String arg0) {
return true;
}
public boolean promptPassphrase(String arg0) {
return false;
}
public String getPassword() {
return password;
}
public String getPassphrase() {
return ""; //$NON-NLS-1$
}
public String[] promptKeyboardInteractive(String destination,
String name, String instruction, String[] prompt, boolean[] echo) {
final String p = password;
return p != null ? new String[] { p } : null;
}
}
private static boolean DEBUG = false;
protected void connectSession() throws JSchException {
synchronized (lock) {
if (session == null) {
IJSchService service = Activator.getDefault().getJSch();
session = service.createSession(hostName, port, userName);
session.setTimeout(0);
session.setServerAliveInterval(300000);
session.setServerAliveCountMax(6);
session.setPassword(password); // Set password
// directly
UserInfo ui = new LocalUserInfo();
session.setUserInfo(ui);
}
if (!session.isConnected()) {
// Connect with default timeout
if (DEBUG) {
log("session.connect()"); //$NON-NLS-1$
}
session.connect(60 * 1000);
if (DEBUG) {
log("...connected"); //$NON-NLS-1$
}
}
}
}
private final Object channelNotifier = new Object();
private final Object lock = new Object();
protected ChannelSftp acquireChannel(final Object context, long timeout) {
final long start = System.currentTimeMillis();
for (;;) {
try {
return acquireChannel(context);
} catch (JSchException e) {
if (isOutOfChannels(e)) {
if (tryCloseOldChannels()) {
continue;
}
}
if (System.currentTimeMillis() - start > timeout) {
Activator.error("Failed to create direct connection", e); //$NON-NLS-1$
return null;
}
if (DEBUG) {
log(" <sleep>"); //$NON-NLS-1$
}
try {
synchronized (channelNotifier) {
channelNotifier.wait(1000);
}
} catch (InterruptedException e1) {
return null;
}
}
}
}
private boolean isOutOfChannels(JSchException e) {
return CHANNEL_IS_NOT_OPENED.equals(e.getMessage());
}
private static final String CHANNEL_IS_NOT_OPENED = "channel is not opened."; //$NON-NLS-1$
protected ChannelSftp acquireChannel(Object context) throws JSchException {
connectSession();
if (DEBUG) {
log("<acquireChannel> " + context); //$NON-NLS-1$
}
synchronized (lock) {
while (!freeChannels.isEmpty()) {
final ChannelSftp channel = freeChannels.remove(freeChannels
.size() - 1);
if (channel.isConnected()) {
usedChannels.put(channel, createUsageInfo(context));
return channel;
}
}
}
final ChannelSftp channel = (ChannelSftp) session.openChannel("sftp"); //$NON-NLS-1$
if (!channel.isConnected()) {
if (DEBUG) {
log("channel.connect()"); //$NON-NLS-1$
}
channel.connect(10000);
}
synchronized (lock) {
usedChannels.put(channel, createUsageInfo(context));
}
return channel;
// String eToStr = e.toString();
// if (eToStr.indexOf("Auth cancel") >= 0 || eToStr.indexOf("Auth fail") >= 0 || eToStr.indexOf("session is down") >= 0) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
// if (session.isConnected()) {
// session.disconnect();
// session = null;
// }
// }
// if (needLog) {
// Activator.error("Failed to create direct connection", e); //$NON-NLS-1$
// }
// if (session != null) {
// session.disconnect();
// session = null;
// }
}
/**
* @return
*/
private ChannelUsageInfo createUsageInfo(Object context) {
return new ChannelUsageInfo(context);
}
protected void releaseChannel(ChannelSftp channel) {
if (DEBUG) {
log("<releaseChannel>"); //$NON-NLS-1$
}
synchronized (lock) {
if (usedChannels.remove(channel) != null) {
freeChannels.add(channel);
} else {
channel.disconnect();
}
}
synchronized (channelNotifier) {
channelNotifier.notifyAll();
}
}
protected void destroyChannel(ChannelSftp channel) {
if (DEBUG) {
log("<destroyChannel>"); //$NON-NLS-1$
}
synchronized (lock) {
usedChannels.remove(channel);
}
channel.disconnect();
}
private boolean tryCloseOldChannels() {
synchronized (lock) {
if (!usedChannels.isEmpty()) {
ChannelSftp selectedChannel = null;
ChannelUsageInfo selectedUsageInfo = null;
long selectedLastActivity = 0;
for (Map.Entry<ChannelSftp, ChannelUsageInfo> entry : usedChannels
.entrySet()) {
final ChannelUsageInfo usageInfo = entry.getValue();
if (canClose(usageInfo.context)) {
final long lastActivity = getLastActivity(usageInfo.context);
if (lastActivity != Long.MIN_VALUE) {
if (selectedChannel == null
|| lastActivity < selectedLastActivity) {
selectedChannel = entry.getKey();
selectedUsageInfo = usageInfo;
selectedLastActivity = lastActivity;
}
}
}
}
if (selectedChannel != null) {
final long currentTime = System.currentTimeMillis();
if (currentTime - selectedLastActivity > inactivityTimeout) {
Activator
.warn("Close active channel \"" + selectedUsageInfo.context + "\" created " + (currentTime - selectedUsageInfo.timestamp) + "ms ago, lastActivity=" + (currentTime - selectedLastActivity) + "ms ago"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
if (DEBUG) {
log(" channel.disconnect() " + selectedUsageInfo.context); //$NON-NLS-1$
}
selectedChannel.disconnect();
usedChannels.remove(selectedChannel);
return true;
}
}
}
}
return false;
}
/**
* Tests if channel allocated for this context could be closed.
*
* @param context
* @return
*/
protected boolean canClose(Object context) {
return false;
}
/**
* Returns the time of last activity in the channel allocated for this
* context.
*
* @param context
* @return
*/
protected long getLastActivity(Object context) {
return Long.MIN_VALUE;
}
public void disconnect() {
synchronized (lock) {
for (ChannelSftp channel : freeChannels) {
if (DEBUG) {
log("channel.disconnect()"); //$NON-NLS-1$
}
channel.disconnect();
}
freeChannels.clear();
for (Map.Entry<ChannelSftp, ChannelUsageInfo> entry : usedChannels
.entrySet()) {
final ChannelUsageInfo usageInfo = entry.getValue();
Activator
.warn("Close active channel \"" + usageInfo.context + "\" created " + (System.currentTimeMillis() - usageInfo.timestamp) + "ms ago"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
if (DEBUG) {
log(" channel.disconnect() " + usageInfo.context); //$NON-NLS-1$
}
entry.getKey().disconnect();
}
usedChannels.clear();
if (session != null) {
if (DEBUG) {
log("session.disconnect()"); //$NON-NLS-1$
}
session.disconnect();
session = null;
}
}
}
private static final long loadedAt = System.currentTimeMillis();
protected void log(Object message) {
System.out
.println("[" + (System.currentTimeMillis() - loadedAt) + "] " + message); //$NON-NLS-1$ //$NON-NLS-2$
}
public boolean isConnected() {
return session != null && session.isConnected();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((hostName == null) ? 0 : hostName.hashCode());
result = prime * result + port;
result = prime * result
+ ((userName == null) ? 0 : userName.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ChannelPool other = (ChannelPool) obj;
if (hostName == null) {
if (other.hostName != null)
return false;
} else if (!hostName.equals(other.hostName))
return false;
if (port != other.port)
return false;
if (userName == null) {
if (other.userName != null)
return false;
} else if (!userName.equals(other.userName))
return false;
return true;
}
}