index: decouple servlet hierarchy from handler hierarchy

Previously AbstractIndexRestApiServlet<T> held a
ForwardedIndexingHandler<T> reference and routed all indexing calls
through the base class. However, all concrete servlet classes inject
concrete indexing handlers and there is no need to route the indexing
calls via the base class. There is also no need that the servlets know
about and resemble the hierarchy of the indexing handler classes.

AbstractIndexRestApiServlet is no longer generic and no longer holds a
handler reference. Instead it exposes a process() helper that accepts an
IndexingOperation lambda. Thus, it is now purely about HTTP mechanics
(read body, set status, handle errors).

Change-Id: I114fee3086e2233984815a57ece47635ebf01933
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractIndexRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractIndexRestApiServlet.java
index 343daab..851aa49 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractIndexRestApiServlet.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractIndexRestApiServlet.java
@@ -19,25 +19,20 @@
 import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType;
-import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler;
-import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation;
-import com.ericsson.gerrit.plugins.highavailability.forwarder.IndexEvent;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetricsRegistry;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.restapi.NotImplementedException;
-import com.google.gson.Gson;
 import java.io.IOException;
-import java.util.Optional;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-public abstract class AbstractIndexRestApiServlet<T> extends AbstractRestApiServlet {
+public abstract class AbstractIndexRestApiServlet extends AbstractRestApiServlet {
   private static final long serialVersionUID = -1L;
 
-  private final ForwardedIndexingHandler<T> forwardedIndexingHandler;
-  private final IndexName indexName;
-  private final boolean allowDelete;
-  private final Gson gson;
+  @FunctionalInterface
+  interface IndexingOperation {
+    void execute(String body) throws IOException;
+  }
 
   public enum IndexName {
     CHANGE,
@@ -51,53 +46,22 @@
     }
   }
 
-  abstract T parse(String id);
+  private final IndexName indexName;
 
   AbstractIndexRestApiServlet(
-      ForwardedIndexingHandler<T> forwardedIndexingHandler,
       IndexName indexName,
-      Gson gson,
       ProcessorMetricsRegistry metricsRegistry,
       EventType postEventType,
       @Nullable EventType deleteEventType) {
     super(metricsRegistry, postEventType, deleteEventType);
-    this.forwardedIndexingHandler = forwardedIndexingHandler;
     this.indexName = indexName;
-    this.gson = gson;
-    this.allowDelete = deleteEventType != null;
   }
 
-  @Override
-  protected boolean processPostRequest(HttpServletRequest req, HttpServletResponse rsp) {
-    return process(req, rsp, Operation.INDEX);
-  }
-
-  @Override
-  protected boolean processDeleteRequest(HttpServletRequest req, HttpServletResponse rsp) {
-    if (!allowDelete) {
-      sendError(
-          rsp, SC_METHOD_NOT_ALLOWED, String.format("cannot delete %s from index", indexName));
-      throw new NotImplementedException("Deletions not allowed for " + indexName);
-    }
-    return process(req, rsp, Operation.DELETE);
-  }
-
-  /**
-   * Process the request by parsing the ID from the URL and invoking the indexing handler.
-   *
-   * @param req the HTTP request
-   * @param rsp the HTTP response
-   * @param operation the indexing operation to perform (INDEX or DELETE)
-   * @return true if the operation was successful, false otherwise
-   */
-  private boolean process(HttpServletRequest req, HttpServletResponse rsp, Operation operation) {
-    String path = req.getRequestURI();
-    T id = parse(path.substring(path.lastIndexOf('/') + 1));
-
+  protected boolean process(HttpServletRequest req, HttpServletResponse rsp, IndexingOperation op) {
     try {
       String body = readRequestBody(req);
       ForwardedMessageLogger.log(req, body);
-      forwardedIndexingHandler.index(id, operation, parseBody(body));
+      op.execute(body);
       rsp.setStatus(SC_NO_CONTENT);
       return true;
     } catch (IOException e) {
@@ -107,7 +71,14 @@
     }
   }
 
-  protected Optional<IndexEvent> parseBody(String body) {
-    return Optional.ofNullable(gson.fromJson(body, IndexEvent.class));
+  @Override
+  protected boolean processDeleteRequest(HttpServletRequest req, HttpServletResponse rsp) {
+    sendError(rsp, SC_METHOD_NOT_ALLOWED, String.format("cannot delete %s from index", indexName));
+    throw new NotImplementedException("Deletions not allowed for " + indexName);
+  }
+
+  protected static String extractRawId(HttpServletRequest req) {
+    String path = req.getRequestURI();
+    return path.substring(path.lastIndexOf('/') + 1);
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexAccountRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexAccountRestApiServlet.java
index 1adfbbe..d4d520a 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexAccountRestApiServlet.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexAccountRestApiServlet.java
@@ -16,26 +16,31 @@
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexAccountHandler;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetricsRegistry;
 import com.google.gerrit.entities.Account;
-import com.google.gson.Gson;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import java.util.Optional;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 
 @Singleton
-class IndexAccountRestApiServlet extends AbstractIndexRestApiServlet<Account.Id> {
+class IndexAccountRestApiServlet extends AbstractIndexRestApiServlet {
   private static final long serialVersionUID = -1L;
 
+  private final ForwardedIndexAccountHandler handler;
+
   @Inject
   IndexAccountRestApiServlet(
-      ForwardedIndexAccountHandler handler,
-      @RestGson Gson gson,
-      ProcessorMetricsRegistry metricRegistry) {
-    super(handler, IndexName.ACCOUNT, gson, metricRegistry, EventType.INDEX_ACCOUNT_UPDATE, null);
+      ForwardedIndexAccountHandler handler, ProcessorMetricsRegistry metricRegistry) {
+    super(IndexName.ACCOUNT, metricRegistry, EventType.INDEX_ACCOUNT_UPDATE, null);
+    this.handler = handler;
   }
 
   @Override
-  Account.Id parse(String id) {
-    return Account.id(Integer.parseInt(id));
+  protected boolean processPostRequest(HttpServletRequest req, HttpServletResponse rsp) {
+    Account.Id id = Account.id(Integer.parseInt(extractRawId(req)));
+    return process(req, rsp, _ -> handler.index(id, Operation.INDEX, Optional.empty()));
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexBatchChangeRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexBatchChangeRestApiServlet.java
index 7c593be..6c25742 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexBatchChangeRestApiServlet.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexBatchChangeRestApiServlet.java
@@ -16,27 +16,41 @@
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexBatchChangeHandler;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.IndexEvent;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetricsRegistry;
 import com.google.gerrit.extensions.restapi.Url;
 import com.google.gson.Gson;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import java.util.Optional;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 
 @Singleton
-class IndexBatchChangeRestApiServlet extends AbstractIndexRestApiServlet<String> {
+class IndexBatchChangeRestApiServlet extends AbstractIndexRestApiServlet {
   private static final long serialVersionUID = -1L;
 
+  private final ForwardedIndexBatchChangeHandler handler;
+  private final Gson gson;
+
   @Inject
   IndexBatchChangeRestApiServlet(
       ForwardedIndexBatchChangeHandler handler,
       @RestGson Gson gson,
       ProcessorMetricsRegistry metricRegistry) {
-    super(
-        handler, IndexName.CHANGE, gson, metricRegistry, EventType.INDEX_CHANGE_UPDATE_BATCH, null);
+    super(IndexName.CHANGE, metricRegistry, EventType.INDEX_CHANGE_UPDATE_BATCH, null);
+    this.handler = handler;
+    this.gson = gson;
   }
 
   @Override
-  String parse(String id) {
-    return Url.decode(id);
+  protected boolean processPostRequest(HttpServletRequest req, HttpServletResponse rsp) {
+    String id = Url.decode(extractRawId(req));
+    return process(req, rsp, body -> handler.index(id, Operation.INDEX, parseBody(body)));
+  }
+
+  private Optional<IndexEvent> parseBody(String body) {
+    return Optional.ofNullable(gson.fromJson(body, IndexEvent.class));
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexChangeRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexChangeRestApiServlet.java
index f63ceeb..1423304 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexChangeRestApiServlet.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexChangeRestApiServlet.java
@@ -16,32 +16,51 @@
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexChangeHandler;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.IndexEvent;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetricsRegistry;
 import com.google.gerrit.extensions.restapi.Url;
 import com.google.gson.Gson;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import java.util.Optional;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 
 @Singleton
-class IndexChangeRestApiServlet extends AbstractIndexRestApiServlet<String> {
+class IndexChangeRestApiServlet extends AbstractIndexRestApiServlet {
   private static final long serialVersionUID = -1L;
 
+  private final ForwardedIndexChangeHandler handler;
+  private final Gson gson;
+
   @Inject
   IndexChangeRestApiServlet(
       ForwardedIndexChangeHandler handler,
       @RestGson Gson gson,
       ProcessorMetricsRegistry metricRegistry) {
     super(
-        handler,
         IndexName.CHANGE,
-        gson,
         metricRegistry,
         EventType.INDEX_CHANGE_UPDATE,
         EventType.INDEX_CHANGE_DELETION);
+    this.handler = handler;
+    this.gson = gson;
   }
 
   @Override
-  String parse(String id) {
-    return Url.decode(id);
+  protected boolean processPostRequest(HttpServletRequest req, HttpServletResponse rsp) {
+    String id = Url.decode(extractRawId(req));
+    return process(req, rsp, body -> handler.index(id, Operation.INDEX, parseBody(body)));
+  }
+
+  @Override
+  protected boolean processDeleteRequest(HttpServletRequest req, HttpServletResponse rsp) {
+    String id = Url.decode(extractRawId(req));
+    return process(req, rsp, body -> handler.index(id, Operation.DELETE, parseBody(body)));
+  }
+
+  private Optional<IndexEvent> parseBody(String body) {
+    return Optional.ofNullable(gson.fromJson(body, IndexEvent.class));
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexGroupRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexGroupRestApiServlet.java
index 6328cd7..7f5e539 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexGroupRestApiServlet.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexGroupRestApiServlet.java
@@ -16,26 +16,31 @@
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexGroupHandler;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetricsRegistry;
 import com.google.gerrit.entities.AccountGroup;
-import com.google.gson.Gson;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import java.util.Optional;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 
 @Singleton
-class IndexGroupRestApiServlet extends AbstractIndexRestApiServlet<AccountGroup.UUID> {
+class IndexGroupRestApiServlet extends AbstractIndexRestApiServlet {
   private static final long serialVersionUID = -1L;
 
+  private final ForwardedIndexGroupHandler handler;
+
   @Inject
   IndexGroupRestApiServlet(
-      ForwardedIndexGroupHandler handler,
-      @RestGson Gson gson,
-      ProcessorMetricsRegistry metricRegistry) {
-    super(handler, IndexName.GROUP, gson, metricRegistry, EventType.INDEX_GROUP_UPDATE, null);
+      ForwardedIndexGroupHandler handler, ProcessorMetricsRegistry metricRegistry) {
+    super(IndexName.GROUP, metricRegistry, EventType.INDEX_GROUP_UPDATE, null);
+    this.handler = handler;
   }
 
   @Override
-  AccountGroup.UUID parse(String id) {
-    return AccountGroup.uuid(id);
+  protected boolean processPostRequest(HttpServletRequest req, HttpServletResponse rsp) {
+    AccountGroup.UUID uuid = AccountGroup.uuid(extractRawId(req));
+    return process(req, rsp, _ -> handler.index(uuid, Operation.INDEX, Optional.empty()));
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexProjectRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexProjectRestApiServlet.java
index bf3a3f0..17aa5c3 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexProjectRestApiServlet.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexProjectRestApiServlet.java
@@ -16,27 +16,32 @@
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexProjectHandler;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetricsRegistry;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.restapi.Url;
-import com.google.gson.Gson;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import java.util.Optional;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 
 @Singleton
-class IndexProjectRestApiServlet extends AbstractIndexRestApiServlet<Project.NameKey> {
+class IndexProjectRestApiServlet extends AbstractIndexRestApiServlet {
   private static final long serialVersionUID = -1L;
 
+  private final ForwardedIndexProjectHandler handler;
+
   @Inject
   IndexProjectRestApiServlet(
-      ForwardedIndexProjectHandler handler,
-      @RestGson Gson gson,
-      ProcessorMetricsRegistry metricRegistry) {
-    super(handler, IndexName.PROJECT, gson, metricRegistry, EventType.INDEX_PROJECT_UPDATE, null);
+      ForwardedIndexProjectHandler handler, ProcessorMetricsRegistry metricRegistry) {
+    super(IndexName.PROJECT, metricRegistry, EventType.INDEX_PROJECT_UPDATE, null);
+    this.handler = handler;
   }
 
   @Override
-  Project.NameKey parse(String projectName) {
-    return Project.nameKey(Url.decode(projectName));
+  protected boolean processPostRequest(HttpServletRequest req, HttpServletResponse rsp) {
+    Project.NameKey projectName = Project.nameKey(Url.decode(extractRawId(req)));
+    return process(req, rsp, _ -> handler.index(projectName, Operation.INDEX, Optional.empty()));
   }
 }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexAccountRestApiServletTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexAccountRestApiServletTest.java
index 49ce5f8..454f000 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexAccountRestApiServletTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexAccountRestApiServletTest.java
@@ -29,7 +29,6 @@
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetrics;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetricsRegistry;
 import com.google.gerrit.entities.Account;
-import com.google.gson.Gson;
 import java.io.IOException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -56,7 +55,7 @@
   @Before
   public void setUpMocks() {
     when(metricsRegistryMock.get(any())).thenReturn(metrics);
-    servlet = new IndexAccountRestApiServlet(handlerMock, new Gson(), metricsRegistryMock);
+    servlet = new IndexAccountRestApiServlet(handlerMock, metricsRegistryMock);
     id = Account.id(ACCOUNT_NUMBER);
     when(requestMock.getRequestURI())
         .thenReturn("http://gerrit.com/index/account/" + ACCOUNT_NUMBER);
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexGroupRestApiServletTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexGroupRestApiServletTest.java
index 0e87bf7..2278b17 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexGroupRestApiServletTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexGroupRestApiServletTest.java
@@ -29,7 +29,6 @@
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetrics;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetricsRegistry;
 import com.google.gerrit.entities.AccountGroup;
-import com.google.gson.Gson;
 import java.io.IOException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -56,7 +55,7 @@
   @Before
   public void setUpMocks() {
     when(metricsRegistryMock.get(any())).thenReturn(metrics);
-    servlet = new IndexGroupRestApiServlet(handlerMock, new Gson(), metricsRegistryMock);
+    servlet = new IndexGroupRestApiServlet(handlerMock, metricsRegistryMock);
     uuid = AccountGroup.uuid(UUID);
     when(requestMock.getRequestURI()).thenReturn("http://gerrit.com/index/group/" + UUID);
   }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexProjectRestApiServletTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexProjectRestApiServletTest.java
index d74a43f..53670c8 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexProjectRestApiServletTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexProjectRestApiServletTest.java
@@ -30,7 +30,6 @@
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetricsRegistry;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.restapi.Url;
-import com.google.gson.Gson;
 import java.io.IOException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -57,7 +56,7 @@
   @Before
   public void setUpMocks() {
     when(metricsRegistryMock.get(any())).thenReturn(metrics);
-    servlet = new IndexProjectRestApiServlet(handlerMock, new Gson(), metricsRegistryMock);
+    servlet = new IndexProjectRestApiServlet(handlerMock, metricsRegistryMock);
     nameKey = Project.nameKey(PROJECT_NAME);
     when(requestMock.getRequestURI())
         .thenReturn("http://gerrit.com/index/project/" + Url.encode(nameKey.get()));