PageRenderTime 46ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/projects/netbeans-7.3/dlight.nativeexecution/src/org/netbeans/modules/nativeexecution/jsch/JSchChannelsSupport.java

https://gitlab.com/essere.lab.public/qualitas.class-corpus
Java | 340 lines | 242 code | 44 blank | 54 comment | 52 complexity | df5efafa2a2f1e71b28b3d97a3a805db MD5 | raw file
  1. /*
  2. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  3. *
  4. * Copyright 2010 Sun Microsystems, Inc. All rights reserved.
  5. *
  6. * The contents of this file are subject to the terms of either the GNU
  7. * General Public License Version 2 only ("GPL") or the Common
  8. * Development and Distribution License("CDDL") (collectively, the
  9. * "License"). You may not use this file except in compliance with the
  10. * License. You can obtain a copy of the License at
  11. * http://www.netbeans.org/cddl-gplv2.html
  12. * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
  13. * specific language governing permissions and limitations under the
  14. * License. When distributing the software, include this License Header
  15. * Notice in each file and include the License file at
  16. * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
  17. * particular file as subject to the "Classpath" exception as provided
  18. * by Sun in the GPL Version 2 section of the License file that
  19. * accompanied this code. If applicable, add the following below the
  20. * License Header, with the fields enclosed by brackets [] replaced by
  21. * your own identifying information:
  22. * "Portions Copyrighted [year] [name of copyright owner]"
  23. *
  24. * If you wish your version of this file to be governed by only the CDDL
  25. * or only the GPL Version 2, indicate your decision by adding
  26. * "[Contributor] elects to include this software in this distribution
  27. * under the [CDDL or GPL Version 2] license." If you do not indicate a
  28. * single choice of license, a recipient has the option to distribute
  29. * your version of this file under either the CDDL, the GPL Version 2 or
  30. * to extend the choice of license to its licensees as provided above.
  31. * However, if you add GPL Version 2 code and therefore, elected the GPL
  32. * Version 2 license, then the option applies only if the new code is
  33. * made subject to such option by the copyright holder.
  34. *
  35. * Contributor(s):
  36. *
  37. * Portions Copyrighted 2010 Sun Microsystems, Inc.
  38. */
  39. package org.netbeans.modules.nativeexecution.jsch;
  40. import com.jcraft.jsch.Channel;
  41. import com.jcraft.jsch.ChannelShell;
  42. import com.jcraft.jsch.JSch;
  43. import com.jcraft.jsch.JSchException;
  44. import com.jcraft.jsch.Session;
  45. import java.io.IOException;
  46. import java.util.ArrayList;
  47. import java.util.HashMap;
  48. import java.util.HashSet;
  49. import java.util.List;
  50. import java.util.Map.Entry;
  51. import java.util.Set;
  52. import java.util.concurrent.CancellationException;
  53. import java.util.concurrent.ConcurrentHashMap;
  54. import java.util.concurrent.atomic.AtomicBoolean;
  55. import java.util.concurrent.atomic.AtomicInteger;
  56. import java.util.concurrent.locks.Condition;
  57. import java.util.concurrent.locks.ReentrantLock;
  58. import java.util.logging.Level;
  59. import org.netbeans.modules.nativeexecution.api.ExecutionEnvironment;
  60. import org.netbeans.modules.nativeexecution.api.util.PasswordManager;
  61. import org.netbeans.modules.nativeexecution.api.util.RemoteStatistics;
  62. import org.netbeans.modules.nativeexecution.support.Logger;
  63. import org.netbeans.modules.nativeexecution.support.RemoteUserInfo;
  64. import org.openide.util.Cancellable;
  65. /**
  66. *
  67. * @author ak119685
  68. */
  69. public final class JSchChannelsSupport {
  70. private static final java.util.logging.Logger log = Logger.getInstance();
  71. private static final int JSCH_CONNECTION_RETRY = Integer.getInteger("jsch.connection.retry", 3); // NOI18N
  72. private static final int JSCH_CONNECTION_TIMEOUT = Integer.getInteger("jsch.connection.timeout", 10000); // NOI18N
  73. private static final int JSCH_SESSIONS_PER_ENV = Integer.getInteger("jsch.sessions.per.env", 10); // NOI18N
  74. private static final int JSCH_CHANNELS_PER_SESSION = Integer.getInteger("jsch.channels.per.session", 10); // NOI18N
  75. private static final boolean UNIT_TEST_MODE = Boolean.getBoolean("nativeexecution.mode.unittest"); // NOI18N
  76. private static final boolean USE_JZLIB = Boolean.getBoolean("jzlib"); // NOI18N
  77. private static final HashMap<String, String> jschSessionConfig = new HashMap<String, String>();
  78. private final JSch jsch;
  79. private final RemoteUserInfo userInfo;
  80. private final ExecutionEnvironment env;
  81. private final ReentrantLock sessionsLock = new ReentrantLock();
  82. private final Condition sessionAvailable = sessionsLock.newCondition();
  83. // AtomicInteger stores a number of available channels for the session
  84. // We use ConcurrentHashMap to be able fast isConnected() check; in most other cases sessions is guarded bu "this"
  85. private final ConcurrentHashMap<Session, AtomicInteger> sessions = new ConcurrentHashMap<Session, AtomicInteger>();
  86. private final Set<Channel> knownChannels = new HashSet<Channel>();
  87. static {
  88. Set<Entry<Object, Object>> data = new HashSet<Entry<Object, Object>>(System.getProperties().entrySet());
  89. for (Entry<Object, Object> prop : data) {
  90. String var = prop.getKey().toString();
  91. String val = prop.getValue().toString();
  92. if (var != null && val != null) {
  93. if (var.startsWith("jsch.session.cfg.")) { // NOI18N
  94. jschSessionConfig.put(var.substring(17), val);
  95. }
  96. if (var.startsWith("jsch.cfg.")) { // NOI18N
  97. JSch.setConfig(var.substring(9), val);
  98. jschSessionConfig.put(var.substring(9), val);
  99. }
  100. }
  101. }
  102. }
  103. public JSchChannelsSupport(JSch jsch, ExecutionEnvironment env) {
  104. this.jsch = jsch;
  105. this.env = env;
  106. this.userInfo = new RemoteUserInfo(env, !UNIT_TEST_MODE);
  107. }
  108. public ChannelShell getShellChannel(boolean waitIfNoAvailable) throws JSchException, IOException, InterruptedException {
  109. return (ChannelShell) acquireChannel("shell", waitIfNoAvailable); // NOI18N
  110. }
  111. public synchronized Channel acquireChannel(String type, boolean waitIfNoAvailable) throws JSchException, IOException, InterruptedException {
  112. JSchException exception = null;
  113. for (int i = 0; i < JSCH_CONNECTION_RETRY; i++) {
  114. Session session = findFreeSession();
  115. if (session == null) {
  116. if (sessions.size() >= JSCH_SESSIONS_PER_ENV) {
  117. if (waitIfNoAvailable) {
  118. try {
  119. sessionsLock.lock();
  120. while (session == null) {
  121. sessionAvailable.await();
  122. session = findFreeSession();
  123. }
  124. } finally {
  125. sessionsLock.unlock();
  126. }
  127. } else {
  128. throw new IOException("All " + JSCH_SESSIONS_PER_ENV + " sessions for " + env.getDisplayName() + " are fully loaded"); // NOI18N
  129. }
  130. }
  131. }
  132. try {
  133. if (session == null) {
  134. session = startNewSession(true);
  135. }
  136. Channel result = session.openChannel(type);
  137. if (result != null) {
  138. log.log(Level.FINE, "Acquired channel [{0}] from session [{1}].", new Object[]{System.identityHashCode(result), System.identityHashCode(session)}); // NOI18N
  139. knownChannels.add(result);
  140. return result;
  141. }
  142. } catch (JSchException ex) {
  143. exception = ex;
  144. }
  145. if (session != null && !session.isConnected()) {
  146. sessions.remove(session);
  147. }
  148. }
  149. // It is either JSCH_CONNECTION_RETRY times we got JSchException =>
  150. // exception is set; or there was another exception => it was thrown
  151. // already
  152. assert exception != null;
  153. throw exception;
  154. }
  155. public boolean isConnected() {
  156. // ConcurrentHashMap.keySet() never throws ConcurrentModificationException
  157. for (Session s : sessions.keySet()) {
  158. if (s.isConnected()) {
  159. return true;
  160. }
  161. }
  162. return false;
  163. }
  164. public synchronized void reconnect(ExecutionEnvironment env) throws IOException, JSchException, InterruptedException {
  165. disconnect();
  166. connect();
  167. }
  168. private Session findFreeSession() {
  169. for (Entry<Session, AtomicInteger> entry : sessions.entrySet()) {
  170. Session s = entry.getKey();
  171. AtomicInteger availableChannels = entry.getValue();
  172. if (s.isConnected() && availableChannels.get() > 0) {
  173. log.log(Level.FINE, "availableChannels == {0}", new Object[]{availableChannels.get()}); // NOI18N
  174. int remains = availableChannels.decrementAndGet();
  175. log.log(Level.FINE, "Reuse session [{0}]. {1} channels remains...", new Object[]{System.identityHashCode(s), remains}); // NOI18N
  176. return s;
  177. }
  178. }
  179. return null;
  180. }
  181. public synchronized void connect() throws JSchException, InterruptedException {
  182. if (isConnected()) {
  183. return;
  184. }
  185. startNewSession(false);
  186. }
  187. public synchronized void disconnect() {
  188. for (Session s : sessions.keySet()) {
  189. s.disconnect();
  190. }
  191. }
  192. private Session startNewSession(boolean acquireChannel) throws JSchException, InterruptedException {
  193. Session newSession = null;
  194. final AtomicBoolean cancelled = new AtomicBoolean(false);
  195. ConnectingProgressHandle.startHandle(env, new Cancellable() {
  196. @Override
  197. public boolean cancel() {
  198. cancelled.set(true);
  199. return true;
  200. }
  201. });
  202. try {
  203. while (!cancelled.get()) {
  204. try {
  205. newSession = jsch.getSession(env.getUser(), env.getHostAddress(), env.getSSHPort());
  206. newSession.setUserInfo(userInfo);
  207. for (Entry<String, String> entry : jschSessionConfig.entrySet()) {
  208. newSession.setConfig(entry.getKey(), entry.getValue());
  209. }
  210. if (USE_JZLIB) {
  211. newSession.setConfig("compression.s2c", "zlib@openssh.com,zlib,none"); // NOI18N
  212. newSession.setConfig("compression.c2s", "zlib@openssh.com,zlib,none"); // NOI18N
  213. newSession.setConfig("compression_level", "9"); // NOI18N
  214. }
  215. if (RemoteStatistics.COLLECT_STATISTICS) {
  216. newSession.setSocketFactory(MeasurableSocketFactory.getInstance());
  217. }
  218. newSession.connect(JSCH_CONNECTION_TIMEOUT);
  219. break;
  220. } catch (JSchException ex) {
  221. if (!UNIT_TEST_MODE && "Auth fail".equals(ex.getMessage())) { // NOI18N
  222. PasswordManager.getInstance().clearPassword(env);
  223. } else {
  224. throw ex;
  225. }
  226. } catch (CancellationException cex) {
  227. cancelled.set(true);
  228. }
  229. }
  230. if (cancelled.get()) {
  231. throw new InterruptedException("StartNewSession was cancelled ..."); // NOI18N
  232. }
  233. sessions.put(newSession, new AtomicInteger(JSCH_CHANNELS_PER_SESSION - (acquireChannel ? 1 : 0)));
  234. log.log(Level.FINE, "New session [{0}] started.", new Object[]{System.identityHashCode(newSession)}); // NOI18N
  235. } finally {
  236. ConnectingProgressHandle.stopHandle(env);
  237. }
  238. return newSession;
  239. }
  240. public synchronized void releaseChannel(final Channel channel) throws JSchException {
  241. if (!knownChannels.remove(channel)) {
  242. // Means it was not in the collection
  243. return;
  244. }
  245. Session s = channel.getSession();
  246. log.log(Level.FINE, "Releasing channel [{0}] for session [{1}].", new Object[]{System.identityHashCode(channel), System.identityHashCode(s)}); // NOI18N
  247. channel.disconnect();
  248. int count = sessions.get(s).incrementAndGet();
  249. List<Session> sessionsToRemove = new ArrayList<Session>();
  250. if (count == JSCH_CHANNELS_PER_SESSION) {
  251. // No more channels in this session ...
  252. // Do we have other ready-to-serve sessions?
  253. // In this case will close this one.
  254. for (Entry<Session, AtomicInteger> entry : sessions.entrySet()) {
  255. if (entry.getKey() == s) {
  256. continue;
  257. }
  258. if (entry.getValue().get() > 0) {
  259. log.log(Level.FINE, "Found another session [{0}] with {1} free slots. Will remove this one [{2}].", // NOI18N
  260. new Object[]{
  261. System.identityHashCode(entry.getKey()),
  262. entry.getValue().get(),
  263. System.identityHashCode(s)});
  264. sessionsToRemove.add(s);
  265. break;
  266. }
  267. }
  268. } else {
  269. // This sessions is capable to provide a channel on next request
  270. // Perhaps we have empty sessions that can be closed then?
  271. for (Entry<Session, AtomicInteger> entry : sessions.entrySet()) {
  272. if (entry.getKey() == s) {
  273. continue;
  274. }
  275. if (entry.getValue().get() == JSCH_CHANNELS_PER_SESSION) {
  276. log.log(Level.FINE, "Found empty session [{0}] while this one is also has free slots [{1}].", // NOI18N
  277. new Object[]{
  278. System.identityHashCode(entry.getKey()),
  279. System.identityHashCode(s)});
  280. sessionsToRemove.add(entry.getKey());
  281. }
  282. }
  283. }
  284. for (Session sr : sessionsToRemove) {
  285. log.log(Level.FINE, "Closing session [{0}].", new Object[]{System.identityHashCode(s)}); // NOI18N
  286. sr.disconnect();
  287. sessions.remove(sr);
  288. }
  289. try {
  290. sessionsLock.lock();
  291. sessionAvailable.signalAll();
  292. } finally {
  293. sessionsLock.unlock();
  294. }
  295. }
  296. }