Allow using healthcheck on slaves

Gerrit slaves do not bind the REST-API servlet: the standard
binding of the HealthCheck API would not work at all.

Define on slaves a special servlet filter that is filtering
exactly the HealthCheck API and serves exactly the same content
in the same way.

Change-Id: I6ab4524dcaa96d8571c3fad2eb03624f00902943
diff --git a/BUILD b/BUILD
index 41f1770..e87843c 100644
--- a/BUILD
+++ b/BUILD
@@ -11,7 +11,8 @@
     srcs = glob(["src/main/java/**/*.java"]),
     manifest_entries = [
         "Gerrit-PluginName: healthcheck",
-        "Gerrit-Module: com.googlesource.gerrit.plugins.healthcheck.Module"
+        "Gerrit-Module: com.googlesource.gerrit.plugins.healthcheck.Module",
+        "Gerrit-HttpModule: com.googlesource.gerrit.plugins.healthcheck.HttpModule",
     ],
     resources = glob(["src/main/resources/**/*"]),
 )
diff --git a/README.md b/README.md
index f375030..8465a54 100644
--- a/README.md
+++ b/README.md
@@ -25,6 +25,9 @@
 ## How to install
 
 Copy the healthcheck.jar into the Gerrit's /plugins directory and wait for the plugin to be automatically loaded.
+The healthcheck plugin is compatible with both Gerrit master and slave setups. The only difference to bear in mind
+is that some checks may not be successful on slaves (e.g. query changes) because the associated subsystem is switched
+off.
 
 ## How to use
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/HttpModule.java b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/HttpModule.java
new file mode 100644
index 0000000..c62a261
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/HttpModule.java
@@ -0,0 +1,42 @@
+// Copyright (C) 2019 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.healthcheck;
+
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.httpd.AllRequestFilter;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.inject.Inject;
+import com.google.inject.Scopes;
+import com.google.inject.servlet.ServletModule;
+import com.googlesource.gerrit.plugins.healthcheck.filter.HealthCheckStatusFilter;
+import org.eclipse.jgit.lib.Config;
+
+public class HttpModule extends ServletModule {
+  private boolean isSlave;
+
+  @Inject
+  public HttpModule(@GerritServerConfig Config gerritConfig) {
+    isSlave = gerritConfig.getBoolean("container", "slave", false);
+  }
+
+  @Override
+  protected void configureServlets() {
+    if (isSlave) {
+      DynamicSet.bind(binder(), AllRequestFilter.class)
+          .to(HealthCheckStatusFilter.class)
+          .in(Scopes.SINGLETON);
+    }
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/filter/HealthCheckStatusFilter.java b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/filter/HealthCheckStatusFilter.java
new file mode 100644
index 0000000..2699add
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/filter/HealthCheckStatusFilter.java
@@ -0,0 +1,83 @@
+// Copyright (C) 2019 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.healthcheck.filter;
+
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.httpd.AllRequestFilter;
+import com.google.gerrit.httpd.restapi.RestApiServlet;
+import com.google.gerrit.server.OutputFormat;
+import com.google.gerrit.server.config.ConfigResource;
+import com.google.gson.Gson;
+import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.healthcheck.api.HealthCheckStatusEndpoint;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Map;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class HealthCheckStatusFilter extends AllRequestFilter {
+  private final HealthCheckStatusEndpoint statusEndpoint;
+  private final Gson gson;
+
+  @Inject
+  public HealthCheckStatusFilter(HealthCheckStatusEndpoint statusEndpoint) {
+    this.statusEndpoint = statusEndpoint;
+    this.gson = OutputFormat.JSON.newGsonBuilder().create();
+  }
+
+  @Override
+  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+      throws IOException, ServletException {
+    if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
+      chain.doFilter(request, response);
+      return;
+    }
+
+    HttpServletResponse httpResponse = (HttpServletResponse) response;
+    HttpServletRequest httpRequest = (HttpServletRequest) request;
+
+    if (isStatusCheck(httpRequest)) {
+      doStatusCheck(httpResponse);
+    } else {
+      chain.doFilter(request, response);
+    }
+  }
+
+  private boolean isStatusCheck(HttpServletRequest httpServletRequest) {
+    return httpServletRequest.getRequestURI().equals("/config/server/healthcheck~status");
+  }
+
+  private void doStatusCheck(HttpServletResponse httpResponse) throws ServletException {
+    try {
+      Response<Map<String, Object>> healthStatus =
+          (Response<Map<String, Object>>) statusEndpoint.apply(new ConfigResource());
+      String healthStatusJson = gson.toJson(healthStatus);
+      if (healthStatus.statusCode() == HttpServletResponse.SC_OK) {
+        PrintWriter writer = httpResponse.getWriter();
+        writer.print(new String(RestApiServlet.JSON_MAGIC));
+        writer.print(healthStatusJson);
+      } else {
+        httpResponse.sendError(healthStatus.statusCode(), healthStatusJson);
+      }
+    } catch (Exception e) {
+      throw new ServletException(e);
+    }
+  }
+}