Merge "Add hashtags to events and query results"
diff --git a/Documentation/json.txt b/Documentation/json.txt
index 3b8a8cb..dc82ad1 100644
--- a/Documentation/json.txt
+++ b/Documentation/json.txt
@@ -29,6 +29,8 @@
 commitMessage:: The full commit message for the change's current patch
 set.
 
+hashtags:: List of hashtags associated with this change.
+
 createdOn:: Time in seconds since the UNIX epoch when this change
 was created.
 
diff --git a/java/com/google/gerrit/server/data/ChangeAttribute.java b/java/com/google/gerrit/server/data/ChangeAttribute.java
index ec76f50..fde5922 100644
--- a/java/com/google/gerrit/server/data/ChangeAttribute.java
+++ b/java/com/google/gerrit/server/data/ChangeAttribute.java
@@ -30,6 +30,7 @@
   public AccountAttribute assignee;
   public String url;
   public String commitMessage;
+  public List<String> hashtags;
 
   public Long createdOn;
   public Long lastUpdated;
diff --git a/java/com/google/gerrit/server/events/EventFactory.java b/java/com/google/gerrit/server/events/EventFactory.java
index f675dd5..75a08bd 100644
--- a/java/com/google/gerrit/server/events/EventFactory.java
+++ b/java/com/google/gerrit/server/events/EventFactory.java
@@ -130,9 +130,9 @@
    * @param change
    * @return object suitable for serialization to JSON
    */
-  public ChangeAttribute asChangeAttribute(Change change) {
+  public ChangeAttribute asChangeAttribute(Change change, ChangeNotes notes) {
     try (ReviewDb db = schema.open()) {
-      return asChangeAttribute(db, change);
+      return asChangeAttribute(db, change, notes);
     } catch (OrmException e) {
       logger.atSevere().withCause(e).log("Cannot open database connection");
       return new ChangeAttribute();
@@ -171,6 +171,24 @@
   }
 
   /**
+   * Create a ChangeAttribute for the given change suitable for serialization to JSON.
+   *
+   * @param db Review database
+   * @param change
+   * @param notes
+   * @return object suitable for serialization to JSON
+   */
+  public ChangeAttribute asChangeAttribute(ReviewDb db, Change change, ChangeNotes notes)
+      throws OrmException {
+    ChangeAttribute a = asChangeAttribute(db, change);
+    Set<String> hashtags = notes.load().getHashtags();
+    if (!hashtags.isEmpty()) {
+      a.hashtags = new ArrayList<String>(hashtags.size());
+      a.hashtags.addAll(hashtags);
+    }
+    return a;
+  }
+  /**
    * Create a RefUpdateAttribute for the given old ObjectId, new ObjectId, and branch that is
    * suitable for serialization to JSON.
    *
diff --git a/java/com/google/gerrit/server/events/StreamEventsApiListener.java b/java/com/google/gerrit/server/events/StreamEventsApiListener.java
index 9592238..7a78055 100644
--- a/java/com/google/gerrit/server/events/StreamEventsApiListener.java
+++ b/java/com/google/gerrit/server/events/StreamEventsApiListener.java
@@ -156,12 +156,12 @@
     return psUtil.get(db.get(), notes, PatchSet.Id.fromRef(info.ref));
   }
 
-  private Supplier<ChangeAttribute> changeAttributeSupplier(Change change) {
+  private Supplier<ChangeAttribute> changeAttributeSupplier(Change change, ChangeNotes notes) {
     return Suppliers.memoize(
         new Supplier<ChangeAttribute>() {
           @Override
           public ChangeAttribute get() {
-            return eventFactory.asChangeAttribute(change);
+            return eventFactory.asChangeAttribute(change, notes);
           }
         });
   }
@@ -257,10 +257,11 @@
   @Override
   public void onAssigneeChanged(AssigneeChangedListener.Event ev) {
     try {
-      Change change = getChange(ev.getChange());
+      ChangeNotes notes = getNotes(ev.getChange());
+      Change change = notes.getChange();
       AssigneeChangedEvent event = new AssigneeChangedEvent(change);
 
-      event.change = changeAttributeSupplier(change);
+      event.change = changeAttributeSupplier(change, notes);
       event.changer = accountAttributeSupplier(ev.getWho());
       event.oldAssignee = accountAttributeSupplier(ev.getOldAssignee());
 
@@ -273,10 +274,11 @@
   @Override
   public void onTopicEdited(TopicEditedListener.Event ev) {
     try {
-      Change change = getChange(ev.getChange());
+      ChangeNotes notes = getNotes(ev.getChange());
+      Change change = notes.getChange();
       TopicChangedEvent event = new TopicChangedEvent(change);
 
-      event.change = changeAttributeSupplier(change);
+      event.change = changeAttributeSupplier(change, notes);
       event.changer = accountAttributeSupplier(ev.getWho());
       event.oldTopic = ev.getOldTopic();
 
@@ -294,7 +296,7 @@
       PatchSet patchSet = getPatchSet(notes, ev.getRevision());
       PatchSetCreatedEvent event = new PatchSetCreatedEvent(change);
 
-      event.change = changeAttributeSupplier(change);
+      event.change = changeAttributeSupplier(change, notes);
       event.patchSet = patchSetAttributeSupplier(change, patchSet);
       event.uploader = accountAttributeSupplier(ev.getWho());
 
@@ -310,7 +312,7 @@
       ChangeNotes notes = getNotes(ev.getChange());
       Change change = notes.getChange();
       ReviewerDeletedEvent event = new ReviewerDeletedEvent(change);
-      event.change = changeAttributeSupplier(change);
+      event.change = changeAttributeSupplier(change, notes);
       event.patchSet = patchSetAttributeSupplier(change, psUtil.current(db.get(), notes));
       event.reviewer = accountAttributeSupplier(ev.getReviewer());
       event.remover = accountAttributeSupplier(ev.getWho());
@@ -331,7 +333,7 @@
       Change change = notes.getChange();
       ReviewerAddedEvent event = new ReviewerAddedEvent(change);
 
-      event.change = changeAttributeSupplier(change);
+      event.change = changeAttributeSupplier(change, notes);
       event.patchSet = patchSetAttributeSupplier(change, psUtil.current(db.get(), notes));
       for (AccountInfo reviewer : ev.getReviewers()) {
         event.reviewer = accountAttributeSupplier(reviewer);
@@ -354,10 +356,11 @@
   @Override
   public void onHashtagsEdited(HashtagsEditedListener.Event ev) {
     try {
-      Change change = getChange(ev.getChange());
+      ChangeNotes notes = getNotes(ev.getChange());
+      Change change = notes.getChange();
       HashtagsChangedEvent event = new HashtagsChangedEvent(change);
 
-      event.change = changeAttributeSupplier(change);
+      event.change = changeAttributeSupplier(change, notes);
       event.editor = accountAttributeSupplier(ev.getWho());
       event.hashtags = hashtagArray(ev.getHashtags());
       event.added = hashtagArray(ev.getAddedHashtags());
@@ -402,7 +405,7 @@
       PatchSet ps = getPatchSet(notes, ev.getRevision());
       CommentAddedEvent event = new CommentAddedEvent(change);
 
-      event.change = changeAttributeSupplier(change);
+      event.change = changeAttributeSupplier(change, notes);
       event.author = accountAttributeSupplier(ev.getWho());
       event.patchSet = patchSetAttributeSupplier(change, ps);
       event.comment = ev.getComment();
@@ -421,7 +424,7 @@
       Change change = notes.getChange();
       ChangeRestoredEvent event = new ChangeRestoredEvent(change);
 
-      event.change = changeAttributeSupplier(change);
+      event.change = changeAttributeSupplier(change, notes);
       event.restorer = accountAttributeSupplier(ev.getWho());
       event.patchSet = patchSetAttributeSupplier(change, psUtil.current(db.get(), notes));
       event.reason = ev.getReason();
@@ -439,7 +442,7 @@
       Change change = notes.getChange();
       ChangeMergedEvent event = new ChangeMergedEvent(change);
 
-      event.change = changeAttributeSupplier(change);
+      event.change = changeAttributeSupplier(change, notes);
       event.submitter = accountAttributeSupplier(ev.getWho());
       event.patchSet = patchSetAttributeSupplier(change, psUtil.current(db.get(), notes));
       event.newRev = ev.getNewRevisionId();
@@ -457,7 +460,7 @@
       Change change = notes.getChange();
       ChangeAbandonedEvent event = new ChangeAbandonedEvent(change);
 
-      event.change = changeAttributeSupplier(change);
+      event.change = changeAttributeSupplier(change, notes);
       event.abandoner = accountAttributeSupplier(ev.getWho());
       event.patchSet = patchSetAttributeSupplier(change, psUtil.current(db.get(), notes));
       event.reason = ev.getReason();
@@ -471,10 +474,11 @@
   @Override
   public void onWorkInProgressStateChanged(WorkInProgressStateChangedListener.Event ev) {
     try {
-      Change change = getChange(ev.getChange());
+      ChangeNotes notes = getNotes(ev.getChange());
+      Change change = notes.getChange();
       WorkInProgressStateChangedEvent event = new WorkInProgressStateChangedEvent(change);
 
-      event.change = changeAttributeSupplier(change);
+      event.change = changeAttributeSupplier(change, notes);
       event.changer = accountAttributeSupplier(ev.getWho());
 
       dispatcher.get().postEvent(change, event);
@@ -486,10 +490,11 @@
   @Override
   public void onPrivateStateChanged(PrivateStateChangedListener.Event ev) {
     try {
-      Change change = getChange(ev.getChange());
+      ChangeNotes notes = getNotes(ev.getChange());
+      Change change = notes.getChange();
       PrivateStateChangedEvent event = new PrivateStateChangedEvent(change);
 
-      event.change = changeAttributeSupplier(change);
+      event.change = changeAttributeSupplier(change, notes);
       event.changer = accountAttributeSupplier(ev.getWho());
 
       dispatcher.get().postEvent(change, event);
@@ -505,7 +510,7 @@
       Change change = notes.getChange();
       VoteDeletedEvent event = new VoteDeletedEvent(change);
 
-      event.change = changeAttributeSupplier(change);
+      event.change = changeAttributeSupplier(change, notes);
       event.patchSet = patchSetAttributeSupplier(change, psUtil.current(db.get(), notes));
       event.comment = ev.getMessage();
       event.reviewer = accountAttributeSupplier(ev.getReviewer());
diff --git a/java/com/google/gerrit/server/query/change/OutputStreamQuery.java b/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
index dc57a9b..dbbf367 100644
--- a/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
+++ b/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
@@ -237,7 +237,7 @@
       ChangeData d, Map<Project.NameKey, Repository> repos, Map<Project.NameKey, RevWalk> revWalks)
       throws OrmException, IOException {
     LabelTypes labelTypes = d.getLabelTypes();
-    ChangeAttribute c = eventFactory.asChangeAttribute(db, d.change());
+    ChangeAttribute c = eventFactory.asChangeAttribute(db, d.change(), d.notes());
     eventFactory.extend(c, d.change());
 
     if (!trackingFooters.isEmpty()) {