Merge "Add REST API to toggle starred change state" into stable-2.8
This commit is contained in:
		@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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(
 | 
			
		||||
 
 | 
			
		||||
@@ -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");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -38,4 +38,5 @@ java_library(
 | 
			
		||||
    '//lib:guava',
 | 
			
		||||
    '//gerrit-reviewdb:server',
 | 
			
		||||
  ],
 | 
			
		||||
  visibility = ['//gerrit-acceptance-tests/...'],
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -24,4 +24,5 @@ public class ChangeInfo {
 | 
			
		||||
  String branch;
 | 
			
		||||
  List<ChangeMessageInfo> messages;
 | 
			
		||||
  Change.Status status;
 | 
			
		||||
  public Boolean starred;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
}
 | 
			
		||||
@@ -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 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<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;
 | 
			
		||||
        }
 | 
			
		||||
        busy = false;
 | 
			
		||||
        super.onFailure(caught);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
        if (!starred && RestApi.isStatus(caught, 404)) {
 | 
			
		||||
          onSuccess(null);
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
  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);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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");
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -27,7 +27,6 @@ public class UiRpcModule extends RpcServletModule {
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  protected void configureServlets() {
 | 
			
		||||
    rpc(ChangeListServiceImpl.class);
 | 
			
		||||
    rpc(SuggestServiceImpl.class);
 | 
			
		||||
    rpc(SystemInfoServiceImpl.class);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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<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 @@ 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();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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));
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -58,7 +58,7 @@ public class ChangesCollection implements
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public RestView<TopLevelResource> list() {
 | 
			
		||||
  public QueryChanges list() {
 | 
			
		||||
    return queryFactory.get();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user