Add PUT to JiraRestApi

Change-Id: I03245612dbca9cc935a60ef972f39d7fe91a9474
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/jira/InitJira.java b/src/main/java/com/googlesource/gerrit/plugins/its/jira/InitJira.java
index 59cb1bd..91cf5b8 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/its/jira/InitJira.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/jira/InitJira.java
@@ -27,9 +27,9 @@
 import com.googlesource.gerrit.plugins.its.base.validation.ItsAssociationPolicy;
 import com.googlesource.gerrit.plugins.its.jira.restapi.JiraServerInfo;
 import com.googlesource.gerrit.plugins.its.jira.restapi.JiraServerInfoRestApi;
+import com.googlesource.gerrit.plugins.its.jira.restapi.JiraURL;
 import java.io.IOException;
 import java.net.MalformedURLException;
-import java.net.URL;
 import java.util.Arrays;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 
@@ -40,7 +40,7 @@
   private final Section.Factory sections;
   private final InitFlags flags;
   private Section jira;
-  private URL jiraUrl;
+  private JiraURL jiraUrl;
   private String jiraUsername;
   private String jiraPassword;
 
@@ -120,7 +120,7 @@
   public void enterJiraConnectivity() throws MalformedURLException {
     String jiraUrlString = jira.string("Jira URL (empty to skip)", "url", null);
     if (jiraUrlString != null) {
-      jiraUrl = new URL(jiraUrlString);
+      jiraUrl = new JiraURL(jiraUrlString);
       jiraUsername = jira.string("Jira username", "username", "");
       jiraPassword = jira.password("username", "password");
     }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/jira/JiraConfig.java b/src/main/java/com/googlesource/gerrit/plugins/its/jira/JiraConfig.java
index 9ec5d9b..a39e059 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/its/jira/JiraConfig.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/jira/JiraConfig.java
@@ -14,15 +14,14 @@
 
 package com.googlesource.gerrit.plugins.its.jira;
 
-import static com.googlesource.gerrit.plugins.its.jira.UrlHelper.*;
 import static java.lang.String.format;
 
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.its.jira.restapi.JiraURL;
 import java.net.MalformedURLException;
-import java.net.URL;
 import org.eclipse.jgit.lib.Config;
 
 /** The JIRA plugin configuration as read from Gerrit config. */
@@ -33,7 +32,7 @@
   static final String GERRIT_CONFIG_USERNAME = "username";
   static final String GERRIT_CONFIG_PASSWORD = "password";
 
-  private final URL jiraUrl;
+  private final JiraURL jiraUrl;
   private final String jiraUsername;
   private final String jiraPassword;
 
@@ -46,7 +45,7 @@
   @Inject
   JiraConfig(@GerritServerConfig Config config, @PluginName String pluginName) {
     try {
-      jiraUrl = adjustUrlPath(new URL(config.getString(pluginName, null, GERRIT_CONFIG_URL)));
+      jiraUrl = new JiraURL(config.getString(pluginName, null, GERRIT_CONFIG_URL)).adjustUrlPath();
     } catch (MalformedURLException e) {
       throw new RuntimeException(format(ERROR_MSG, pluginName, e.getLocalizedMessage()));
     }
@@ -63,7 +62,7 @@
    *
    * @return the jira url
    */
-  public URL getJiraUrl() {
+  public JiraURL getJiraUrl() {
     return jiraUrl;
   }
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/jira/UrlHelper.java b/src/main/java/com/googlesource/gerrit/plugins/its/jira/UrlHelper.java
deleted file mode 100644
index a6b89ef..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/its/jira/UrlHelper.java
+++ /dev/null
@@ -1,37 +0,0 @@
-
-package com.googlesource.gerrit.plugins.its.jira;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.Arrays;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class UrlHelper {
-  private static final Logger log = LoggerFactory.getLogger(UrlHelper.class);
-
-  public static URL resolveUrl(URL url, String... paths) {
-    if (url == null) {
-      return url;
-    }
-
-    String relativePath = String.join("", Arrays.asList(paths));
-    try {
-      return new URL(url, relativePath);
-    } catch (MalformedURLException e) {
-      log.error("Unexpected exception while composing URL {} with path {}", url, relativePath, e);
-      throw new IllegalArgumentException(e);
-    }
-  }
-
-  public static URL adjustUrlPath(URL url) {
-    if (url == null) {
-      return url;
-    }
-    try {
-      return url.getPath().endsWith("/") ? url : new URL(url, "/");
-    } catch (MalformedURLException e) {
-      throw new RuntimeException(e);
-    }
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/jira/restapi/JiraRestApi.java b/src/main/java/com/googlesource/gerrit/plugins/its/jira/restapi/JiraRestApi.java
index 450ac47..ceec13d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/its/jira/restapi/JiraRestApi.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/jira/restapi/JiraRestApi.java
@@ -14,27 +14,22 @@
 
 package com.googlesource.gerrit.plugins.its.jira.restapi;
 
-import static com.googlesource.gerrit.plugins.its.jira.UrlHelper.*;
-
 import com.google.gson.Gson;
 import com.google.inject.Inject;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.net.HttpURLConnection;
-import java.net.Proxy;
 import java.net.ProxySelector;
-import java.net.URL;
 import java.util.Base64;
 import org.apache.commons.lang.ArrayUtils;
-import org.eclipse.jgit.util.HttpSupport;
 
 /** Jira Rest Client. */
 public class JiraRestApi<T> {
 
   private static final String BASE_PREFIX = "rest/api/2";
 
-  private final URL baseUrl;
+  private final JiraURL baseUrl;
   private final String auth;
   private final Gson gson;
 
@@ -52,9 +47,9 @@
    * @param classPrefix prefix for the rest api request
    */
   @Inject
-  public JiraRestApi(URL url, String user, String pass, Class<T> classOfT, String classPrefix) {
+  public JiraRestApi(JiraURL url, String user, String pass, Class<T> classOfT, String classPrefix) {
     this.auth = encode(user, pass);
-    this.baseUrl = resolveUrl(url, BASE_PREFIX, classPrefix, "/");
+    this.baseUrl = url == null ? url : url.resolveUrl(BASE_PREFIX, classPrefix, "/");
     this.gson = new Gson();
     this.classOfT = classOfT;
   }
@@ -75,7 +70,7 @@
    * @throws IOException generated if unexpected failCode is returned
    */
   public T doGet(String spec, int passCode, int[] failCodes) throws IOException {
-    HttpURLConnection conn = prepHttpConnection(spec, false);
+    HttpURLConnection conn = prepHttpConnection(spec, "GET", false);
     try {
       if (validateResponse(conn, passCode, failCodes)) {
         readIncomingData(conn);
@@ -90,43 +85,51 @@
     return doGet(spec, passCode, null);
   }
 
-  URL getBaseUrl() {
+  JiraURL getBaseUrl() {
     return baseUrl;
   }
 
   /** Do a simple POST request. */
   public boolean doPost(String spec, String jsonInput, int passCode) throws IOException {
-    HttpURLConnection conn = prepHttpConnection(spec, true);
+    return sendPayload("POST", spec, jsonInput, passCode);
+  }
+
+  /** Do a simple PUT request. */
+  public boolean doPut(String spec, String jsonInput, int passCode) throws IOException {
+    return sendPayload("PUT", spec, jsonInput, passCode);
+  }
+
+  private boolean sendPayload(String method, String spec, String jsonInput, int passCode)
+      throws IOException {
+    HttpURLConnection conn = prepHttpConnection(spec, method, true);
     try {
-      writePostData(jsonInput, conn);
+      writeBodyData(jsonInput, conn);
       return validateResponse(conn, passCode, null);
     } finally {
       conn.disconnect();
     }
   }
 
-  private HttpURLConnection prepHttpConnection(String spec, boolean isPostRequest)
+  private HttpURLConnection prepHttpConnection(String spec, String method, boolean withPayload)
       throws IOException {
-    URL url = new URL(baseUrl, spec);
+    JiraURL url = baseUrl.withSpec(spec);
     ProxySelector proxySelector = ProxySelector.getDefault();
-    Proxy proxy = HttpSupport.proxyFor(proxySelector, url);
-    HttpURLConnection conn = (HttpURLConnection) url.openConnection(proxy);
+    HttpURLConnection conn = url.openConnection(proxySelector);
     conn.setRequestProperty("Authorization", "Basic " + auth);
     conn.setRequestProperty("Content-Type", "application/json");
-    if (isPostRequest) {
-      conn.setRequestMethod("POST");
+
+    conn.setRequestMethod(method.toUpperCase());
+    if (withPayload) {
       conn.setDoOutput(true);
-    } else {
-      conn.setRequestMethod("GET");
     }
     return conn;
   }
 
-  /** Write the Read the returned data from the HTTP connection. */
-  private void writePostData(String postData, HttpURLConnection conn) throws IOException {
-    if (postData != null) {
+  /** Write the data to the HTTP connection. */
+  private void writeBodyData(String data, HttpURLConnection conn) throws IOException {
+    if (data != null) {
       try (OutputStream os = conn.getOutputStream()) {
-        os.write(postData.getBytes());
+        os.write(data.getBytes());
         os.flush();
       }
     }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/jira/restapi/JiraServerInfoRestApi.java b/src/main/java/com/googlesource/gerrit/plugins/its/jira/restapi/JiraServerInfoRestApi.java
index 10a4c91..0a64de0 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/its/jira/restapi/JiraServerInfoRestApi.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/jira/restapi/JiraServerInfoRestApi.java
@@ -17,11 +17,10 @@
 import static java.net.HttpURLConnection.HTTP_OK;
 
 import java.io.IOException;
-import java.net.URL;
 
 public class JiraServerInfoRestApi extends JiraRestApi<JiraServerInfo> {
 
-  public JiraServerInfoRestApi(URL url, String user, String pass) {
+  public JiraServerInfoRestApi(JiraURL url, String user, String pass) {
     super(url, user, pass, JiraServerInfo.class, "/serverInfo/");
   }
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/jira/restapi/JiraURL.java b/src/main/java/com/googlesource/gerrit/plugins/its/jira/restapi/JiraURL.java
new file mode 100644
index 0000000..03935a5
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/jira/restapi/JiraURL.java
@@ -0,0 +1,93 @@
+// Copyright (C) 2018 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.its.jira.restapi;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Objects;
+import org.eclipse.jgit.util.HttpSupport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class JiraURL {
+
+  private static final Logger log = LoggerFactory.getLogger(JiraURL.class);
+
+  private final URL url;
+
+  public JiraURL(String spec) throws MalformedURLException {
+    this.url = new URL(spec);
+  }
+
+  private JiraURL(URL url) {
+    this.url = requireNonNull(url);
+  }
+
+  public JiraURL resolveUrl(String... paths) {
+    String relativePath = String.join("", Arrays.asList(paths));
+    try {
+      return new JiraURL(new URL(url, relativePath));
+    } catch (MalformedURLException e) {
+      log.error("Unexpected exception while composing URL {} with path {}", url, relativePath, e);
+      throw new IllegalArgumentException(e);
+    }
+  }
+
+  public JiraURL adjustUrlPath() {
+    try {
+      return url.getPath().endsWith("/") ? this : this.withSpec("/");
+    } catch (MalformedURLException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public JiraURL withSpec(String spec) throws MalformedURLException {
+    return new JiraURL(new URL(url, spec));
+  }
+
+  public HttpURLConnection openConnection(ProxySelector proxySelector) throws IOException {
+    Proxy proxy = HttpSupport.proxyFor(proxySelector, url);
+    return (HttpURLConnection) url.openConnection(proxy);
+  }
+
+  public String getPath() {
+    return url.getPath();
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    JiraURL jiraURL = (JiraURL) o;
+    return Objects.equals(url, jiraURL.url);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(url);
+  }
+
+  @Override
+  public String toString() {
+    return url.toString();
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/its/jira/JiraConfigTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/jira/JiraConfigTest.java
index fba7db7..e25d1ef 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/its/jira/JiraConfigTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/jira/JiraConfigTest.java
@@ -20,8 +20,8 @@
 import static com.googlesource.gerrit.plugins.its.jira.JiraConfig.GERRIT_CONFIG_USERNAME;
 import static org.mockito.Mockito.when;
 
+import com.googlesource.gerrit.plugins.its.jira.restapi.JiraURL;
 import java.net.MalformedURLException;
-import java.net.URL;
 import org.eclipse.jgit.lib.Config;
 import org.junit.Rule;
 import org.junit.Test;
@@ -34,7 +34,7 @@
 public class JiraConfigTest {
 
   private static final String PASS = "pass";
-  private static final URL TEST_URL = newUrl("http://jira_example.com/");
+  private static final JiraURL TEST_URL = newUrl("http://jira_example.com/");
   private static final String USER = "user";
   private static final String PLUGIN_NAME = "its-jira";
 
@@ -60,9 +60,9 @@
     jiraConfig = new JiraConfig(cfg, PLUGIN_NAME);
   }
 
-  private static URL newUrl(String url) {
+  private static JiraURL newUrl(String url) {
     try {
-      return new URL(url);
+      return new JiraURL(url);
     } catch (MalformedURLException e) {
       throw new RuntimeException(e);
     }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/its/jira/restapi/JiraRestApiTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/jira/restapi/JiraRestApiTest.java
index 073a03c..1ee2ab2 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/its/jira/restapi/JiraRestApiTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/jira/restapi/JiraRestApiTest.java
@@ -15,10 +15,14 @@
 package com.googlesource.gerrit.plugins.its.jira.restapi;
 
 import static com.google.common.truth.Truth.assertThat;
+import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
 
 import com.google.common.base.CharMatcher;
+import java.io.ByteArrayOutputStream;
+import java.net.HttpURLConnection;
 import java.net.MalformedURLException;
-import java.net.URL;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.junit.MockitoJUnitRunner;
@@ -27,14 +31,15 @@
 @SuppressWarnings({"unchecked", "rawtypes"})
 public class JiraRestApiTest {
   private static final String ISSUE_CLASS_PREFIX = "/issue/";
+  private static final String JSON_PAYLOAD = "{}";
 
-  private URL url;
+  private JiraURL url;
   private String user = "user";
   private String password = "pass";
   private JiraRestApi restApi;
 
   private void setURL(String jiraUrl) throws MalformedURLException {
-    url = new URL(CharMatcher.is('/').trimFrom(jiraUrl) + "/");
+    url = new JiraURL(CharMatcher.is('/').trimFrom(jiraUrl) + "/");
   }
 
   @Test
@@ -60,4 +65,25 @@
     String jiraApiUrl = restApi.getBaseUrl().toString();
     assertThat(jiraApiUrl).startsWith(url.toString());
   }
+
+  @Test
+  public void testDoPut() throws Exception {
+    JiraURL url = mock(JiraURL.class);
+    when(url.resolveUrl(any())).thenReturn(url);
+    when(url.withSpec(ISSUE_CLASS_PREFIX)).thenReturn(url);
+
+    HttpURLConnection connection = mock(HttpURLConnection.class);
+    when(url.openConnection(any())).thenReturn(connection);
+    ByteArrayOutputStream connectionOutputStream = new ByteArrayOutputStream();
+    when(connection.getOutputStream()).thenReturn(connectionOutputStream);
+    when(connection.getResponseCode()).thenReturn(HTTP_NO_CONTENT);
+
+    restApi = new JiraRestApi(url, user, password, JiraIssue.class, ISSUE_CLASS_PREFIX);
+    boolean pass = restApi.doPut(ISSUE_CLASS_PREFIX, JSON_PAYLOAD, HTTP_NO_CONTENT);
+
+    verify(connection).setRequestMethod("PUT");
+    verify(connection).setDoOutput(true);
+    assertThat(pass).isTrue();
+    assertThat(new String(connectionOutputStream.toByteArray(), "UTF-8")).isEqualTo(JSON_PAYLOAD);
+  }
 }