From d4765db81686cb00ee80952201d40ae5ceb4f863 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Fri, 8 Nov 2013 19:19:45 -0800 Subject: [PATCH] Add REST API to toggle starred change state Tests-by: David Ostrovsky Change-Id: I4c1da5f7e0a1514e091eaab8c532b356c53f138a --- Documentation/rest-api-accounts.txt | 78 +++++++ .../gerrit/acceptance/rest/account/BUCK | 6 +- .../rest/account/StarredChangesIT.java | 122 +++++++++++ .../google/gerrit/acceptance/rest/change/BUCK | 1 + .../acceptance/rest/change/ChangeInfo.java | 1 + .../gerrit/common/data/ChangeListService.java | 35 --- .../gerrit/client/changes/StarredChanges.java | 69 +++--- .../google/gerrit/client/changes/Util.java | 5 - .../httpd/rpc/ChangeListServiceImpl.java | 74 ------- .../google/gerrit/httpd/rpc/UiRpcModule.java | 1 - .../server/account/AccountResource.java | 18 ++ .../google/gerrit/server/account/Module.java | 9 +- .../gerrit/server/account/StarredChanges.java | 199 ++++++++++++++++++ .../server/change/ChangesCollection.java | 2 +- 14 files changed, 467 insertions(+), 153 deletions(-) create mode 100644 gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/StarredChangesIT.java delete mode 100644 gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeListService.java delete mode 100644 gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/ChangeListServiceImpl.java create mode 100644 gerrit-server/src/main/java/com/google/gerrit/server/account/StarredChanges.java diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt index 02bb5493b0..fdea3c7d00 100644 --- a/Documentation/rest-api-accounts.txt +++ b/Documentation/rest-api-accounts.txt @@ -921,6 +921,84 @@ link:#diff-preferences-info[DiffPreferencesInfo] entity. } ---- +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 d8135016fc..1fca451fde 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 @@ include_defs('//gerrit-acceptance-tests/tests.defs') 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 0000000000..b5ae7de6bd --- /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 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 c = (new Gson()).fromJson(r.getReader(), + new TypeToken>() {}.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 b9c2d08836..20b1033f36 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 @@ java_library( '//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 fe8737e665..8b431f0201 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 @@ public class ChangeInfo { String branch; List 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 0c466497ad..0000000000 --- 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 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 7d2084af7e..b097bd8049 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 TYPE = @@ -105,57 +109,52 @@ public class StarredChanges { 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 pending = + new LinkedHashMap(4); - private static void start() { - final ToggleStarRequest req = next; - next = null; + private static void startRequest() { busy = true; - Util.LIST_SVC.toggleStars(req, new GerritCallback() { + 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 cb = new AsyncCallback() { @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); - } - } - if (req.getRemoveSet() != null) { - for (Change.Id id : req.getRemoveSet()) { - fireChangeStarEvent(id, true); + fireChangeStarEvent(id, !starred); + for (Map.Entry e : pending.entrySet()) { + fireChangeStarEvent(e.getKey(), !e.getValue()); + } + pending.clear(); + busy = false; } + }; + 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 590ad876a3..76dfd58aec 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 class Util { 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 @@ public class Util { 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 0b54db1fef..0000000000 --- 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; - - @Inject - ChangeListServiceImpl(final Provider schema, - final Provider currentUser) { - super(schema, currentUser); - this.currentUser = currentUser; - } - - public void toggleStars(final ToggleStarRequest req, - final AsyncCallback callback) { - run(callback, new Action() { - public VoidResult run(final ReviewDb db) throws OrmException { - final Account.Id me = getAccountId(); - final Set existing = currentUser.get().getStarredChanges(); - List add = new ArrayList(); - List remove = new ArrayList(); - - 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 7de332aad5..08e158259c 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 @@ public class UiRpcModule extends RpcServletModule { @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 629bd158ee..106c033ec0 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 @@ package com.google.gerrit.server.account; 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 class AccountResource implements RestResource { public static final TypeLiteral> SSH_KEY_KIND = new TypeLiteral>() {}; + public static final TypeLiteral> STARRED_CHANGE_KIND = + new TypeLiteral>() {}; + private final IdentifiedUser user; public AccountResource(IdentifiedUser user) { @@ -90,4 +95,17 @@ public class AccountResource implements RestResource { 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 79a0089a4d..11f2e9183f 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.ACCOUNT_KIND; 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 @@ public class Module extends RestApiModule { 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 @@ public class Module extends RestApiModule { 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 0000000000..0e335d0540 --- /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, + AcceptsCreate { + private static final Logger log = LoggerFactory.getLogger(StarredChanges.class); + + private final ChangesCollection changes; + private final DynamicMap> views; + private final Provider createProvider; + + @Inject + StarredChanges(ChangesCollection changes, + DynamicMap> views, + Provider 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> views() { + return views; + } + + @Override + public RestView list() throws ResourceNotFoundException { + return new RestReadView() { + @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 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 { + private final Provider self; + private final Provider dbProvider; + private ChangeResource change; + + @Inject + Create(Provider self, Provider 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 { + private final Provider self; + + @Inject + Put(Provider 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 { + private final Provider self; + private final Provider dbProvider; + + @Inject + Delete(Provider self, Provider 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 ecb83b48ed..e93a0d8a88 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 @@ public class ChangesCollection implements } @Override - public RestView list() { + public QueryChanges list() { return queryFactory.get(); }