| /******************************************************************************* |
| * 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; |
| } |
| |
| } |