Merge "Add REST client"
diff --git a/BUCK b/BUCK
index 2b74c47..39591cb 100644
--- a/BUCK
+++ b/BUCK
@@ -1,5 +1,15 @@
 include_defs('//bucklets/gerrit_plugin.bucklet')
 
+# TODO: support standalone Buck build with 2.11
+#if STANDALONE_MODE:
+#  HTTP_LIB = '//lib/http:http_lib'
+#  GSON = '//lib/gson:gson'
+#else:
+#  HTTP_LIB = '//plugins/importer/lib/http:http_lib'
+#  GSON = '//plugins/importer/lib/gson:gson'
+HTTP_LIB = '//plugins/importer/lib/http:http_lib'
+GSON = '//plugins/importer/lib/gson:gson'
+
 gerrit_plugin(
   name = 'importer',
   srcs = glob(['src/main/java/**/*.java']),
@@ -11,6 +21,10 @@
     'Gerrit-Module: com.googlesource.gerrit.plugins.importer.Module',
     'Gerrit-SshModule: com.googlesource.gerrit.plugins.importer.SshModule',
   ],
+  deps = [
+    HTTP_LIB,
+    GSON,
+  ],
 )
 
 # this is required for bucklets/tools/eclipse/project.py to work
diff --git a/lib/commons/BUCK b/lib/commons/BUCK
new file mode 100644
index 0000000..ba42a1f
--- /dev/null
+++ b/lib/commons/BUCK
@@ -0,0 +1,41 @@
+include_defs('//bucklets/maven_jar.bucklet')
+
+java_library(
+  name = 'commons_lib',
+  deps = [
+    ':codec',
+    ':io',
+    ':lang',
+  ],
+  visibility = ['PUBLIC'],
+)
+
+EXCLUDE = [
+  'META-INF/LICENSE.txt',
+  'META-INF/NOTICE.txt'
+]
+
+maven_jar(
+  name = 'codec',
+  id = 'commons-codec:commons-codec:1.4',
+  sha1 = '4216af16d38465bbab0f3dff8efa14204f7a399a',
+  license = 'Apache2.0',
+  exclude = EXCLUDE,
+)
+
+maven_jar(
+  name = 'io',
+  id = 'commons-io:commons-io:1.4',
+  sha1 = 'a8762d07e76cfde2395257a5da47ba7c1dbd3dce',
+  license = 'Apache2.0',
+  exclude = EXCLUDE,
+)
+
+maven_jar(
+  name = 'lang',
+  id = 'commons-lang:commons-lang:2.5',
+  sha1 = 'b0236b252e86419eef20c31a44579d2aee2f0a69',
+  license = 'Apache2.0',
+  exclude = EXCLUDE,
+)
+
diff --git a/lib/gson/BUCK b/lib/gson/BUCK
new file mode 100644
index 0000000..8892994
--- /dev/null
+++ b/lib/gson/BUCK
@@ -0,0 +1,8 @@
+include_defs('//bucklets/maven_jar.bucklet')
+
+maven_jar(
+  name = 'gson',
+  id = 'com.google.code.gson:gson:2.1',
+  sha1 = '2e66da15851f9f5b5079228f856c2f090ba98c38',
+  license = 'Apache2.0',
+)
diff --git a/lib/http/BUCK b/lib/http/BUCK
new file mode 100644
index 0000000..0b9d990
--- /dev/null
+++ b/lib/http/BUCK
@@ -0,0 +1,43 @@
+include_defs('//bucklets/gerrit_plugin.bucklet')
+include_defs('//bucklets/maven_jar.bucklet')
+
+# TODO: support standalone Buck build with 2.11
+#if STANDALONE_MODE:
+#  COMMONS = '//lib/commons:commons_lib'
+#  LOG = '//lib/log:jcl-over-slf4j'
+#else:
+#  COMMONS = '//plugins/importer/lib/commons:commons_lib'
+#  LOG = '//plugins/importer/lib/log:jcl-over-slf4j'
+COMMONS = '//plugins/importer/lib/commons:commons_lib'
+LOG = '//plugins/importer/lib/log:jcl-over-slf4j'
+
+java_library(
+  name = 'http_lib',
+  exported_deps = [
+    ':httpclient',
+    ':httpcore',
+  ],
+  visibility = ['PUBLIC'],
+)
+
+maven_jar(
+  name = 'httpclient',
+  id = 'org.apache.httpcomponents:httpclient:4.3.4',
+  bin_sha1 = 'a9a1fef2faefed639ee0d0fba5b3b8e4eb2ff2d8',
+  src_sha1 = '7a14aafed8c5e2c4e360a2c1abd1602efa768b1f',
+  license = 'Apache2.0',
+  deps = [
+    COMMONS,
+    ':httpcore',
+    LOG,
+  ],
+)
+
+maven_jar(
+  name = 'httpcore',
+  id = 'org.apache.httpcomponents:httpcore:4.3.2',
+  bin_sha1 = '31fbbff1ddbf98f3aa7377c94d33b0447c646b6e',
+  src_sha1 = '4809f38359edeea9487f747e09aa58ec8d3a54c5',
+  license = 'Apache2.0',
+)
+
diff --git a/lib/log/BUCK b/lib/log/BUCK
new file mode 100644
index 0000000..fc994d6
--- /dev/null
+++ b/lib/log/BUCK
@@ -0,0 +1,8 @@
+include_defs('//bucklets/maven_jar.bucklet')
+
+maven_jar(
+  name = 'jcl-over-slf4j',
+  id = 'org.slf4j:jcl-over-slf4j:1.7.7',
+  sha1 = '56003dcd0a31deea6391b9e2ef2f2dc90b205a92',
+  license = 'slf4j',
+)
diff --git a/pom.xml b/pom.xml
index 2a881fb..3a9818a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -65,6 +65,24 @@
           <encoding>UTF-8</encoding>
         </configuration>
       </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-shade-plugin</artifactId>
+        <version>2.3</version>
+        <configuration>
+          <promoteTransitiveDependencies>true</promoteTransitiveDependencies>
+          <createDependencyReducedPom>false</createDependencyReducedPom>
+        </configuration>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>shade</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
     </plugins>
   </build>
 
@@ -75,5 +93,15 @@
       <version>${Gerrit-ApiVersion}</version>
       <scope>provided</scope>
     </dependency>
+    <dependency>
+      <groupId>org.apache.httpcomponents</groupId>
+      <artifactId>httpclient</artifactId>
+      <version>4.3.4</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.httpcomponents</groupId>
+      <artifactId>httpcore</artifactId>
+      <version>4.3.2</version>
+    </dependency>
   </dependencies>
 </project>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/HttpResponse.java b/src/main/java/com/googlesource/gerrit/plugins/importer/HttpResponse.java
new file mode 100755
index 0000000..4cd58dd
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/HttpResponse.java
@@ -0,0 +1,67 @@
+// Copyright (C) 2015 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.importer;
+
+import com.google.common.base.Preconditions;
+
+import org.eclipse.jgit.util.IO;
+import org.eclipse.jgit.util.RawParseUtils;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.ByteBuffer;
+
+public class HttpResponse {
+
+  protected org.apache.http.HttpResponse response;
+  protected Reader reader;
+
+  HttpResponse(org.apache.http.HttpResponse response) {
+    this.response = response;
+  }
+
+  public Reader getReader() throws IllegalStateException, IOException {
+    if (reader == null && response.getEntity() != null) {
+      reader = new InputStreamReader(response.getEntity().getContent());
+    }
+    return reader;
+  }
+
+  public void consume() throws IllegalStateException, IOException {
+    Reader reader = getReader();
+    if (reader != null) {
+      while (reader.read() != -1);
+    }
+  }
+
+  public int getStatusCode() {
+    return response.getStatusLine().getStatusCode();
+  }
+
+  public String getEntityContent() throws IOException {
+    Preconditions.checkNotNull(response, "Response is not initialized.");
+    Preconditions.checkNotNull(response.getEntity(),
+        "Response.Entity is not initialized.");
+    ByteBuffer buf = IO.readWholeStream(
+        response.getEntity().getContent(),
+        1024);
+    return RawParseUtils.decode(
+        buf.array(),
+        buf.arrayOffset(),
+        buf.limit())
+        .trim();
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/HttpSession.java b/src/main/java/com/googlesource/gerrit/plugins/importer/HttpSession.java
new file mode 100755
index 0000000..bce528b
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/HttpSession.java
@@ -0,0 +1,62 @@
+// Copyright (C) 2015 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.importer;
+
+import com.google.common.base.CharMatcher;
+
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.impl.client.HttpClientBuilder;
+
+import java.io.IOException;
+import java.net.URI;
+
+public class HttpSession {
+
+  protected final String url;
+  private final String user;
+  private final String pass;
+  private HttpClient client;
+
+  public HttpSession(String url, String user, String pass) {
+    this.url = CharMatcher.is('/').trimTrailingFrom(url);
+    this.user = user;
+    this.pass = pass;
+  }
+
+  public HttpResponse get(String path) throws IOException {
+    HttpGet get = new HttpGet(url + path);
+    return new HttpResponse(getClient().execute(get));
+  }
+
+  protected HttpClient getClient() {
+    if (client == null) {
+      URI uri = URI.create(url);
+      BasicCredentialsProvider creds = new BasicCredentialsProvider();
+      creds.setCredentials(new AuthScope(uri.getHost(), uri.getPort()),
+          new UsernamePasswordCredentials(user, pass));
+      client = HttpClientBuilder
+          .create()
+          .setDefaultCredentialsProvider(creds)
+          .setMaxConnPerRoute(10)
+          .setMaxConnTotal(1024)
+          .build();
+    }
+    return client;
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/RestResponse.java b/src/main/java/com/googlesource/gerrit/plugins/importer/RestResponse.java
new file mode 100755
index 0000000..512db6e
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/RestResponse.java
@@ -0,0 +1,37 @@
+// Copyright (C) 2015 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.importer;
+
+import static com.google.gerrit.httpd.restapi.RestApiServlet.JSON_MAGIC;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+
+public class RestResponse extends HttpResponse {
+
+  RestResponse(org.apache.http.HttpResponse response) {
+    super(response);
+  }
+
+  @Override
+  public Reader getReader() throws IllegalStateException, IOException {
+    if (reader == null && response.getEntity() != null) {
+      reader = new InputStreamReader(response.getEntity().getContent());
+      reader.skip(JSON_MAGIC.length);
+    }
+    return reader;
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/RestSession.java b/src/main/java/com/googlesource/gerrit/plugins/importer/RestSession.java
new file mode 100755
index 0000000..f02ecf7
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/RestSession.java
@@ -0,0 +1,117 @@
+// Copyright (C) 2015 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.importer;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Preconditions;
+import com.google.gerrit.extensions.restapi.RawInput;
+import com.google.gerrit.server.OutputFormat;
+
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.entity.BufferedHttpEntity;
+import org.apache.http.entity.InputStreamEntity;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.message.BasicHeader;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+public class RestSession extends HttpSession {
+
+  public RestSession(String url, String user, String pass) {
+    super(url, user, pass);
+  }
+
+  @Override
+  public RestResponse get(String endPoint) throws IOException {
+    HttpGet get = new HttpGet(url + "/a" + endPoint);
+    return new RestResponse(getClient().execute(get));
+  }
+
+  public RestResponse put(String endPoint) throws IOException {
+    return put(endPoint, null);
+  }
+
+  public RestResponse put(String endPoint, Object content) throws IOException {
+    HttpPut put = new HttpPut(url + "/a" + endPoint);
+    if (content != null) {
+      put.addHeader(new BasicHeader("Content-Type", "application/json"));
+      put.setEntity(new StringEntity(
+          OutputFormat.JSON_COMPACT.newGson().toJson(content),
+          Charsets.UTF_8.name()));
+    }
+    return new RestResponse(getClient().execute(put));
+  }
+
+  public RestResponse putRaw(String endPoint, RawInput stream) throws IOException {
+    Preconditions.checkNotNull(stream);
+    HttpPut put = new HttpPut(url + "/a" + endPoint);
+    put.addHeader(new BasicHeader("Content-Type", stream.getContentType()));
+    put.setEntity(new BufferedHttpEntity(
+        new InputStreamEntity(
+            stream.getInputStream(),
+            stream.getContentLength())));
+    return new RestResponse(getClient().execute(put));
+  }
+
+  public RestResponse post(String endPoint) throws IOException {
+    return post(endPoint, null);
+  }
+
+  public RestResponse post(String endPoint, Object content) throws IOException {
+    HttpPost post = new HttpPost(url + "/a" + endPoint);
+    if (content != null) {
+      post.addHeader(new BasicHeader("Content-Type", "application/json"));
+      post.setEntity(new StringEntity(
+          OutputFormat.JSON_COMPACT.newGson().toJson(content),
+          Charsets.UTF_8.name()));
+    }
+    return new RestResponse(getClient().execute(post));
+  }
+
+  public RestResponse delete(String endPoint) throws IOException {
+    HttpDelete delete = new HttpDelete(url + "/a" + endPoint);
+    return new RestResponse(getClient().execute(delete));
+  }
+
+
+  public static RawInput newRawInput(final String content) throws IOException {
+    Preconditions.checkNotNull(content);
+    Preconditions.checkArgument(!content.isEmpty());
+    return new RawInput() {
+      byte bytes[] = content.getBytes(StandardCharsets.UTF_8);
+
+      @Override
+      public InputStream getInputStream() throws IOException {
+        return new ByteArrayInputStream(bytes);
+      }
+
+      @Override
+      public String getContentType() {
+        return "application/octet-stream";
+      }
+
+      @Override
+      public long getContentLength() {
+        return bytes.length;
+      }
+    };
+  }
+}