Adds proxy support

- Adds unauthenticated/authenticated proxy support to the WebhookClient class.
- Adds proxy configuration options via gerrit.config.

Change-Id: I71b5476c825b58d25c660cb3583e31292997195f
diff --git a/README.md b/README.md
index 9e6c441..d828f14 100644
--- a/README.md
+++ b/README.md
@@ -126,9 +126,41 @@
         added to a review.
     publish-on-wip-ready - boolean(true/false)
         Whether a Slack notification should be published when a
-        work-in-progress change is marked ready. (defaults to the value for
-        publish-on-patch-set-created)
+        work-in-progress change is marked ready (defaults to the value for
+        publish-on-patch-set-created).
     publish-on-private-to-public - boolean(true/false)
         Whether a Slack notification should be published when a
-        private change is changed to public. (defaults to the value for
-        publish-on-patch-set-created)
+        private change is changed to public (defaults to the value for
+        publish-on-patch-set-created).
+   
+        
+Proxy Configuration
+-------------------
+
+If a proxy is needed to connect externally to Slack, a proxy server may
+be configured via Gerrit's main configuration file. The path to this file will
+vary based on where Gerrit was installed. This example assumes that Gerrit was 
+installed in _/usr/local/gerrit_.
+
+Edit _/usr/local/gerrit/etc/gerrit.config_ and add the following block.
+
+    [plugin "slack-integration"]
+        proxy-host = <hostname or IP address of the proxy server>
+        proxy-port = <port of the proxy server>
+
+
+Proxy Configuration Options
+---------------------------
+
+The following configuration options are available
+    
+    proxy-host - String
+        The host of the proxy server, e.g. my.proxy.host (defaults to null).
+    proxy-port - int
+        The port of the proxy server (defaults to 8080)
+    proxy-username - String
+        The username, if needed, to authenticate to the proxy server 
+        (defaults to null).
+    proxy-password - String
+        The password, if needed, for the specified username to authenticate to 
+        the proxy server, (defaults to null).
diff --git a/src/main/java/com/cisco/gerrit/plugins/slack/PublishEventListener.java b/src/main/java/com/cisco/gerrit/plugins/slack/PublishEventListener.java
index 3b9bc5e..05e2768 100644
--- a/src/main/java/com/cisco/gerrit/plugins/slack/PublishEventListener.java
+++ b/src/main/java/com/cisco/gerrit/plugins/slack/PublishEventListener.java
@@ -96,7 +96,7 @@
 
       if (messageGenerator.shouldPublish()) {
         WebhookClient client;
-        client = new WebhookClient();
+        client = new WebhookClient(config);
 
         client.publish(messageGenerator.generate(), config.getWebhookUrl());
       }
diff --git a/src/main/java/com/cisco/gerrit/plugins/slack/client/WebhookClient.java b/src/main/java/com/cisco/gerrit/plugins/slack/client/WebhookClient.java
index df0b088..c609d7d 100644
--- a/src/main/java/com/cisco/gerrit/plugins/slack/client/WebhookClient.java
+++ b/src/main/java/com/cisco/gerrit/plugins/slack/client/WebhookClient.java
@@ -17,14 +17,20 @@
 
 package com.cisco.gerrit.plugins.slack.client;
 
+import com.cisco.gerrit.plugins.slack.config.ProjectConfig;
 import java.io.DataOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.Authenticator;
 import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
 import java.net.MalformedURLException;
+import java.net.PasswordAuthentication;
+import java.net.Proxy;
 import java.net.URL;
 import java.nio.charset.StandardCharsets;
 import java.util.Scanner;
+import org.apache.commons.lang.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -38,6 +44,17 @@
   /** The class logger instance. */
   private static final Logger LOGGER = LoggerFactory.getLogger(WebhookClient.class);
 
+  private ProjectConfig config;
+
+  /**
+   * Creates a new WebhookClient.
+   *
+   * @param config The ProjectConfig instance to use.
+   */
+  public WebhookClient(ProjectConfig config) {
+    this.config = config;
+  }
+
   /**
    * Publish a message to the provided Slack webhook URL.
    *
@@ -94,7 +111,7 @@
         DataOutputStream request;
         request = new DataOutputStream(connection.getOutputStream());
 
-        request.write(message.getBytes("UTF-8"));
+        request.write(message.getBytes(StandardCharsets.UTF_8));
         request.flush();
         request.close();
       } catch (IOException e) {
@@ -119,7 +136,33 @@
    */
   private HttpURLConnection openConnection(String webhookUrl) {
     try {
-      return (HttpURLConnection) new URL(webhookUrl).openConnection();
+      HttpURLConnection connection;
+      if (StringUtils.isNotBlank(config.getProxyHost())) {
+        LOGGER.info("Connecting via proxy");
+        if (StringUtils.isNotBlank(config.getProxyUsername())) {
+          Authenticator authenticator;
+          authenticator =
+              new Authenticator() {
+                public PasswordAuthentication getPasswordAuthentication() {
+                  return (new PasswordAuthentication(
+                      config.getProxyUsername(), config.getProxyPassword().toCharArray()));
+                }
+              };
+          Authenticator.setDefault(authenticator);
+        }
+
+        Proxy proxy;
+        proxy =
+            new Proxy(
+                Proxy.Type.HTTP,
+                new InetSocketAddress(config.getProxyHost(), config.getProxyPort()));
+
+        connection = (HttpURLConnection) new URL(webhookUrl).openConnection(proxy);
+      } else {
+        LOGGER.info("Connecting directly");
+        connection = (HttpURLConnection) new URL(webhookUrl).openConnection();
+      }
+      return connection;
     } catch (MalformedURLException e) {
       throw new RuntimeException("Unable to create webhook URL: " + webhookUrl, e);
     } catch (IOException e) {
diff --git a/src/main/java/com/cisco/gerrit/plugins/slack/config/ProjectConfig.java b/src/main/java/com/cisco/gerrit/plugins/slack/config/ProjectConfig.java
index ee0879d..088cc4f 100644
--- a/src/main/java/com/cisco/gerrit/plugins/slack/config/ProjectConfig.java
+++ b/src/main/java/com/cisco/gerrit/plugins/slack/config/ProjectConfig.java
@@ -31,6 +31,7 @@
 public class ProjectConfig {
   /** The name of the plugin config section to lookup within the gerrit.config file. */
   public static final String CONFIG_NAME = "slack-integration";
+
   /** The class logger instance. */
   private static final Logger LOGGER = LoggerFactory.getLogger(ProjectConfig.class);
 
@@ -49,6 +50,11 @@
   private boolean publishOnWipReady;
   private boolean publishOnPrivateToPublic;
 
+  private String proxyHost;
+  private int proxyPort;
+  private String proxyUsername;
+  private String proxyPassword;
+
   /**
    * Creates a new instance of the ProjectConfig class for the given project.
    *
@@ -131,6 +137,16 @@
           configFactory
               .getFromProjectConfigWithInheritance(projectNameKey, CONFIG_NAME)
               .getBoolean("publish-on-private-to-public", publishOnPatchSetCreated);
+
+      proxyHost = configFactory.getFromGerritConfig(CONFIG_NAME).getString("proxy-host", null);
+
+      proxyPort = configFactory.getFromGerritConfig(CONFIG_NAME).getInt("proxy-port", 8080);
+
+      proxyUsername =
+          configFactory.getFromGerritConfig(CONFIG_NAME).getString("proxy-username", null);
+
+      proxyPassword =
+          configFactory.getFromGerritConfig(CONFIG_NAME).getString("proxy-password", null);
     } catch (NoSuchProjectException e) {
       LOGGER.warn("The specified project could not be found: " + project);
     }
@@ -191,4 +207,20 @@
   public boolean shouldPublishOnPrivateToPublic() {
     return publishOnPrivateToPublic;
   }
+
+  public String getProxyHost() {
+    return proxyHost;
+  }
+
+  public int getProxyPort() {
+    return proxyPort;
+  }
+
+  public String getProxyUsername() {
+    return proxyUsername;
+  }
+
+  public String getProxyPassword() {
+    return proxyPassword;
+  }
 }
diff --git a/src/test/java/com/cisco/gerrit/plugins/slack/client/WebhookClientIntegrationTest.java b/src/test/java/com/cisco/gerrit/plugins/slack/client/WebhookClientIntegrationTest.java
index b4f8849..8f02fcc 100644
--- a/src/test/java/com/cisco/gerrit/plugins/slack/client/WebhookClientIntegrationTest.java
+++ b/src/test/java/com/cisco/gerrit/plugins/slack/client/WebhookClientIntegrationTest.java
@@ -18,18 +18,64 @@
 package com.cisco.gerrit.plugins.slack.client;
 
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
+import com.cisco.gerrit.plugins.slack.config.ProjectConfig;
 import com.cisco.gerrit.plugins.slack.message.MessageTemplate;
 import com.cisco.gerrit.plugins.slack.util.ResourceHelper;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.config.PluginConfig;
+import com.google.gerrit.server.config.PluginConfigFactory;
 import java.io.InputStream;
 import java.util.Properties;
+import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
 
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({Project.NameKey.class})
 public class WebhookClientIntegrationTest {
+  private static final String PROJECT_NAME = "test-project";
+
+  private Project.NameKey mockNameKey = mock(Project.NameKey.class);
+  private PluginConfigFactory mockConfigFactory = mock(PluginConfigFactory.class);
+  private PluginConfig mockPluginConfig = mock(PluginConfig.class);
+
+  @Before
+  public void setup() throws Exception {
+    PowerMockito.mockStatic(Project.NameKey.class);
+    when(Project.NameKey.parse(PROJECT_NAME)).thenReturn(mockNameKey);
+  }
+
+  private ProjectConfig getConfig() throws Exception {
+    Project.NameKey projectNameKey;
+    projectNameKey = Project.NameKey.parse(PROJECT_NAME);
+
+    // Setup mocks
+    when(mockConfigFactory.getFromProjectConfigWithInheritance(
+            projectNameKey, ProjectConfig.CONFIG_NAME))
+        .thenReturn(mockPluginConfig);
+
+    when(mockConfigFactory.getFromGerritConfig(ProjectConfig.CONFIG_NAME))
+        .thenReturn(mockPluginConfig);
+
+    // Internal proxy integration test config
+    // when(mockPluginConfig.getString("proxy-host", null)).thenReturn("127.0.0.1");
+    // when(mockPluginConfig.getInt("proxy-port", 8080)).thenReturn(8080);
+    // when(mockPluginConfig.getString("proxy-username", null)).thenReturn("user");
+    // when(mockPluginConfig.getString("proxy-password", null)).thenReturn("password");
+
+    return new ProjectConfig(mockConfigFactory, PROJECT_NAME);
+  }
+
   @Test
   public void canPublishMessage() throws Exception {
     WebhookClient client;
-    client = new WebhookClient();
+    client = new WebhookClient(getConfig());
 
     InputStream testProperties;
     testProperties = ResourceHelper.loadNamedResourceAsStream("test.properties");
@@ -61,7 +107,7 @@
   @Test
   public void canPublishMessageWithLongMessage() throws Exception {
     WebhookClient client;
-    client = new WebhookClient();
+    client = new WebhookClient(getConfig());
 
     InputStream testProperties;
     testProperties = ResourceHelper.loadNamedResourceAsStream("test.properties");
diff --git a/src/test/java/com/cisco/gerrit/plugins/slack/config/ProjectConfigTest.java b/src/test/java/com/cisco/gerrit/plugins/slack/config/ProjectConfigTest.java
index 4c3c57e..efd5c6f 100644
--- a/src/test/java/com/cisco/gerrit/plugins/slack/config/ProjectConfigTest.java
+++ b/src/test/java/com/cisco/gerrit/plugins/slack/config/ProjectConfigTest.java
@@ -61,6 +61,9 @@
             projectNameKey, ProjectConfig.CONFIG_NAME))
         .thenReturn(mockPluginConfig);
 
+    when(mockConfigFactory.getFromGerritConfig(ProjectConfig.CONFIG_NAME))
+        .thenReturn(mockPluginConfig);
+
     when(mockPluginConfig.getBoolean("enabled", false)).thenReturn(true);
     when(mockPluginConfig.getString("webhookurl", "")).thenReturn("https://webook/");
     when(mockPluginConfig.getString("channel", "general")).thenReturn("test-channel");
diff --git a/src/test/java/com/cisco/gerrit/plugins/slack/message/ChangeMergedMessageGeneratorTest.java b/src/test/java/com/cisco/gerrit/plugins/slack/message/ChangeMergedMessageGeneratorTest.java
index 9a793c8..0e8e1a8 100644
--- a/src/test/java/com/cisco/gerrit/plugins/slack/message/ChangeMergedMessageGeneratorTest.java
+++ b/src/test/java/com/cisco/gerrit/plugins/slack/message/ChangeMergedMessageGeneratorTest.java
@@ -72,6 +72,9 @@
             projectNameKey, ProjectConfig.CONFIG_NAME))
         .thenReturn(mockPluginConfig);
 
+    when(mockConfigFactory.getFromGerritConfig(ProjectConfig.CONFIG_NAME))
+            .thenReturn(mockPluginConfig);
+
     when(mockPluginConfig.getBoolean("enabled", false)).thenReturn(true);
     when(mockPluginConfig.getString("webhookurl", "")).thenReturn("https://webook/");
     when(mockPluginConfig.getString("channel", "general")).thenReturn("testchannel");
diff --git a/src/test/java/com/cisco/gerrit/plugins/slack/message/CommentAddedMessageGeneratorTest.java b/src/test/java/com/cisco/gerrit/plugins/slack/message/CommentAddedMessageGeneratorTest.java
index cc62aa5..026ab08 100644
--- a/src/test/java/com/cisco/gerrit/plugins/slack/message/CommentAddedMessageGeneratorTest.java
+++ b/src/test/java/com/cisco/gerrit/plugins/slack/message/CommentAddedMessageGeneratorTest.java
@@ -77,6 +77,9 @@
             projectNameKey, ProjectConfig.CONFIG_NAME))
         .thenReturn(mockPluginConfig);
 
+    when(mockConfigFactory.getFromGerritConfig(ProjectConfig.CONFIG_NAME))
+            .thenReturn(mockPluginConfig);
+
     when(mockPluginConfig.getBoolean("enabled", false)).thenReturn(true);
     when(mockPluginConfig.getString("webhookurl", "")).thenReturn("https://webook/");
     when(mockPluginConfig.getString("channel", "general")).thenReturn("testchannel");
diff --git a/src/test/java/com/cisco/gerrit/plugins/slack/message/PatchSetCreatedMessageGeneratorTest.java b/src/test/java/com/cisco/gerrit/plugins/slack/message/PatchSetCreatedMessageGeneratorTest.java
index 80e8cf8..1eb7a48 100644
--- a/src/test/java/com/cisco/gerrit/plugins/slack/message/PatchSetCreatedMessageGeneratorTest.java
+++ b/src/test/java/com/cisco/gerrit/plugins/slack/message/PatchSetCreatedMessageGeneratorTest.java
@@ -78,6 +78,9 @@
             projectNameKey, ProjectConfig.CONFIG_NAME))
         .thenReturn(mockPluginConfig);
 
+    when(mockConfigFactory.getFromGerritConfig(ProjectConfig.CONFIG_NAME))
+            .thenReturn(mockPluginConfig);
+
     when(mockPluginConfig.getBoolean("enabled", false)).thenReturn(true);
     when(mockPluginConfig.getString("webhookurl", "")).thenReturn("https://webook/");
     when(mockPluginConfig.getString("channel", "general")).thenReturn("testchannel");
diff --git a/src/test/java/com/cisco/gerrit/plugins/slack/message/PrivateStateChangedGeneratorTest.java b/src/test/java/com/cisco/gerrit/plugins/slack/message/PrivateStateChangedGeneratorTest.java
index 7460dc3..038c061 100644
--- a/src/test/java/com/cisco/gerrit/plugins/slack/message/PrivateStateChangedGeneratorTest.java
+++ b/src/test/java/com/cisco/gerrit/plugins/slack/message/PrivateStateChangedGeneratorTest.java
@@ -72,6 +72,9 @@
             projectNameKey, ProjectConfig.CONFIG_NAME))
         .thenReturn(mockPluginConfig);
 
+    when(mockConfigFactory.getFromGerritConfig(ProjectConfig.CONFIG_NAME))
+            .thenReturn(mockPluginConfig);
+
     when(mockPluginConfig.getBoolean("enabled", false)).thenReturn(true);
     when(mockPluginConfig.getString("webhookurl", "")).thenReturn("https://webook/");
     when(mockPluginConfig.getString("channel", "general")).thenReturn("testchannel");
diff --git a/src/test/java/com/cisco/gerrit/plugins/slack/message/ReviewerAddedMessageGeneratorTest.java b/src/test/java/com/cisco/gerrit/plugins/slack/message/ReviewerAddedMessageGeneratorTest.java
index 23720a9..9ba5fe0 100644
--- a/src/test/java/com/cisco/gerrit/plugins/slack/message/ReviewerAddedMessageGeneratorTest.java
+++ b/src/test/java/com/cisco/gerrit/plugins/slack/message/ReviewerAddedMessageGeneratorTest.java
@@ -77,6 +77,9 @@
             projectNameKey, ProjectConfig.CONFIG_NAME))
         .thenReturn(mockPluginConfig);
 
+    when(mockConfigFactory.getFromGerritConfig(ProjectConfig.CONFIG_NAME))
+            .thenReturn(mockPluginConfig);
+
     when(mockPluginConfig.getBoolean("enabled", false)).thenReturn(true);
     when(mockPluginConfig.getString("webhookurl", "")).thenReturn("https://webook/");
     when(mockPluginConfig.getString("channel", "general")).thenReturn("testchannel");
diff --git a/src/test/java/com/cisco/gerrit/plugins/slack/message/WorkInProgressStateChangedGeneratorTest.java b/src/test/java/com/cisco/gerrit/plugins/slack/message/WorkInProgressStateChangedGeneratorTest.java
index d9a62c8..056b363 100644
--- a/src/test/java/com/cisco/gerrit/plugins/slack/message/WorkInProgressStateChangedGeneratorTest.java
+++ b/src/test/java/com/cisco/gerrit/plugins/slack/message/WorkInProgressStateChangedGeneratorTest.java
@@ -72,6 +72,9 @@
             projectNameKey, ProjectConfig.CONFIG_NAME))
         .thenReturn(mockPluginConfig);
 
+    when(mockConfigFactory.getFromGerritConfig(ProjectConfig.CONFIG_NAME))
+            .thenReturn(mockPluginConfig);
+
     when(mockPluginConfig.getBoolean("enabled", false)).thenReturn(true);
     when(mockPluginConfig.getString("webhookurl", "")).thenReturn("https://webook/");
     when(mockPluginConfig.getString("channel", "general")).thenReturn("testchannel");