Add REST endpoints to retrieve group members and included groups

The '/groups/*/members/' REST endpoint lists the members of a group.
Information for a certain member can be accessed by
'/groups/*/members/<account-id>'.

The '/groups/*/groups/' REST endpoint lists the included groups of a
group. Information for a certain included group can be accessed by
'/groups/*/groups/uuid-<group-uuid>' or '/groups/*/groups/<group-id>'.

Having seperate REST endpoints for members and included groups makes
it easy to add support for adding/deleting members/included groups
later.

It is not possible yet to list members/included groups of system groups
such as 'Project Owners', 'Registered Users' etc. Support for resolving
system groups will be added later.

Change-Id: Iea45f41d2b64927179cebd897aced619c5b9cd04
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
This commit is contained in:
Edwin Kempin
2013-01-09 17:28:14 +01:00
parent 34fbcc1fa8
commit 4fc0920c45
16 changed files with 543 additions and 7 deletions

View File

@@ -28,7 +28,7 @@ public class AccountResource implements RestResource {
private final IdentifiedUser user;
AccountResource(IdentifiedUser user) {
public AccountResource(IdentifiedUser user) {
this.user = user;
}

View File

@@ -69,6 +69,15 @@ public class GroupControl {
}
return c;
}
public GroupControl validateFor(final AccountGroup.UUID groupUUID)
throws NoSuchGroupException {
final GroupControl c = controlFor(groupUUID);
if (!c.isVisible()) {
throw new NoSuchGroupException(groupUUID);
}
return c;
}
}
private final CurrentUser user;

View File

@@ -43,6 +43,7 @@ import com.google.gerrit.server.account.EmailExpander;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupCacheImpl;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.account.GroupDetailFactory;
import com.google.gerrit.server.account.GroupIncludeCacheImpl;
import com.google.gerrit.server.account.GroupInfoCacheFactory;
import com.google.gerrit.server.account.IncludingGroupMembership;
@@ -153,6 +154,7 @@ public class GerritGlobalModule extends FactoryModule {
factory(ChangeQueryBuilder.Factory.class);
factory(GroupInfoCacheFactory.Factory.class);
factory(VisibleGroups.Factory.class);
factory(GroupDetailFactory.Factory.class);
factory(InternalUser.Factory.class);
factory(ProjectNode.Factory.class);
factory(ProjectState.Factory.class);

View File

@@ -20,7 +20,6 @@ import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.RequestCleanup;
import com.google.gerrit.server.account.AccountControl;
import com.google.gerrit.server.account.GroupDetailFactory;
import com.google.gerrit.server.account.GroupMembers;
import com.google.gerrit.server.account.PerformCreateGroup;
import com.google.gerrit.server.account.PerformRenameGroup;
@@ -85,7 +84,6 @@ public class GerritRequestModule extends FactoryModule {
factory(MergeFailSender.Factory.class);
factory(PerformCreateGroup.Factory.class);
factory(PerformRenameGroup.Factory.class);
factory(GroupDetailFactory.Factory.class);
factory(GroupMembers.Factory.class);
factory(CreateProject.Factory.class);
factory(SuggestParentCandidates.Factory.class);

View File

@@ -28,14 +28,18 @@ class GetGroup implements RestReadView<GroupResource> {
@Override
public Object apply(GroupResource resource) throws AuthException,
BadRequestException, ResourceConflictException, Exception {
GroupDescription.Basic group = resource.getControl().getGroup();
return parse(resource.getControl().getGroup());
}
public static GroupInfo parse(final GroupDescription.Basic group) {
GroupInfo info = new GroupInfo();
info.name = resource.getName();
info.uuid = resource.getGroupUUID().get();
info.name = group.getName();
info.uuid = group.getGroupUUID().get();
info.isVisibleToAll = group.isVisibleToAll();
if (group instanceof GroupDescription.Internal) {
final AccountGroup internalGroup =
((GroupDescription.Internal) group).getAccountGroup();
info.groupId = internalGroup.getId().get();
info.description = Strings.emptyToNull(internalGroup.getDescription());
info.ownerUuid = internalGroup.getOwnerGroupUUID().get();
}
@@ -43,11 +47,12 @@ class GetGroup implements RestReadView<GroupResource> {
return info;
}
static class GroupInfo {
public static class GroupInfo {
final String kind = "gerritcodereview#group";
String id;
String name;
String uuid;
int groupId;
String description;
boolean isVisibleToAll;
String ownerUuid;

View File

@@ -0,0 +1,30 @@
// 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.group;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.group.GetGroup.GroupInfo;
public class GetIncludedGroup implements RestReadView<IncludedGroupResource> {
@Override
public GroupInfo apply(final IncludedGroupResource resource) throws AuthException,
BadRequestException, ResourceConflictException, Exception {
return GetGroup.parse(resource.getControl().getGroup());
}
}

View File

@@ -0,0 +1,30 @@
// 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.group;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.group.MembersCollection.MemberInfo;
public class GetMember implements RestReadView<MemberResource> {
@Override
public MemberInfo apply(final MemberResource resource) throws AuthException,
BadRequestException, ResourceConflictException, Exception {
return MembersCollection.parse(resource.getUser().getAccount());
}
}

View File

@@ -72,7 +72,12 @@ public class GroupsCollection implements
} else if(!(user instanceof IdentifiedUser)) {
throw new ResourceNotFoundException(id);
}
return parse(id, groupControlFactory);
}
public static GroupResource parse(final String id,
final GroupControl.Factory groupControlFactory)
throws ResourceNotFoundException {
final String decodedId = Url.decode(id);
final GroupControl ctl;
try {

View File

@@ -0,0 +1,28 @@
// 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.group;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.server.account.GroupControl;
import com.google.inject.TypeLiteral;
public class IncludedGroupResource extends GroupResource {
public static final TypeLiteral<RestView<IncludedGroupResource>> INCLUDED_GROUP_KIND =
new TypeLiteral<RestView<IncludedGroupResource>>() {};
IncludedGroupResource(final GroupControl control) {
super(control);
}
}

View File

@@ -0,0 +1,84 @@
// 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.group;
import com.google.gerrit.common.data.GroupDetail;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupInclude;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.account.GroupDetailFactory;
import com.google.inject.Inject;
import com.google.inject.Provider;
public class IncludedGroupsCollection implements
ChildCollection<GroupResource, IncludedGroupResource> {
private final DynamicMap<RestView<IncludedGroupResource>> views;
private final Provider<ListIncludedGroups> list;
private final GroupControl.Factory groupControlFactory;
private final GroupCache groupCache;
private final GroupDetailFactory.Factory groupDetailFactory;
@Inject
IncludedGroupsCollection(final DynamicMap<RestView<IncludedGroupResource>> views,
final Provider<ListIncludedGroups> list,
final GroupControl.Factory groupControlFactory,
final GroupCache groupCache,
final GroupDetailFactory.Factory groupDetailFactory) {
this.views = views;
this.list = list;
this.groupControlFactory = groupControlFactory;
this.groupCache = groupCache;
this.groupDetailFactory = groupDetailFactory;
}
@Override
public RestView<GroupResource> list() throws ResourceNotFoundException,
AuthException {
return list.get();
}
@Override
public IncludedGroupResource parse(final GroupResource parent, final String id)
throws ResourceNotFoundException, Exception {
final GroupResource groupResource =
GroupsCollection.parse(id, groupControlFactory);
final AccountGroup group =
groupCache.get(parent.getControl().getGroup().getGroupUUID());
final GroupDetail groupDetail =
groupDetailFactory.create(group.getId()).call();
if (groupDetail.includes != null) {
for (final AccountGroupInclude groupInclude : groupDetail.includes) {
final AccountGroup includedGroup =
groupCache.get(groupInclude.getIncludeId());
if (includedGroup.getGroupUUID().equals(groupResource.getGroupUUID())) {
return new IncludedGroupResource(groupResource.getControl());
}
}
}
throw new ResourceNotFoundException(id);
}
@Override
public DynamicMap<RestView<IncludedGroupResource>> views() {
return views;
}
}

View File

@@ -0,0 +1,72 @@
// 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.group;
import com.google.common.collect.Lists;
import com.google.gerrit.common.data.GroupDescriptions;
import com.google.gerrit.common.data.GroupDetail;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupInclude;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.account.GroupDetailFactory;
import com.google.gerrit.server.group.GetGroup.GroupInfo;
import com.google.inject.Inject;
import java.util.List;
public class ListIncludedGroups implements RestReadView<GroupResource> {
private final GroupControl.Factory groupControlFactory;
private final GroupCache groupCache;
private final GroupDetailFactory.Factory groupDetailFactory;
@Inject
ListIncludedGroups(final GroupControl.Factory groupControlFactory,
final GroupCache groupCache,
final GroupDetailFactory.Factory groupDetailFactory) {
this.groupControlFactory = groupControlFactory;
this.groupCache = groupCache;
this.groupDetailFactory = groupDetailFactory;
}
@Override
public List<GroupInfo> apply(final GroupResource resource)
throws AuthException, BadRequestException, ResourceConflictException,
Exception {
final List<GroupInfo> includedGroups = Lists.newArrayList();
final GroupControl groupControl =
groupControlFactory.validateFor(resource.getGroupUUID());
final AccountGroup group =
groupCache.get(groupControl.getGroup().getGroupUUID());
final GroupDetail groupDetail =
groupDetailFactory.create(group.getId()).call();
if (groupDetail.includes != null) {
for (final AccountGroupInclude groupInclude : groupDetail.includes) {
final AccountGroup includedGroup =
groupCache.get(groupInclude.getIncludeId());
includedGroups.add(GetGroup.parse(GroupDescriptions
.forAccountGroup(includedGroup)));
}
}
return includedGroups;
}
}

View File

@@ -0,0 +1,73 @@
// 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.group;
import com.google.common.collect.Lists;
import com.google.gerrit.common.data.GroupDetail;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.account.GroupDetailFactory;
import com.google.gerrit.server.group.MembersCollection.MemberInfo;
import com.google.inject.Inject;
import java.util.List;
public class ListMembers implements RestReadView<GroupResource> {
private final GroupControl.Factory groupControlFactory;
private final GroupCache groupCache;
private final GroupDetailFactory.Factory groupDetailFactory;
private final AccountCache accountCache;
@Inject
ListMembers(final GroupControl.Factory groupControlFactory,
final GroupCache groupCache,
final GroupDetailFactory.Factory groupDetailFactory,
final AccountCache accountCache) {
this.groupControlFactory = groupControlFactory;
this.groupCache = groupCache;
this.groupDetailFactory = groupDetailFactory;
this.accountCache = accountCache;
}
@Override
public List<MemberInfo> apply(final GroupResource resource) throws AuthException,
BadRequestException, ResourceConflictException, Exception {
final List<MemberInfo> members = Lists.newArrayList();
final GroupControl groupControl =
groupControlFactory.validateFor(resource.getGroupUUID());
final AccountGroup group =
groupCache.get(groupControl.getGroup().getGroupUUID());
final GroupDetail groupDetail =
groupDetailFactory.create(group.getId()).call();
if (groupDetail.members != null) {
for (final AccountGroupMember member : groupDetail.members) {
final Account account = accountCache.get(member.getAccountId()).getAccount();
members.add(MembersCollection.parse(account));
}
}
return members;
}
}

View File

@@ -0,0 +1,29 @@
// 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.group;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountResource;
import com.google.inject.TypeLiteral;
public class MemberResource extends AccountResource {
public static final TypeLiteral<RestView<MemberResource>> MEMBER_KIND =
new TypeLiteral<RestView<MemberResource>>() {};
public MemberResource(IdentifiedUser user) {
super(user);
}
}

View File

@@ -0,0 +1,114 @@
// 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.group;
import com.google.gerrit.common.data.GroupDetail;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupDetailFactory;
import com.google.gerrit.server.util.Url;
import com.google.inject.Inject;
import com.google.inject.Provider;
public class MembersCollection implements
ChildCollection<GroupResource, MemberResource> {
private final DynamicMap<RestView<MemberResource>> views;
private final Provider<ListMembers> list;
private final IdentifiedUser.GenericFactory userGenericFactory;
private final GroupCache groupCache;
private final GroupDetailFactory.Factory groupDetailFactory;
@Inject
MembersCollection(final DynamicMap<RestView<MemberResource>> views,
final Provider<ListMembers> list,
final IdentifiedUser.GenericFactory userGenericFactory,
final GroupCache groupCache,
final GroupDetailFactory.Factory groupDetailFactory) {
this.views = views;
this.list = list;
this.userGenericFactory = userGenericFactory;
this.groupCache = groupCache;
this.groupDetailFactory = groupDetailFactory;
}
@Override
public RestView<GroupResource> list() throws ResourceNotFoundException,
AuthException {
return list.get();
}
@Override
public MemberResource parse(final GroupResource parent, final String id)
throws ResourceNotFoundException, Exception {
final Account.Id accountId;
try {
accountId = new Account.Id(Integer.parseInt(Url.decode(id)));
} catch (NumberFormatException e) {
throw new ResourceNotFoundException(id);
}
final AccountGroup group =
groupCache.get(parent.getControl().getGroup().getGroupUUID());
final GroupDetail groupDetail =
groupDetailFactory.create(group.getId()).call();
if (groupDetail.members != null) {
for (final AccountGroupMember member : groupDetail.members) {
if (member.getAccountId().equals(accountId)) {
return new MemberResource(
userGenericFactory.create(accountId));
}
}
}
throw new ResourceNotFoundException(id);
}
@Override
public DynamicMap<RestView<MemberResource>> views() {
return views;
}
public static MemberInfo parse(final Account account) {
final MemberInfo accountInfo = new MemberInfo();
accountInfo.setId(account.getId());
accountInfo.fullName = account.getFullName();
accountInfo.preferredEmail = account.getPreferredEmail();
accountInfo.userName = account.getUserName();
return accountInfo;
}
static class MemberInfo {
final String kind = "gerritcodereview#member";
String fullName;
String id;
int accountId;
String preferredEmail;
String userName;
void setId(Account.Id i) {
accountId = i.get();
id = Url.encode(Integer.toString(accountId));
}
}
}

View File

@@ -15,6 +15,8 @@
package com.google.gerrit.server.group;
import static com.google.gerrit.server.group.GroupResource.GROUP_KIND;
import static com.google.gerrit.server.group.IncludedGroupResource.INCLUDED_GROUP_KIND;
import static com.google.gerrit.server.group.MemberResource.MEMBER_KIND;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.RestApiModule;
@@ -25,7 +27,15 @@ public class Module extends RestApiModule {
bind(GroupsCollection.class);
DynamicMap.mapOf(binder(), GROUP_KIND);
DynamicMap.mapOf(binder(), MEMBER_KIND);
DynamicMap.mapOf(binder(), INCLUDED_GROUP_KIND);
get(GROUP_KIND).to(GetGroup.class);
child(GROUP_KIND, "members").to(MembersCollection.class);
get(MEMBER_KIND).to(GetMember.class);
child(GROUP_KIND, "groups").to(IncludedGroupsCollection.class);
get(INCLUDED_GROUP_KIND).to(GetIncludedGroup.class);
}
}