Split ChangeHookRunner along new interface lines

Create a new EventBroker class that can post and fire Events to
listeners. Make ChangeHookRunner use this new class to post events.

Change-Id: I308a61cc53f102a76265d1f318c7f42983d5599f
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
index 5108315..1edf4a1 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
@@ -20,6 +20,7 @@
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.MoreObjects;
 import com.google.gerrit.common.ChangeHookRunner;
+import com.google.gerrit.common.EventBroker;
 import com.google.gerrit.gpg.GpgModule;
 import com.google.gerrit.httpd.AllRequestFilter;
 import com.google.gerrit.httpd.GerritOptions;
@@ -340,6 +341,7 @@
 
     modules.add(new WorkQueue.Module());
     modules.add(new ChangeHookRunner.Module());
+    modules.add(new EventBroker.Module());
     modules.add(new ReceiveCommitsExecutorModule());
     modules.add(new DiffExecutorModule());
     modules.add(new MimeUtil2Module());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
index 400c1c0..af92b73 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
@@ -32,7 +32,6 @@
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.config.AnonymousCowardName;
@@ -44,7 +43,6 @@
 import com.google.gerrit.server.data.PatchSetAttribute;
 import com.google.gerrit.server.data.RefUpdateAttribute;
 import com.google.gerrit.server.events.ChangeAbandonedEvent;
-import com.google.gerrit.server.events.ChangeEvent;
 import com.google.gerrit.server.events.ChangeMergedEvent;
 import com.google.gerrit.server.events.ChangeRestoredEvent;
 import com.google.gerrit.server.events.CommentAddedEvent;
@@ -54,17 +52,12 @@
 import com.google.gerrit.server.events.MergeFailedEvent;
 import com.google.gerrit.server.events.PatchSetCreatedEvent;
 import com.google.gerrit.server.events.ProjectCreatedEvent;
-import com.google.gerrit.server.events.ProjectEvent;
-import com.google.gerrit.server.events.RefEvent;
 import com.google.gerrit.server.events.RefUpdatedEvent;
 import com.google.gerrit.server.events.ReviewerAddedEvent;
 import com.google.gerrit.server.events.TopicChangedEvent;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.WorkQueue;
-import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.project.ProjectControl;
-import com.google.gerrit.server.project.ProjectState;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -100,8 +93,8 @@
 
 /** Spawns local executables when a hook action occurs. */
 @Singleton
-public class ChangeHookRunner implements ChangeHooks, EventDispatcher,
-    LifecycleListener, NewProjectCreatedListener {
+public class ChangeHookRunner implements ChangeHooks, LifecycleListener,
+    NewProjectCreatedListener {
     /** A logger for this class. */
     private static final Logger log = LoggerFactory.getLogger(ChangeHookRunner.class);
 
@@ -110,7 +103,6 @@
       protected void configure() {
         bind(ChangeHookRunner.class);
         bind(ChangeHooks.class).to(ChangeHookRunner.class);
-        bind(EventDispatcher.class).to(ChangeHookRunner.class);
         DynamicSet.bind(binder(), NewProjectCreatedListener.class).to(ChangeHookRunner.class);
         listener().to(ChangeHookRunner.class);
       }
@@ -164,13 +156,6 @@
       }
     }
 
-    /** Listeners to receive changes as they happen (limited by visibility
-     *  of user). */
-    private final DynamicSet<UserScopedEventListener> listeners;
-
-    /** Listeners to receive all changes as they happen. */
-    private final DynamicSet<EventListener> unrestrictedListeners;
-
     /** Path of the new patchset hook. */
     private final Optional<Path> patchsetCreatedHook;
 
@@ -235,7 +220,7 @@
     /** Timeout value for synchronous hooks */
     private final int syncHookTimeout;
 
-    private final ChangeNotes.Factory notesFactory;
+    private final EventDispatcher dispatcher;
 
     /**
      * Create a new ChangeHookRunner.
@@ -255,9 +240,7 @@
       ProjectCache projectCache,
       AccountCache accountCache,
       EventFactory eventFactory,
-      DynamicSet<UserScopedEventListener> listeners,
-      DynamicSet<EventListener> unrestrictedListeners,
-      ChangeNotes.Factory notesFactory) {
+      EventDispatcher dispatcher) {
         this.anonymousCowardName = anonymousCowardName;
         this.repoManager = repoManager;
         this.hookQueue = queue.createQueue(1, "hook");
@@ -265,9 +248,7 @@
         this.accountCache = accountCache;
         this.eventFactory = eventFactory;
         this.sitePaths = sitePath;
-        this.listeners = listeners;
-        this.unrestrictedListeners = unrestrictedListeners;
-        this.notesFactory = notesFactory;
+        this.dispatcher = dispatcher;
 
         Path hooksPath;
         String hooksPathConfig = config.getString("hooks", null, "path");
@@ -353,7 +334,7 @@
       event.projectName = project.get();
       event.headName = headName;
 
-      fireEvent(project, event);
+      dispatcher.postEvent(project, event);
 
       if (!projectCreatedHook.isPresent()) {
         return;
@@ -378,7 +359,7 @@
       event.patchSet = patchSetAttributeSupplier(change, patchSet);
       event.uploader = accountAttributeSupplier(uploader);
 
-      fireEvent(change, event, db);
+      dispatcher.postEvent(change, event, db);
 
       if (!patchsetCreatedHook.isPresent()) {
         return;
@@ -415,7 +396,7 @@
       event.patchSet = patchSetAttributeSupplier(change, patchSet);
       event.uploader = accountAttributeSupplier(uploader);
 
-      fireEvent(change, event, db);
+      dispatcher.postEvent(change, event, db);
 
       if (!draftPublishedHook.isPresent()) {
         return;
@@ -467,7 +448,7 @@
             }
           });
 
-      fireEvent(change, event, db);
+      dispatcher.postEvent(change, event, db);
 
       if (!commentAddedHook.isPresent()) {
         return;
@@ -511,7 +492,7 @@
       event.patchSet = patchSetAttributeSupplier(change, patchSet);
       event.newRev = mergeResultRev;
 
-      fireEvent(change, event, db);
+      dispatcher.postEvent(change, event, db);
 
       if (!changeMergedHook.isPresent()) {
         return;
@@ -546,7 +527,7 @@
       event.patchSet = patchSetAttributeSupplier(change, patchSet);
       event.reason = reason;
 
-      fireEvent(change, event, db);
+      dispatcher.postEvent(change, event, db);
 
       if (!mergeFailedHook.isPresent()) {
         return;
@@ -581,7 +562,7 @@
       event.patchSet = patchSetAttributeSupplier(change, patchSet);
       event.reason = reason;
 
-      fireEvent(change, event, db);
+      dispatcher.postEvent(change, event, db);
 
       if (!changeAbandonedHook.isPresent()) {
         return;
@@ -616,7 +597,7 @@
       event.patchSet = patchSetAttributeSupplier(change, patchSet);
       event.reason = reason;
 
-      fireEvent(change, event, db);
+      dispatcher.postEvent(change, event, db);
 
       if (!changeRestoredHook.isPresent()) {
         return;
@@ -662,7 +643,7 @@
             }
           });
 
-      fireEvent(refName, event);
+      dispatcher.postEvent(refName, event);
 
       if (!refUpdatedHook.isPresent()) {
         return;
@@ -691,7 +672,7 @@
       event.patchSet = patchSetAttributeSupplier(change, patchSet);
       event.reviewer = accountAttributeSupplier(account);
 
-      fireEvent(change, event, db);
+      dispatcher.postEvent(change, event, db);
 
       if (!reviewerAddedHook.isPresent()) {
         return;
@@ -721,7 +702,7 @@
       event.changer = accountAttributeSupplier(account);
       event.oldTopic = oldTopic;
 
-      fireEvent(change, event, db);
+      dispatcher.postEvent(change, event, db);
 
       if (!topicChangedHook.isPresent()) {
         return;
@@ -762,7 +743,7 @@
       event.added = hashtagArray(added);
       event.removed = hashtagArray(removed);
 
-      fireEvent(change, event, db);
+      dispatcher.postEvent(change, event, db);
 
       if (!hashtagsChangedHook.isPresent()) {
         return;
@@ -810,28 +791,6 @@
       }
     }
 
-    @Override
-    public void postEvent(Change change, ChangeEvent event, ReviewDb db)
-        throws OrmException {
-      fireEvent(change, event, db);
-    }
-
-    @Override
-    public void postEvent(Branch.NameKey branchName, RefEvent event) {
-      fireEvent(branchName, event);
-    }
-
-    @Override
-    public void postEvent(Project.NameKey projectName, ProjectEvent event) {
-      fireEvent(projectName, event);
-    }
-
-    @Override
-    public void postEvent(com.google.gerrit.server.events.Event event,
-        ReviewDb db) throws OrmException {
-      fireEvent(event, db);
-    }
-
     private Supplier<AccountState> getAccountSupplier(
         final Account.Id account) {
       return Suppliers.memoize(
@@ -894,100 +853,6 @@
           });
     }
 
-    private void fireEventForUnrestrictedListeners(com.google.gerrit.server.events.Event event) {
-      for (EventListener listener : unrestrictedListeners) {
-        listener.onEvent(event);
-      }
-    }
-
-    private void fireEvent(Change change, ChangeEvent event, ReviewDb db)
-        throws OrmException {
-      for (UserScopedEventListener listener : listeners) {
-        if (isVisibleTo(change, listener.getUser(), db)) {
-          listener.onEvent(event);
-        }
-      }
-
-      fireEventForUnrestrictedListeners( event );
-    }
-
-    private void fireEvent(Project.NameKey project, ProjectEvent event) {
-      for (UserScopedEventListener listener : listeners) {
-        if (isVisibleTo(project, listener.getUser())) {
-          listener.onEvent(event);
-        }
-      }
-
-      fireEventForUnrestrictedListeners(event);
-    }
-
-    private void fireEvent(Branch.NameKey branchName, RefEvent event) {
-      for (UserScopedEventListener listener : listeners) {
-        if (isVisibleTo(branchName, listener.getUser())) {
-          listener.onEvent(event);
-        }
-      }
-
-      fireEventForUnrestrictedListeners(event);
-    }
-
-    private void fireEvent(com.google.gerrit.server.events.Event event,
-        ReviewDb db) throws OrmException {
-      for (UserScopedEventListener listener : listeners) {
-        if (isVisibleTo(event, listener.getUser(), db)) {
-          listener.onEvent(event);
-        }
-      }
-
-      fireEventForUnrestrictedListeners(event);
-    }
-
-    private boolean isVisibleTo(Project.NameKey project, CurrentUser user) {
-      ProjectState pe = projectCache.get(project);
-      if (pe == null) {
-        return false;
-      }
-      return pe.controlFor(user).isVisible();
-    }
-
-    private boolean isVisibleTo(Change change, CurrentUser user, ReviewDb db)
-        throws OrmException {
-      if (change == null) {
-        return false;
-      }
-      ProjectState pe = projectCache.get(change.getProject());
-      if (pe == null) {
-        return false;
-      }
-      ProjectControl pc = pe.controlFor(user);
-      return pc.controlFor(db, change).isVisible(db);
-    }
-
-    private boolean isVisibleTo(Branch.NameKey branchName, CurrentUser user) {
-      ProjectState pe = projectCache.get(branchName.getParentKey());
-      if (pe == null) {
-        return false;
-      }
-      ProjectControl pc = pe.controlFor(user);
-      return pc.controlForRef(branchName).isVisible();
-    }
-
-    private boolean isVisibleTo(com.google.gerrit.server.events.Event event,
-        CurrentUser user, ReviewDb db) throws OrmException {
-      if (event instanceof RefEvent) {
-        RefEvent refEvent = (RefEvent) event;
-        String ref = refEvent.getRefName();
-        if (PatchSet.isChangeRef(ref)) {
-          Change.Id cid = PatchSet.Id.fromRef(ref).getParentKey();
-          Change change = notesFactory
-              .create(db, refEvent.getProjectNameKey(), cid).getChange();
-          return isVisibleTo(change, user, db);
-        }
-        return isVisibleTo(refEvent.getBranchNameKey(), user);
-      }
-      return true;
-    }
-
     /**
      * Create an ApprovalAttribute for the given approval suitable for serialization to JSON.
      * @param approval
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/EventBroker.java b/gerrit-server/src/main/java/com/google/gerrit/common/EventBroker.java
new file mode 100644
index 0000000..01317e2
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/EventBroker.java
@@ -0,0 +1,181 @@
+// Copyright (C) 2016 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.google.gerrit.common;
+
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.events.ChangeEvent;
+import com.google.gerrit.server.events.Event;
+import com.google.gerrit.server.events.ProjectEvent;
+import com.google.gerrit.server.events.RefEvent;
+import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+/** Distributes Events to listeners if they are allowed to see them */
+@Singleton
+public class EventBroker implements EventDispatcher {
+
+  public static class Module extends LifecycleModule {
+    @Override
+    protected void configure() {
+      bind(EventDispatcher.class).to(EventBroker.class);
+    }
+  }
+
+  /**
+   * Listeners to receive changes as they happen (limited by visibility of
+   * user).
+   */
+  private final DynamicSet<UserScopedEventListener> listeners;
+
+  /** Listeners to receive all changes as they happen. */
+  private final DynamicSet<EventListener> unrestrictedListeners;
+
+  private final ProjectCache projectCache;
+
+  private final ChangeNotes.Factory notesFactory;
+
+  @Inject
+  public EventBroker(DynamicSet<UserScopedEventListener> listeners,
+      DynamicSet<EventListener> unrestrictedListeners,
+      ProjectCache projectCache,
+      ChangeNotes.Factory notesFactory) {
+    this.listeners = listeners;
+    this.unrestrictedListeners = unrestrictedListeners;
+    this.projectCache = projectCache;
+    this.notesFactory = notesFactory;
+  }
+
+  @Override
+  public void postEvent(Change change, ChangeEvent event, ReviewDb db)
+      throws OrmException {
+    fireEvent(change, event, db);
+  }
+
+  @Override
+  public void postEvent(Branch.NameKey branchName, RefEvent event) {
+    fireEvent(branchName, event);
+  }
+
+  @Override
+  public void postEvent(Project.NameKey projectName, ProjectEvent event) {
+    fireEvent(projectName, event);
+  }
+
+  @Override
+  public void postEvent(Event event, ReviewDb db) throws OrmException {
+    fireEvent(event, db);
+  }
+
+  private void fireEventForUnrestrictedListeners(Event event) {
+    for (EventListener listener : unrestrictedListeners) {
+      listener.onEvent(event);
+    }
+  }
+
+  protected void fireEvent(Change change, ChangeEvent event, ReviewDb db)
+      throws OrmException {
+    for (UserScopedEventListener listener : listeners) {
+      if (isVisibleTo(change, listener.getUser(), db)) {
+        listener.onEvent(event);
+      }
+    }
+    fireEventForUnrestrictedListeners(event);
+  }
+
+  protected void fireEvent(Project.NameKey project, ProjectEvent event) {
+    for (UserScopedEventListener listener : listeners) {
+      if (isVisibleTo(project, listener.getUser())) {
+        listener.onEvent(event);
+      }
+    }
+    fireEventForUnrestrictedListeners(event);
+  }
+
+  protected void fireEvent(Branch.NameKey branchName, RefEvent event) {
+    for (UserScopedEventListener listener : listeners) {
+      if (isVisibleTo(branchName, listener.getUser())) {
+        listener.onEvent(event);
+      }
+    }
+    fireEventForUnrestrictedListeners(event);
+  }
+
+  protected void fireEvent(Event event, ReviewDb db) throws OrmException {
+    for (UserScopedEventListener listener : listeners) {
+      if (isVisibleTo(event, listener.getUser(), db)) {
+        listener.onEvent(event);
+      }
+    }
+    fireEventForUnrestrictedListeners(event);
+  }
+
+  protected boolean isVisibleTo(Project.NameKey project, CurrentUser user) {
+    ProjectState pe = projectCache.get(project);
+    if (pe == null) {
+      return false;
+    }
+    return pe.controlFor(user).isVisible();
+  }
+
+  protected boolean isVisibleTo(Change change, CurrentUser user, ReviewDb db)
+      throws OrmException {
+    if (change == null) {
+      return false;
+    }
+    ProjectState pe = projectCache.get(change.getProject());
+    if (pe == null) {
+      return false;
+    }
+    ProjectControl pc = pe.controlFor(user);
+    return pc.controlFor(db, change).isVisible(db);
+  }
+
+  protected boolean isVisibleTo(Branch.NameKey branchName, CurrentUser user) {
+    ProjectState pe = projectCache.get(branchName.getParentKey());
+    if (pe == null) {
+      return false;
+    }
+    ProjectControl pc = pe.controlFor(user);
+    return pc.controlForRef(branchName).isVisible();
+  }
+
+  protected boolean isVisibleTo(Event event, CurrentUser user, ReviewDb db)
+      throws OrmException {
+    if (event instanceof RefEvent) {
+      RefEvent refEvent = (RefEvent) event;
+      String ref = refEvent.getRefName();
+      if (PatchSet.isChangeRef(ref)) {
+        Change.Id cid = PatchSet.Id.fromRef(ref).getParentKey();
+        Change change = notesFactory
+            .create(db, refEvent.getProjectNameKey(), cid).getChange();
+        return isVisibleTo(change, user, db);
+      }
+      return isVisibleTo(refEvent.getBranchNameKey(), user);
+    }
+    return true;
+  }
+}
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
index cf3e76c..20c3c2a 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
@@ -19,6 +19,7 @@
 
 import com.google.common.base.Splitter;
 import com.google.gerrit.common.ChangeHookRunner;
+import com.google.gerrit.common.EventBroker;
 import com.google.gerrit.gpg.GpgModule;
 import com.google.gerrit.httpd.auth.oauth.OAuthModule;
 import com.google.gerrit.httpd.auth.openid.OpenIdModule;
@@ -294,6 +295,7 @@
   private Injector createSysInjector() {
     final List<Module> modules = new ArrayList<>();
     modules.add(new DropWizardMetricMaker.RestModule());
+    modules.add(new EventBroker.Module());
     modules.add(new ChangeHookRunner.Module());
     modules.add(new ReceiveCommitsExecutorModule());
     modules.add(new DiffExecutorModule());