Initial empty skeleton to display list of plugins

Define the initial project skeleton with:
- Buck build
- AngularJS HTML and JS
- X-GerritAuth expansion for REST API calls

As demo of the skeleton working well, the index.html display the
list of currently installed plugin invoking the Gerrit REST API
/plugins/?all

Change-Id: I122c37b8486609363bc92222d60153266c92e6d0
diff --git a/BUCK b/BUCK
new file mode 100644
index 0000000..de8caf8
--- /dev/null
+++ b/BUCK
@@ -0,0 +1,12 @@
+gerrit_plugin(
+  name = 'plugin-manager',
+  srcs = glob(['src/main/java/**/*.java']),
+  resources = glob(['src/main/**/*']),
+  manifest_entries = [
+    'Gerrit-PluginName: plugin-manager',
+    'Gerrit-ApiType: plugin',
+    'Gerrit-HttpModule: com.googlesource.gerrit.plugins.manager.WebModule',
+    'Implementation-Title: Plugin manager',
+    'Implementation-URL: https://gerrit-review.googlesource.com/#/admin/projects/plugins/plugin-manager',
+  ],
+)
diff --git a/src/main/java/com/googlesource/gerrit/plugins/manager/TokenReplaceOutputStream.java b/src/main/java/com/googlesource/gerrit/plugins/manager/TokenReplaceOutputStream.java
new file mode 100644
index 0000000..9810318
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/manager/TokenReplaceOutputStream.java
@@ -0,0 +1,107 @@
+// 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.manager;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.WriteListener;
+import javax.servlet.http.HttpServletResponse;
+
+public class TokenReplaceOutputStream extends ServletOutputStream {
+
+  private final byte[] token;
+
+  private final byte[] replace;
+
+  private final ByteArrayOutputStream outBuff;
+
+  private HttpServletResponse resp;
+
+  private int outLen;
+
+  public TokenReplaceOutputStream(HttpServletResponse resp, int contentLength,
+      byte[] token, byte[] replace) {
+    this.resp = resp;
+    this.outLen = contentLength;
+    this.token = token;
+    this.replace = replace;
+    this.outBuff = new ByteArrayOutputStream(contentLength);
+  }
+
+  @Override
+  public void write(int b) throws IOException {
+    outBuff.write(b);
+  }
+
+  @Override
+  public void flush() throws IOException {
+    if (outBuff.size() < outLen) {
+      return;
+    }
+
+    byte[] outData = outBuff.toByteArray();
+    byte[] cmp = new byte[token.length];
+    ByteArrayOutputStream convertedData =
+        new ByteArrayOutputStream(outData.length);
+
+    for (int i = 0; i < outData.length; i++) {
+      byte b = outData[i];
+      if (b != token[0] || (outData.length - i) < token.length) {
+        convertedData.write(outData, i, 1);
+        continue;
+      }
+
+      System.arraycopy(outData, i, cmp, 0, token.length);
+      if (Arrays.equals(cmp, token)) {
+        convertedData.write(replace);
+        i += token.length - 1;
+      }
+    }
+
+    resp.setHeader("Content-Length", "" + convertedData.size());
+
+    ServletOutputStream out = resp.getOutputStream();
+    out.write(convertedData.toByteArray());
+    out.flush();
+
+  }
+
+  @Override
+  public void close() throws IOException {
+    flush();
+  }
+
+  @Override
+  public void write(byte[] b) throws IOException {
+    outBuff.write(b);
+    flush();
+  }
+
+  @Override
+  public void write(byte[] b, int off, int len) throws IOException {
+    outBuff.write(b, off, len);
+    flush();
+  }
+
+  public void setWriteListener(@SuppressWarnings("unused") WriteListener writeListener) {
+  }
+
+  public boolean isReady() {
+    return true;
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/manager/WebModule.java b/src/main/java/com/googlesource/gerrit/plugins/manager/WebModule.java
new file mode 100644
index 0000000..9a32518
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/manager/WebModule.java
@@ -0,0 +1,30 @@
+// 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.manager;
+
+import com.google.gerrit.extensions.restapi.RestApiModule;
+import com.google.inject.servlet.ServletModule;
+
+public class WebModule extends ServletModule {
+
+  @Override
+  protected void configureServlets() {
+    install(new RestApiModule() {
+      @Override
+      protected void configure() {
+        filterRegex(".*\\.js").through(XAuthFilter.class);
+      }
+    });
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/manager/XAuthFilter.java b/src/main/java/com/googlesource/gerrit/plugins/manager/XAuthFilter.java
new file mode 100644
index 0000000..68874be
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/manager/XAuthFilter.java
@@ -0,0 +1,97 @@
+// 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.manager;
+
+import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.httpd.WebSession;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+@Singleton
+public class XAuthFilter implements Filter {
+  private static final Logger log = LoggerFactory.getLogger(XAuthFilter.class);
+
+  private DynamicItem<WebSession> webSession;
+
+  @Inject
+  public XAuthFilter(DynamicItem<WebSession> webSession) {
+    this.webSession = webSession;
+  }
+
+  @Override
+  public void destroy() {
+  }
+
+  @Override
+  public void doFilter(ServletRequest req, ServletResponse resp,
+      FilterChain chain) throws IOException, ServletException {
+    HttpServletRequest httpReq = (HttpServletRequest) req;
+    HttpServletResponse httpResp = (HttpServletResponse) resp;
+
+    final String gerritAuth = webSession.get().getXGerritAuth();
+    if (gerritAuth != null) {
+      log.info("Injecting X-Gerrit-Auth for {}", httpReq.getRequestURI());
+      httpResp = new HttpServletResponseWrapper(httpResp) {
+
+        private int origContentLength;
+
+        @Override
+        public void setHeader(String name, String value) {
+          log.info("{}: {}", name, value);
+          if (name.equalsIgnoreCase("Content-Length")) {
+            origContentLength = Integer.parseInt(value);
+          } else {
+            super.setHeader(name, value);
+          }
+        }
+
+        @Override
+        public PrintWriter getWriter() throws IOException {
+          return super.getWriter();
+        }
+
+        @Override
+        public ServletOutputStream getOutputStream() throws IOException {
+          return new TokenReplaceOutputStream(
+              (HttpServletResponse) getResponse(), origContentLength,
+              "@X-Gerrit-Auth".getBytes(), gerritAuth.getBytes());
+        }
+      };
+    }
+
+    chain.doFilter(req, httpResp);
+  }
+
+  @Override
+  public void init(FilterConfig conf) throws ServletException {
+  }
+}
diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html
new file mode 100644
index 0000000..9d16466
--- /dev/null
+++ b/src/main/resources/static/index.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html ng-app="PluginManager">
+<head>
+<meta charset="UTF-8">
+<title>Gerrit Plugin Manager</title>
+<script src= "https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js"></script>
+<script src="js/plugin-manager.js"></script>
+</head>
+<body>
+<div ng-controller="LoadInstalledPlugins as plugins">
+    <h1>Installed plugins</h1>
+    <ul>
+        <li ng-repeat="(key, prop) in plugins.list">
+            {{key}}
+        </li>
+    </ul>
+</div>
+</body>
+</html>
diff --git a/src/main/resources/static/js/plugin-manager.js b/src/main/resources/static/js/plugin-manager.js
new file mode 100644
index 0000000..ad01b06
--- /dev/null
+++ b/src/main/resources/static/js/plugin-manager.js
@@ -0,0 +1,35 @@
+// 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.
+
+var app = angular.module('PluginManager', []).controller(
+    'LoadInstalledPlugins',
+    function($http) {
+      var plugins = this;
+
+      plugins.list = {};
+
+      $http.get('/plugins/?all', plugins.httpConfig).then(
+          function successCallback(response) {
+            plugins.list = response.data;
+          }, function errorCallback(response) {
+            // called asynchronously if an error occurs
+            // or server returns response with an error status.
+          });
+    });
+
+app.config(function($httpProvider) {
+  $httpProvider.defaults.headers.common = {
+    'X-Gerrit-Auth' : '@X-Gerrit-Auth'
+  };
+});
\ No newline at end of file