Extract ssh related code into SshHelper class

Move ssh related code out from ReplicationQueue into separate class
SshHelper. Later on the same code could be used to interact with other
git server that does not support shell access, but allow to execute
build in commands (like eg. Gerrit does).

Change-Id: I07c142a58571f71bf8765d956c6050b36d2e44fc
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java
index bff4651..30f54ac 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationQueue.java
@@ -25,7 +25,6 @@
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.inject.Inject;
-import com.google.inject.Provider;
 import com.googlesource.gerrit.plugins.replication.PushResultProcessing.GitUpdateProcessing;
 import com.googlesource.gerrit.plugins.replication.ReplicationConfig.FilterType;
 import java.io.File;
@@ -35,17 +34,12 @@
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
-import org.eclipse.jgit.errors.TransportException;
 import org.eclipse.jgit.internal.storage.file.FileRepository;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.transport.RemoteSession;
-import org.eclipse.jgit.transport.SshSessionFactory;
 import org.eclipse.jgit.transport.URIish;
-import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.QuotedString;
-import org.eclipse.jgit.util.io.StreamCopyThread;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -58,7 +52,6 @@
         HeadUpdatedListener {
   static final String REPLICATION_LOG_NAME = "replication_log";
   static final Logger repLog = LoggerFactory.getLogger(REPLICATION_LOG_NAME);
-  private static final int SSH_REMOTE_TIMEOUT = 120 * 1000;
 
   private final ReplicationStateListener stateLog;
 
@@ -75,23 +68,23 @@
   }
 
   private final WorkQueue workQueue;
+  private final SshHelper sshHelper;
   private final DynamicItem<EventDispatcher> dispatcher;
   private final ReplicationConfig config;
-  private final Provider<SshSessionFactory> sshSessionFactoryProvider;
   private volatile boolean running;
 
   @Inject
   ReplicationQueue(
       WorkQueue wq,
+      SshHelper sh,
       ReplicationConfig rc,
       DynamicItem<EventDispatcher> dis,
-      ReplicationStateListener sl,
-      Provider<SshSessionFactory> sshSessionFactoryProvider) {
+      ReplicationStateListener sl) {
     workQueue = wq;
+    sshHelper = sh;
     dispatcher = dis;
     config = rc;
     stateLog = sl;
-    this.sshSessionFactoryProvider = sshSessionFactoryProvider;
   }
 
   @Override
@@ -273,9 +266,9 @@
     if (head != null) {
       cmd = cmd + " && git symbolic-ref HEAD " + QuotedString.BOURNE.quote(head);
     }
-    OutputStream errStream = newErrorBufferStream();
+    OutputStream errStream = sshHelper.newErrorBufferStream();
     try {
-      executeRemoteSsh(uri, cmd, errStream);
+      sshHelper.executeRemoteSsh(uri, cmd, errStream);
     } catch (IOException e) {
       repLog.error(
           String.format(
@@ -334,9 +327,9 @@
   private void deleteRemoteSsh(URIish uri) {
     String quotedPath = QuotedString.BOURNE.quote(uri.getPath());
     String cmd = "rm -rf " + quotedPath;
-    OutputStream errStream = newErrorBufferStream();
+    OutputStream errStream = sshHelper.newErrorBufferStream();
     try {
-      executeRemoteSsh(uri, cmd, errStream);
+      sshHelper.executeRemoteSsh(uri, cmd, errStream);
     } catch (IOException e) {
       repLog.error(
           String.format(
@@ -368,9 +361,9 @@
     String quotedPath = QuotedString.BOURNE.quote(uri.getPath());
     String cmd =
         "cd " + quotedPath + " && git symbolic-ref HEAD " + QuotedString.BOURNE.quote(newHead);
-    OutputStream errStream = newErrorBufferStream();
+    OutputStream errStream = sshHelper.newErrorBufferStream();
     try {
-      executeRemoteSsh(uri, cmd, errStream);
+      sshHelper.executeRemoteSsh(uri, cmd, errStream);
     } catch (IOException e) {
       repLog.error(
           String.format(
@@ -395,57 +388,6 @@
     }
   }
 
-  private void executeRemoteSsh(URIish uri, String cmd, OutputStream errStream) throws IOException {
-    RemoteSession ssh = connect(uri);
-    Process proc = ssh.exec(cmd, 0);
-    proc.getOutputStream().close();
-    StreamCopyThread out = new StreamCopyThread(proc.getInputStream(), errStream);
-    StreamCopyThread err = new StreamCopyThread(proc.getErrorStream(), errStream);
-    out.start();
-    err.start();
-    try {
-      proc.waitFor();
-      out.halt();
-      err.halt();
-    } catch (InterruptedException interrupted) {
-      // Don't wait, drop out immediately.
-    }
-    ssh.disconnect();
-  }
-
-  private RemoteSession connect(URIish uri) throws TransportException {
-    return sshSessionFactoryProvider.get().getSession(uri, null, FS.DETECTED, SSH_REMOTE_TIMEOUT);
-  }
-
-  private static OutputStream newErrorBufferStream() {
-    return new OutputStream() {
-      private final StringBuilder out = new StringBuilder();
-      private final StringBuilder line = new StringBuilder();
-
-      @Override
-      public synchronized String toString() {
-        while (out.length() > 0 && out.charAt(out.length() - 1) == '\n') {
-          out.setLength(out.length() - 1);
-        }
-        return out.toString();
-      }
-
-      @Override
-      public synchronized void write(final int b) {
-        if (b == '\r') {
-          return;
-        }
-
-        line.append((char) b);
-
-        if (b == '\n') {
-          out.append(line);
-          line.setLength(0);
-        }
-      }
-    };
-  }
-
   private static boolean isSSH(URIish uri) {
     String scheme = uri.getScheme();
     if (!uri.isRemote()) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/SshHelper.java b/src/main/java/com/googlesource/gerrit/plugins/replication/SshHelper.java
new file mode 100644
index 0000000..56a9236
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/SshHelper.java
@@ -0,0 +1,88 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// 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 com.googlesource.gerrit.plugins.replication;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import java.io.IOException;
+import java.io.OutputStream;
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.transport.RemoteSession;
+import org.eclipse.jgit.transport.SshSessionFactory;
+import org.eclipse.jgit.transport.URIish;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.io.StreamCopyThread;
+
+class SshHelper {
+  private static final int SSH_REMOTE_TIMEOUT = 120 * 1000; // 2 minutes = 120 * 1000ms
+
+  private final Provider<SshSessionFactory> sshSessionFactoryProvider;
+
+  @Inject
+  SshHelper(Provider<SshSessionFactory> sshSessionFactoryProvider) {
+    this.sshSessionFactoryProvider = sshSessionFactoryProvider;
+  }
+
+  void executeRemoteSsh(URIish uri, String cmd, OutputStream errStream) throws IOException {
+    RemoteSession ssh = connect(uri);
+    Process proc = ssh.exec(cmd, 0);
+    proc.getOutputStream().close();
+    StreamCopyThread out = new StreamCopyThread(proc.getInputStream(), errStream);
+    StreamCopyThread err = new StreamCopyThread(proc.getErrorStream(), errStream);
+    out.start();
+    err.start();
+    try {
+      proc.waitFor();
+      out.halt();
+      err.halt();
+    } catch (InterruptedException interrupted) {
+      // Don't wait, drop out immediately.
+    }
+    ssh.disconnect();
+  }
+
+  OutputStream newErrorBufferStream() {
+    return new OutputStream() {
+      private final StringBuilder out = new StringBuilder();
+      private final StringBuilder line = new StringBuilder();
+
+      @Override
+      public synchronized String toString() {
+        while (out.length() > 0 && out.charAt(out.length() - 1) == '\n') {
+          out.setLength(out.length() - 1);
+        }
+        return out.toString();
+      }
+
+      @Override
+      public synchronized void write(final int b) {
+        if (b == '\r') {
+          return;
+        }
+
+        line.append((char) b);
+
+        if (b == '\n') {
+          out.append(line);
+          line.setLength(0);
+        }
+      }
+    };
+  }
+
+  RemoteSession connect(URIish uri) throws TransportException {
+    return sshSessionFactoryProvider.get().getSession(uri, null, FS.DETECTED, SSH_REMOTE_TIMEOUT);
+  }
+}