Merge "Implemented GET endpoint for watched projects"
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index ae42477..bdfe65e 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -1437,6 +1437,47 @@
   }
 ----
 
+[[get-watched-projects]]
+=== Get Watched Projects
+--
+'GET /accounts/link:#account-id[\{account-id\}]/watched.projects'
+--
+
+Retrieves all projects a user is watching.
+
+.Request
+----
+  GET /a/accounts/self/watched.projects HTTP/1.0
+----
+
+As result the watched projects of the user are returned as a list of
+link:#project-watch-info[ProjectWatchInfo] entities.
+The result is sorted by project name in ascending order.
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json; charset=UTF-8
+
+  )]}'
+  [
+    {
+      "project": "Test Project 1",
+      "notify_new_changes": true,
+      "notify_new_patch_sets": true,
+      "notify_all_comments": true,
+    },
+    {
+      "project": "Test Project 2",
+      "filter": "branch:experimental",
+      "notify_all_comments": true,
+      "notify_submitted_changes": true,
+      "notify_abandoned_changes": true
+    }
+  ]
+----
+
 [[get-starred-changes]]
 === Get Starred Changes
 --
@@ -2095,6 +2136,22 @@
 |`username` |The new username of the account.
 |=======================
 
+[[project-watch-info]]
+=== ProjectWatchInfo
+The `WatchedProjectsInfo` entity contains information about a project watch
+for a user.
+
+[options="header",cols="1,^1,5"]
+|=======================
+|Field Name                 |        |Description
+|`project`                  |        |The name of the project.
+|`filter`                   |optional|A filter string to be applied to the project.
+|`notify_new_changes`       |optional|Notify on new changes.
+|`notify_new_patch_sets`    |optional|Notify on new patch sets.
+|`notify_all_comments`      |optional|Notify on comments.
+|`notify_submitted_changes` |optional|Notify on submitted changes.
+|`notify_abandoned_changes` |optional|Notify on abandoned changes.
+|=======================
 
 GERRIT
 ------
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/AccountApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/AccountApi.java
index a6e54b2..74e77a0 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/AccountApi.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/AccountApi.java
@@ -17,6 +17,7 @@
 import com.google.gerrit.extensions.client.DiffPreferencesInfo;
 import com.google.gerrit.extensions.client.EditPreferencesInfo;
 import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
+import com.google.gerrit.extensions.client.ProjectWatchInfo;
 import com.google.gerrit.extensions.common.AccountInfo;
 import com.google.gerrit.extensions.common.GpgKeyInfo;
 import com.google.gerrit.extensions.common.SshKeyInfo;
@@ -43,6 +44,8 @@
   EditPreferencesInfo setEditPreferences(EditPreferencesInfo in)
       throws RestApiException;
 
+  List<ProjectWatchInfo> getWatchedProjects() throws RestApiException;
+
   void starChange(String id) throws RestApiException;
   void unstarChange(String id) throws RestApiException;
   void addEmail(EmailInput input) throws RestApiException;
@@ -105,6 +108,12 @@
     }
 
     @Override
+    public List<ProjectWatchInfo> getWatchedProjects()
+        throws RestApiException {
+      throw new NotImplementedException();
+    }
+
+    @Override
     public void starChange(String id) throws RestApiException {
       throw new NotImplementedException();
     }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ProjectWatchInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ProjectWatchInfo.java
new file mode 100644
index 0000000..5ebf663
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ProjectWatchInfo.java
@@ -0,0 +1,26 @@
+// 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.extensions.client;
+
+public class ProjectWatchInfo {
+  public String project;
+  public String filter;
+
+  public Boolean notifyNewChanges;
+  public Boolean notifyNewPatchSets;
+  public Boolean notifyAllComments;
+  public Boolean notifySubmittedChanges;
+  public Boolean notifyAbandonedChanges;
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetWatchedProjects.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetWatchedProjects.java
new file mode 100644
index 0000000..fd00b7a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetWatchedProjects.java
@@ -0,0 +1,78 @@
+// 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.server.account;
+
+import com.google.gerrit.extensions.client.ProjectWatchInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.reviewdb.client.AccountProjectWatch;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import java.util.LinkedList;
+import java.util.List;
+
+@Singleton
+public class GetWatchedProjects implements RestReadView<AccountResource> {
+
+  private final Provider<ReviewDb> dbProvider;
+  private final Provider<IdentifiedUser> self;
+
+  @Inject
+  public GetWatchedProjects(Provider<ReviewDb> dbProvider,
+      Provider<IdentifiedUser> self) {
+    this.dbProvider = dbProvider;
+    this.self = self;
+  }
+
+  @Override
+  public List<ProjectWatchInfo> apply(AccountResource rsrc)
+      throws OrmException, AuthException {
+    if (self.get() != rsrc.getUser()) {
+      throw new AuthException("It is not allowed to list project watches "
+          + "of other users");
+    }
+    List<ProjectWatchInfo> projectWatchInfos = new LinkedList<>();
+    Iterable<AccountProjectWatch> projectWatches =
+        dbProvider.get().accountProjectWatches()
+            .byAccount(rsrc.getUser().getAccountId());
+    for (AccountProjectWatch a : projectWatches) {
+      ProjectWatchInfo pwi = new ProjectWatchInfo();
+      pwi.filter = a.getFilter();
+      pwi.project = a.getProjectNameKey().get();
+      pwi.notifyAbandonedChanges =
+          toBoolean(
+              a.isNotify(AccountProjectWatch.NotifyType.ABANDONED_CHANGES));
+      pwi.notifyNewChanges =
+          toBoolean(a.isNotify(AccountProjectWatch.NotifyType.NEW_CHANGES));
+      pwi.notifyNewPatchSets =
+          toBoolean(a.isNotify(AccountProjectWatch.NotifyType.NEW_PATCHSETS));
+      pwi.notifySubmittedChanges =
+          toBoolean(
+              a.isNotify(AccountProjectWatch.NotifyType.SUBMITTED_CHANGES));
+      pwi.notifyAllComments =
+          toBoolean(a.isNotify(AccountProjectWatch.NotifyType.ALL_COMMENTS));
+      projectWatchInfos.add(pwi);
+    }
+    return projectWatchInfos;
+  }
+
+  private static Boolean toBoolean(boolean value) {
+    return value ? true : null;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java
index 54d4cc0..e123066 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java
@@ -56,6 +56,7 @@
     delete(ACCOUNT_KIND, "password.http").to(PutHttpPassword.class);
     child(ACCOUNT_KIND, "sshkeys").to(SshKeys.class);
     post(ACCOUNT_KIND, "sshkeys").to(AddSshKey.class);
+    get(ACCOUNT_KIND, "watched.projects").to(GetWatchedProjects.class);
 
     get(SSH_KEY_KIND).to(GetSshKey.class);
     delete(SSH_KEY_KIND).to(DeleteSshKey.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
index 709020f..d88606f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.extensions.client.DiffPreferencesInfo;
 import com.google.gerrit.extensions.client.EditPreferencesInfo;
 import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
+import com.google.gerrit.extensions.client.ProjectWatchInfo;
 import com.google.gerrit.extensions.common.AccountInfo;
 import com.google.gerrit.extensions.common.GpgKeyInfo;
 import com.google.gerrit.extensions.common.SshKeyInfo;
@@ -39,6 +40,7 @@
 import com.google.gerrit.server.account.GetEditPreferences;
 import com.google.gerrit.server.account.GetPreferences;
 import com.google.gerrit.server.account.GetSshKeys;
+import com.google.gerrit.server.account.GetWatchedProjects;
 import com.google.gerrit.server.account.SetDiffPreferences;
 import com.google.gerrit.server.account.SetEditPreferences;
 import com.google.gerrit.server.account.SetPreferences;
@@ -71,6 +73,7 @@
   private final SetDiffPreferences setDiffPreferences;
   private final GetEditPreferences getEditPreferences;
   private final SetEditPreferences setEditPreferences;
+  private final GetWatchedProjects getWatchedProjects;
   private final StarredChanges.Create starredChangesCreate;
   private final StarredChanges.Delete starredChangesDelete;
   private final CreateEmail.Factory createEmailFactory;
@@ -90,6 +93,7 @@
       SetDiffPreferences setDiffPreferences,
       GetEditPreferences getEditPreferences,
       SetEditPreferences setEditPreferences,
+      GetWatchedProjects getWatchedProjects,
       StarredChanges.Create starredChangesCreate,
       StarredChanges.Delete starredChangesDelete,
       CreateEmail.Factory createEmailFactory,
@@ -109,6 +113,7 @@
     this.setDiffPreferences = setDiffPreferences;
     this.getEditPreferences = getEditPreferences;
     this.setEditPreferences = setEditPreferences;
+    this.getWatchedProjects = getWatchedProjects;
     this.starredChangesCreate = starredChangesCreate;
     this.starredChangesDelete = starredChangesDelete;
     this.createEmailFactory = createEmailFactory;
@@ -192,6 +197,15 @@
   }
 
   @Override
+  public List<ProjectWatchInfo> getWatchedProjects() throws RestApiException {
+    try {
+      return getWatchedProjects.apply(account);
+    } catch (OrmException e) {
+      throw new RestApiException("Cannot get watched projects", e);
+    }
+  }
+
+  @Override
   public void starChange(String id) throws RestApiException {
     try {
       ChangeResource rsrc = changes.parse(