Merge "Add REST API to toggle starred change state" into stable-2.8
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index 02bb549..fdea3c7 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -921,6 +921,84 @@
}
----
+Get Starred Changes
+~~~~~~~~~~~~~~~~~~~
+[verse]
+'GET /accounts/link:#account-id[\{account-id\}]/starred.changes'
+
+Gets the changes starred by the identified user account. This
+URL endpoint is functionally identical to the changes query
+`GET /changes/?q=is:starred`. The result is a list of
+link:rest-api-changes.html#change-info[ChangeInfo] entities.
+
+.Request
+----
+ GET /a/accounts/self/starred.changes
+----
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json;charset=UTF-8
+
+ )]}'
+ [
+ {
+ "kind": "gerritcodereview#change",
+ "id": "myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940",
+ "project": "myProject",
+ "branch": "master",
+ "change_id": "I8473b95934b5732ac55d26311a706c9c2bde9940",
+ "subject": "Implementing Feature X",
+ "status": "NEW",
+ "created": "2013-02-01 09:59:32.126000000",
+ "updated": "2013-02-21 11:16:36.775000000",
+ "mergeable": true,
+ "_sortkey": "0023412400000f7d",
+ "_number": 3965,
+ "owner": {
+ "name": "John Doe"
+ }
+ }
+ ]
+----
+
+Star Change
+~~~~~~~~~~~
+[verse]
+'PUT /accounts/link:#account-id[\{account-id\}]/starred.changes/link:rest-api-changes.html#change-id[\{change-id\}]'
+
+Star a change. Starred changes are returned for the search query
+`is:starred` or `starredby:USER` and automatically notify the user
+whenever updates are made to the change.
+
+.Request
+----
+ PUT /a/accounts/self/starred.changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940 HTTP/1.0
+----
+
+.Response
+----
+ HTTP/1.1 204 No Content
+----
+
+Unstar Change
+~~~~~~~~~~~~~
+[verse]
+'DELETE /accounts/link:#account-id[\{account-id\}]/starred.changes/link:rest-api-changes#change-id[\{change-id\}]'
+
+Unstar a change. Removes the starred flag, stopping notifications.
+
+.Request
+----
+ DELETE /a/accounts/self/starred.changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940 HTTP/1.0
+----
+
+.Response
+----
+ HTTP/1.1 204 No Content
+----
[[ids]]
IDs
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/BUCK
index d813501..1fca451 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/BUCK
@@ -2,7 +2,11 @@
acceptance_tests(
srcs = glob(['*IT.java']),
- deps = [':util'],
+ deps = [
+ ':util',
+ '//gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git:util',
+ '//gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change:util',
+ ],
)
java_library(
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/StarredChangesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/StarredChangesIT.java
new file mode 100644
index 0000000..b5ae7de
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/StarredChangesIT.java
@@ -0,0 +1,122 @@
+// Copyright (C) 2013 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.acceptance.rest.account;
+
+import static com.google.gerrit.acceptance.git.GitUtil.cloneProject;
+import static com.google.gerrit.acceptance.git.GitUtil.createProject;
+import static com.google.gerrit.acceptance.git.GitUtil.initSsh;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.AccountCreator;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.RestSession;
+import com.google.gerrit.acceptance.SshSession;
+import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.git.PushOneCommit;
+import com.google.gerrit.acceptance.git.PushOneCommit.Result;
+import com.google.gerrit.acceptance.rest.change.ChangeInfo;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.List;
+
+public class StarredChangesIT extends AbstractDaemonTest {
+
+ @Inject
+ private AccountCreator accounts;
+
+ @Inject
+ private SchemaFactory<ReviewDb> reviewDbProvider;
+
+ private TestAccount admin;
+
+ private RestSession session;
+ private Git git;
+ private ReviewDb db;
+
+ @Before
+ public void setUp() throws Exception {
+ admin = accounts.admin();
+ session = new RestSession(server, admin);
+ initSsh(admin);
+ Project.NameKey project = new Project.NameKey("p");
+ SshSession sshSession = new SshSession(server, admin);
+ createProject(sshSession, project.get());
+ git = cloneProject(sshSession.getUrl() + "/" + project.get());
+ sshSession.close();
+ db = reviewDbProvider.open();
+ }
+
+ @After
+ public void cleanup() {
+ db.close();
+ }
+
+ @Test
+ public void starredChangeState() throws GitAPIException, IOException,
+ OrmException {
+ Result c1 = createChange();
+ Result c2 = createChange();
+ assertNull(getChange(c1.getChangeId()).starred);
+ assertNull(getChange(c2.getChangeId()).starred);
+ starChange(true, c1.getPatchSetId().getParentKey());
+ starChange(true, c2.getPatchSetId().getParentKey());
+ assertTrue(getChange(c1.getChangeId()).starred);
+ assertTrue(getChange(c2.getChangeId()).starred);
+ starChange(false, c1.getPatchSetId().getParentKey());
+ starChange(false, c2.getPatchSetId().getParentKey());
+ assertNull(getChange(c1.getChangeId()).starred);
+ assertNull(getChange(c2.getChangeId()).starred);
+ }
+
+ private ChangeInfo getChange(String changeId) throws IOException {
+ RestResponse r = session.get("/changes/?q=" + changeId);
+ List<ChangeInfo> c = (new Gson()).fromJson(r.getReader(),
+ new TypeToken<List<ChangeInfo>>() {}.getType());
+ return c.get(0);
+ }
+
+ private void starChange(boolean on, Change.Id id) throws IOException {
+ String url = "/accounts/self/starred.changes/" + id.get();
+ if (on) {
+ RestResponse r = session.put(url);
+ assertEquals(204, r.getStatusCode());
+ } else {
+ RestResponse r = session.delete(url);
+ assertEquals(204, r.getStatusCode());
+ }
+ }
+
+ private Result createChange() throws GitAPIException, IOException {
+ PushOneCommit push = new PushOneCommit(db, admin.getIdent());
+ return push.to(git, "refs/for/master");
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUCK
index b9c2d08..20b1033 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUCK
@@ -38,4 +38,5 @@
'//lib:guava',
'//gerrit-reviewdb:server',
],
+ visibility = ['//gerrit-acceptance-tests/...'],
)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeInfo.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeInfo.java
index fe8737e..8b431f0 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeInfo.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeInfo.java
@@ -24,4 +24,5 @@
String branch;
List<ChangeMessageInfo> messages;
Change.Status status;
+ public Boolean starred;
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeListService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeListService.java
deleted file mode 100644
index 0c466497..0000000
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeListService.java
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (C) 2008 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.data;
-
-import com.google.gerrit.common.audit.Audit;
-import com.google.gerrit.common.auth.SignInRequired;
-import com.google.gwtjsonrpc.common.AsyncCallback;
-import com.google.gwtjsonrpc.common.RemoteJsonService;
-import com.google.gwtjsonrpc.common.RpcImpl;
-import com.google.gwtjsonrpc.common.VoidResult;
-import com.google.gwtjsonrpc.common.RpcImpl.Version;
-
-@RpcImpl(version = Version.V2_0)
-public interface ChangeListService extends RemoteJsonService {
- /**
- * Add and/or remove changes from the set of starred changes of the caller.
- *
- * @param req the add and remove cluster.
- */
- @Audit
- @SignInRequired
- void toggleStars(ToggleStarRequest req, AsyncCallback<VoidResult> callback);
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarredChanges.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarredChanges.java
index 7d2084a..b097bd8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarredChanges.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarredChanges.java
@@ -15,19 +15,23 @@
package com.google.gerrit.client.changes;
import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.rpc.GerritCallback;
-import com.google.gerrit.common.data.ToggleStarRequest;
+import com.google.gerrit.client.account.AccountApi;
+import com.google.gerrit.client.rpc.RestApi;
import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Image;
import com.google.gwtexpui.globalkey.client.KeyCommand;
-import com.google.gwtjsonrpc.common.VoidResult;
import com.google.web.bindery.event.shared.Event;
import com.google.web.bindery.event.shared.HandlerRegistration;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
/** Supports the star icon displayed on changes and tracking the status. */
public class StarredChanges {
private static final Event.Type<ChangeStarHandler> TYPE =
@@ -105,57 +109,52 @@
public static void toggleStar(
final Change.Id changeId,
final boolean newValue) {
- if (next == null) {
- next = new ToggleStarRequest();
- }
- next.toggle(changeId, newValue);
+ pending.put(changeId, newValue);
fireChangeStarEvent(changeId, newValue);
if (!busy) {
- start();
+ startRequest();
}
}
- private static ToggleStarRequest next;
private static boolean busy;
+ private static final Map<Change.Id, Boolean> pending =
+ new LinkedHashMap<Change.Id, Boolean>(4);
- private static void start() {
- final ToggleStarRequest req = next;
- next = null;
+ private static void startRequest() {
busy = true;
- Util.LIST_SVC.toggleStars(req, new GerritCallback<VoidResult>() {
+ final Change.Id id = pending.keySet().iterator().next();
+ final boolean starred = pending.remove(id);
+ RestApi call = AccountApi.self().view("starred.changes").id(id.get());
+ AsyncCallback<JavaScriptObject> cb = new AsyncCallback<JavaScriptObject>() {
@Override
- public void onSuccess(VoidResult result) {
- if (next != null) {
- start();
- } else {
+ public void onSuccess(JavaScriptObject none) {
+ if (pending.isEmpty()) {
busy = false;
+ } else {
+ startRequest();
}
}
@Override
public void onFailure(Throwable caught) {
- rollback(req);
- if (next != null) {
- rollback(next);
- next = null;
+ if (!starred && RestApi.isStatus(caught, 404)) {
+ onSuccess(null);
+ return;
}
- busy = false;
- super.onFailure(caught);
- }
- });
- }
- private static void rollback(ToggleStarRequest req) {
- if (req.getAddSet() != null) {
- for (Change.Id id : req.getAddSet()) {
- fireChangeStarEvent(id, false);
+ fireChangeStarEvent(id, !starred);
+ for (Map.Entry<Change.Id, Boolean> e : pending.entrySet()) {
+ fireChangeStarEvent(e.getKey(), !e.getValue());
+ }
+ pending.clear();
+ busy = false;
}
- }
- if (req.getRemoveSet() != null) {
- for (Change.Id id : req.getRemoveSet()) {
- fireChangeStarEvent(id, true);
- }
+ };
+ if (starred) {
+ call.put(cb);
+ } else {
+ call.delete(cb);
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/Util.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/Util.java
index 590ad87..76dfd58 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/Util.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/Util.java
@@ -15,7 +15,6 @@
package com.google.gerrit.client.changes;
import com.google.gerrit.common.data.ChangeDetailService;
-import com.google.gerrit.common.data.ChangeListService;
import com.google.gerrit.common.data.ChangeManageService;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gwt.core.client.GWT;
@@ -27,7 +26,6 @@
public static final ChangeResources R = GWT.create(ChangeResources.class);
public static final ChangeDetailService DETAIL_SVC;
- public static final ChangeListService LIST_SVC;
public static final ChangeManageService MANAGE_SVC;
private static final int SUBJECT_MAX_LENGTH = 80;
@@ -38,9 +36,6 @@
DETAIL_SVC = GWT.create(ChangeDetailService.class);
JsonUtil.bind(DETAIL_SVC, "rpc/ChangeDetailService");
- LIST_SVC = GWT.create(ChangeListService.class);
- JsonUtil.bind(LIST_SVC, "rpc/ChangeListService");
-
MANAGE_SVC = GWT.create(ChangeManageService.class);
JsonUtil.bind(MANAGE_SVC, "rpc/ChangeManageService");
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/ChangeListServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/ChangeListServiceImpl.java
deleted file mode 100644
index 0b54db1..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/ChangeListServiceImpl.java
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright (C) 2008 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.httpd.rpc;
-
-import com.google.gerrit.common.data.ChangeListService;
-import com.google.gerrit.common.data.ToggleStarRequest;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.StarredChange;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.CurrentUser;
-import com.google.gwtjsonrpc.common.AsyncCallback;
-import com.google.gwtjsonrpc.common.VoidResult;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
-public class ChangeListServiceImpl extends BaseServiceImplementation implements
- ChangeListService {
- private final Provider<CurrentUser> currentUser;
-
- @Inject
- ChangeListServiceImpl(final Provider<ReviewDb> schema,
- final Provider<CurrentUser> currentUser) {
- super(schema, currentUser);
- this.currentUser = currentUser;
- }
-
- public void toggleStars(final ToggleStarRequest req,
- final AsyncCallback<VoidResult> callback) {
- run(callback, new Action<VoidResult>() {
- public VoidResult run(final ReviewDb db) throws OrmException {
- final Account.Id me = getAccountId();
- final Set<Change.Id> existing = currentUser.get().getStarredChanges();
- List<StarredChange> add = new ArrayList<StarredChange>();
- List<StarredChange.Key> remove = new ArrayList<StarredChange.Key>();
-
- if (req.getAddSet() != null) {
- for (final Change.Id id : req.getAddSet()) {
- if (!existing.contains(id)) {
- add.add(new StarredChange(new StarredChange.Key(me, id)));
- }
- }
- }
-
- if (req.getRemoveSet() != null) {
- for (final Change.Id id : req.getRemoveSet()) {
- remove.add(new StarredChange.Key(me, id));
- }
- }
-
- db.starredChanges().insert(add);
- db.starredChanges().deleteKeys(remove);
- return VoidResult.INSTANCE;
- }
- });
- }
-}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/UiRpcModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/UiRpcModule.java
index 7de332a..08e1582 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/UiRpcModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/UiRpcModule.java
@@ -27,7 +27,6 @@
@Override
protected void configureServlets() {
- rpc(ChangeListServiceImpl.class);
rpc(SuggestServiceImpl.class);
rpc(SystemInfoServiceImpl.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResource.java
index 629bd15..106c033 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResource.java
@@ -17,7 +17,9 @@
import com.google.gerrit.extensions.restapi.RestResource;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.reviewdb.client.AccountSshKey;
+import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.change.ChangeResource;
import com.google.inject.TypeLiteral;
public class AccountResource implements RestResource {
@@ -33,6 +35,9 @@
public static final TypeLiteral<RestView<SshKey>> SSH_KEY_KIND =
new TypeLiteral<RestView<SshKey>>() {};
+ public static final TypeLiteral<RestView<StarredChange>> STARRED_CHANGE_KIND =
+ new TypeLiteral<RestView<StarredChange>>() {};
+
private final IdentifiedUser user;
public AccountResource(IdentifiedUser user) {
@@ -90,4 +95,17 @@
return sshKey;
}
}
+
+ public static class StarredChange extends AccountResource {
+ private final ChangeResource change;
+
+ public StarredChange(IdentifiedUser user, ChangeResource change) {
+ super(user);
+ this.change = change;
+ }
+
+ public Change getChange() {
+ return change.getChange();
+ }
+ }
}
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 79a0089..11f2e91 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
@@ -18,6 +18,7 @@
import static com.google.gerrit.server.account.AccountResource.CAPABILITY_KIND;
import static com.google.gerrit.server.account.AccountResource.EMAIL_KIND;
import static com.google.gerrit.server.account.AccountResource.SSH_KEY_KIND;
+import static com.google.gerrit.server.account.AccountResource.STARRED_CHANGE_KIND;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.RestApiModule;
@@ -30,9 +31,10 @@
bind(Capabilities.class);
DynamicMap.mapOf(binder(), ACCOUNT_KIND);
+ DynamicMap.mapOf(binder(), CAPABILITY_KIND);
DynamicMap.mapOf(binder(), EMAIL_KIND);
DynamicMap.mapOf(binder(), SSH_KEY_KIND);
- DynamicMap.mapOf(binder(), CAPABILITY_KIND);
+ DynamicMap.mapOf(binder(), STARRED_CHANGE_KIND);
put(ACCOUNT_KIND).to(PutAccount.class);
get(ACCOUNT_KIND).to(GetAccount.class);
@@ -65,6 +67,11 @@
put(ACCOUNT_KIND, "preferences.diff").to(SetDiffPreferences.class);
get(CAPABILITY_KIND).to(GetCapabilities.CheckOne.class);
+ child(ACCOUNT_KIND, "starred.changes").to(StarredChanges.class);
+ put(STARRED_CHANGE_KIND).to(StarredChanges.Put.class);
+ delete(STARRED_CHANGE_KIND).to(StarredChanges.Delete.class);
+ bind(StarredChanges.Create.class);
+
install(new FactoryModuleBuilder().build(CreateAccount.Factory.class));
install(new FactoryModuleBuilder().build(CreateEmail.Factory.class));
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/StarredChanges.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/StarredChanges.java
new file mode 100644
index 0000000..0e335d0
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/StarredChanges.java
@@ -0,0 +1,199 @@
+// Copyright (C) 2013 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.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.AcceptsCreate;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ChildCollection;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.reviewdb.client.StarredChange;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.change.ChangesCollection;
+import com.google.gerrit.server.query.change.QueryChanges;
+import com.google.gwtorm.server.OrmDuplicateKeyException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.UnsupportedEncodingException;
+import java.util.Collections;
+
+class StarredChanges implements
+ ChildCollection<AccountResource, AccountResource.StarredChange>,
+ AcceptsCreate<AccountResource> {
+ private static final Logger log = LoggerFactory.getLogger(StarredChanges.class);
+
+ private final ChangesCollection changes;
+ private final DynamicMap<RestView<AccountResource.StarredChange>> views;
+ private final Provider<Create> createProvider;
+
+ @Inject
+ StarredChanges(ChangesCollection changes,
+ DynamicMap<RestView<AccountResource.StarredChange>> views,
+ Provider<Create> createProvider) {
+ this.changes = changes;
+ this.views = views;
+ this.createProvider = createProvider;
+ }
+
+ @Override
+ public AccountResource.StarredChange parse(AccountResource parent, IdString id)
+ throws ResourceNotFoundException, OrmException, UnsupportedEncodingException {
+ IdentifiedUser user = parent.getUser();
+ try {
+ user.asyncStarredChanges();
+
+ ChangeResource change = changes.parse(TopLevelResource.INSTANCE, id);
+ if (user.getStarredChanges().contains(change.getChange().getId())) {
+ return new AccountResource.StarredChange(user, change);
+ }
+ throw new ResourceNotFoundException(id);
+ } finally {
+ user.abortStarredChanges();
+ }
+ }
+
+ @Override
+ public DynamicMap<RestView<AccountResource.StarredChange>> views() {
+ return views;
+ }
+
+ @Override
+ public RestView<AccountResource> list() throws ResourceNotFoundException {
+ return new RestReadView<AccountResource>() {
+ @Override
+ public Object apply(AccountResource self) throws BadRequestException,
+ AuthException, OrmException {
+ QueryChanges query = changes.list();
+ query.addQuery("starredby:" + self.getUser().getAccountId().get());
+ return query.apply(TopLevelResource.INSTANCE);
+ }
+ };
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public RestModifyView<AccountResource, EmptyInput> create(
+ AccountResource parent, IdString id) throws UnprocessableEntityException{
+ try {
+ return createProvider.get()
+ .setChange(changes.parse(TopLevelResource.INSTANCE, id));
+ } catch (ResourceNotFoundException e) {
+ throw new UnprocessableEntityException(String.format("change %s not found", id.get()));
+ } catch (UnsupportedEncodingException e) {
+ log.error("cannot resolve change", e);
+ throw new UnprocessableEntityException("internal server error");
+ } catch (OrmException e) {
+ log.error("cannot resolve change", e);
+ throw new UnprocessableEntityException("internal server error");
+ }
+ }
+
+ static class Create implements RestModifyView<AccountResource, EmptyInput> {
+ private final Provider<CurrentUser> self;
+ private final Provider<ReviewDb> dbProvider;
+ private ChangeResource change;
+
+ @Inject
+ Create(Provider<CurrentUser> self, Provider<ReviewDb> dbProvider) {
+ this.self = self;
+ this.dbProvider = dbProvider;
+ }
+
+ Create setChange(ChangeResource change) {
+ this.change = change;
+ return this;
+ }
+
+ @Override
+ public Response<?> apply(AccountResource rsrc, EmptyInput in)
+ throws AuthException, OrmException {
+ if (self.get() != rsrc.getUser()) {
+ throw new AuthException("not allowed to add starred change");
+ }
+ try {
+ dbProvider.get().starredChanges().insert(Collections.singleton(
+ new StarredChange(new StarredChange.Key(
+ rsrc.getUser().getAccountId(),
+ change.getChange().getId()))));
+ } catch (OrmDuplicateKeyException e) {
+ return Response.none();
+ }
+ return Response.none();
+ }
+ }
+
+ static class Put implements
+ RestModifyView<AccountResource.StarredChange, EmptyInput> {
+ private final Provider<CurrentUser> self;
+
+ @Inject
+ Put(Provider<CurrentUser> self) {
+ this.self = self;
+ }
+
+ @Override
+ public Response<?> apply(AccountResource.StarredChange rsrc, EmptyInput in)
+ throws AuthException, OrmException {
+ if (self.get() != rsrc.getUser()) {
+ throw new AuthException("not allowed update starred changes");
+ }
+ return Response.none();
+ }
+ }
+
+ static class Delete implements
+ RestModifyView<AccountResource.StarredChange, EmptyInput> {
+ private final Provider<CurrentUser> self;
+ private final Provider<ReviewDb> dbProvider;
+
+ @Inject
+ Delete(Provider<CurrentUser> self, Provider<ReviewDb> dbProvider) {
+ this.self = self;
+ this.dbProvider = dbProvider;
+ }
+
+ @Override
+ public Response<?> apply(AccountResource.StarredChange rsrc,
+ EmptyInput in) throws AuthException, OrmException {
+ if (self.get() != rsrc.getUser()) {
+ throw new AuthException("not allowed remove starred change");
+ }
+ dbProvider.get().starredChanges().delete(Collections.singleton(
+ new StarredChange(new StarredChange.Key(
+ rsrc.getUser().getAccountId(),
+ rsrc.getChange().getId()))));
+ return Response.none();
+ }
+ }
+
+ static class EmptyInput {
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java
index ecb83b48..e93a0d8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java
@@ -58,7 +58,7 @@
}
@Override
- public RestView<TopLevelResource> list() {
+ public QueryChanges list() {
return queryFactory.get();
}