Remove @SecondaryKey from AccountGroup

Like our prior changes, we need to drop these @SecondaryKey
annotations for databases which don't support multiple keys
on a single entity.

For the external name attribute we simply change it to honor
a list of groups which match the external name.  This allows
an administrator to create multiple groups in Gerrit that use
the same underlying LDAP group for membership.  Its crazy to
do, but there isn't really any good reason to not allow it.

For the internal name attribute we create a new entity that
can be used to enforce uniqueness on the name attribute, and
connects the name to the group.

Change-Id: I933c38a6a4e2c3ed3d7d5a66cab04c2e7175e24f
Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
Shawn O. Pearce
2009-12-31 10:26:26 -08:00
parent b80f07dd5e
commit b1dac0a73b
18 changed files with 379 additions and 87 deletions

View File

@@ -16,7 +16,6 @@ package com.google.gerrit.httpd.rpc.account;
import com.google.gerrit.httpd.rpc.RpcServletModule;
import com.google.gerrit.httpd.rpc.UiRpcModule;
import com.google.gerrit.server.account.ChangeUserName;
import com.google.gerrit.server.config.FactoryModule;
public class AccountModule extends RpcServletModule {
@@ -30,10 +29,12 @@ public class AccountModule extends RpcServletModule {
@Override
protected void configure() {
factory(AgreementInfoFactory.Factory.class);
factory(CreateGroup.Factory.class);
factory(DeleteExternalIds.Factory.class);
factory(ExternalIdDetailFactory.Factory.class);
factory(GroupDetailFactory.Factory.class);
factory(MyGroupsFactory.Factory.class);
factory(RenameGroup.Factory.class);
}
});
rpc(AccountSecurityImpl.class);

View File

@@ -0,0 +1,89 @@
// Copyright (C) 2009 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.account;
import com.google.gerrit.common.errors.NameAlreadyUsedException;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AccountGroupMember;
import com.google.gerrit.reviewdb.AccountGroupMemberAudit;
import com.google.gerrit.reviewdb.AccountGroupName;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountCache;
import com.google.gwtorm.client.OrmDuplicateKeyException;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.util.Collections;
class CreateGroup extends Handler<AccountGroup.Id> {
interface Factory {
CreateGroup create(String newName);
}
private final ReviewDb db;
private final IdentifiedUser user;
private final AccountCache accountCache;
private final String name;
@Inject
CreateGroup(final ReviewDb db, final IdentifiedUser user,
final AccountCache accountCache,
@Assisted final String newName) {
this.db = db;
this.user = user;
this.accountCache = accountCache;
this.name = newName;
}
@Override
public AccountGroup.Id call() throws OrmException, NameAlreadyUsedException {
final AccountGroup.NameKey key = new AccountGroup.NameKey(name);
if (db.accountGroupNames().get(key) != null) {
throw new NameAlreadyUsedException();
}
final AccountGroup.Id id = new AccountGroup.Id(db.nextAccountGroupId());
final Account.Id me = user.getAccountId();
final AccountGroup group = new AccountGroup(key, id);
db.accountGroups().insert(Collections.singleton(group));
try {
final AccountGroupName n = new AccountGroupName(key, id);
db.accountGroupNames().insert(Collections.singleton(n));
} catch (OrmDuplicateKeyException dupeErr) {
db.accountGroups().delete(Collections.singleton(group));
throw new NameAlreadyUsedException();
}
AccountGroupMember member = new AccountGroupMember(//
new AccountGroupMember.Key(me, id));
db.accountGroupMembersAudit().insert(
Collections.singleton(new AccountGroupMemberAudit(member, me)));
db.accountGroupMembers().insert(Collections.singleton(member));
accountCache.evict(me);
return id;
}
}

View File

@@ -54,6 +54,9 @@ class GroupAdminServiceImpl extends BaseServiceImplementation implements
private final Realm accountRealm;
private final GroupCache groupCache;
private final GroupControl.Factory groupControlFactory;
private final CreateGroup.Factory createGroupFactory;
private final RenameGroup.Factory renameGroupFactory;
private final GroupDetailFactory.Factory groupDetailFactory;
@Inject
@@ -62,6 +65,8 @@ class GroupAdminServiceImpl extends BaseServiceImplementation implements
final AccountCache accountCache, final AccountResolver accountResolver,
final Realm accountRealm, final GroupCache groupCache,
final GroupControl.Factory groupControlFactory,
final CreateGroup.Factory createGroupFactory,
final RenameGroup.Factory renameGroupFactory,
final GroupDetailFactory.Factory groupDetailFactory) {
super(schema, currentUser);
this.identifiedUser = currentUser;
@@ -70,6 +75,8 @@ class GroupAdminServiceImpl extends BaseServiceImplementation implements
this.accountRealm = accountRealm;
this.groupCache = groupCache;
this.groupControlFactory = groupControlFactory;
this.createGroupFactory = createGroupFactory;
this.renameGroupFactory = renameGroupFactory;
this.groupDetailFactory = groupDetailFactory;
}
@@ -112,37 +119,7 @@ class GroupAdminServiceImpl extends BaseServiceImplementation implements
public void createGroup(final String newName,
final AsyncCallback<AccountGroup.Id> callback) {
run(callback, new Action<AccountGroup.Id>() {
public AccountGroup.Id run(final ReviewDb db) throws OrmException,
Failure {
final AccountGroup.NameKey nameKey = new AccountGroup.NameKey(newName);
if (db.accountGroups().get(nameKey) != null) {
throw new Failure(new NameAlreadyUsedException());
}
final AccountGroup group =
new AccountGroup(nameKey, new AccountGroup.Id(db
.nextAccountGroupId()));
group.setNameKey(nameKey);
group.setType(AccountGroup.Type.INTERNAL);
group.setDescription("");
final Account.Id me = getAccountId();
final AccountGroupMember m =
new AccountGroupMember(
new AccountGroupMember.Key(me, group.getId()));
final Transaction txn = db.beginTransaction();
db.accountGroups().insert(Collections.singleton(group), txn);
db.accountGroupMembers().insert(Collections.singleton(m), txn);
db.accountGroupMembersAudit().insert(
Collections.singleton(new AccountGroupMemberAudit(m, me)), txn);
txn.commit();
accountCache.evict(m.getAccountId());
return group.getId();
}
});
createGroupFactory.create(newName).to(callback);
}
public void groupDetail(final AccountGroup.Id groupId,
@@ -172,7 +149,7 @@ class GroupAdminServiceImpl extends BaseServiceImplementation implements
assertAmGroupOwner(db, group);
final AccountGroup owner =
db.accountGroups().get(new AccountGroup.NameKey(newOwnerName));
groupCache.get(new AccountGroup.NameKey(newOwnerName));
if (owner == null) {
throw new Failure(new NoSuchEntityException());
}
@@ -187,25 +164,7 @@ class GroupAdminServiceImpl extends BaseServiceImplementation implements
public void renameGroup(final AccountGroup.Id groupId, final String newName,
final AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {
public VoidResult run(final ReviewDb db) throws OrmException, Failure {
final AccountGroup group = db.accountGroups().get(groupId);
assertAmGroupOwner(db, group);
final AccountGroup.NameKey oldKey = group.getNameKey();
final AccountGroup.NameKey newKey = new AccountGroup.NameKey(newName);
if (!newKey.equals(oldKey)) {
if (db.accountGroups().get(newKey) != null) {
throw new Failure(new NameAlreadyUsedException());
}
group.setNameKey(newKey);
db.accountGroups().update(Collections.singleton(group));
groupCache.evict(group);
groupCache.evictAfterRename(oldKey);
}
return VoidResult.INSTANCE;
}
});
renameGroupFactory.create(groupId, newName).to(callback);
}
public void changeGroupType(final AccountGroup.Id groupId,

View File

@@ -0,0 +1,99 @@
// Copyright (C) 2009 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.account;
import com.google.gerrit.common.errors.NameAlreadyUsedException;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AccountGroupName;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.account.NoSuchGroupException;
import com.google.gwtjsonrpc.client.VoidResult;
import com.google.gwtorm.client.OrmDuplicateKeyException;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.util.Collections;
class RenameGroup extends Handler<VoidResult> {
interface Factory {
RenameGroup create(AccountGroup.Id id, String newName);
}
private final ReviewDb db;
private final GroupCache groupCache;
private final GroupControl.Factory groupControlFactory;
private final AccountGroup.Id groupId;
private final String newName;
@Inject
RenameGroup(final ReviewDb db, final GroupCache groupCache,
final GroupControl.Factory groupControlFactory,
@Assisted final AccountGroup.Id groupId, @Assisted final String newName) {
this.db = db;
this.groupCache = groupCache;
this.groupControlFactory = groupControlFactory;
this.groupId = groupId;
this.newName = newName;
}
@Override
public VoidResult call() throws OrmException, NameAlreadyUsedException,
NoSuchGroupException {
final GroupControl ctl = groupControlFactory.validateFor(groupId);
final AccountGroup group = db.accountGroups().get(groupId);
if (group == null || !ctl.isOwner()) {
throw new NoSuchGroupException(groupId);
}
final AccountGroup.NameKey old = group.getNameKey();
final AccountGroup.NameKey key = new AccountGroup.NameKey(newName);
try {
final AccountGroupName id = new AccountGroupName(key, groupId);
db.accountGroupNames().insert(Collections.singleton(id));
} catch (OrmDuplicateKeyException dupeErr) {
// If we are using this identity, don't report the exception.
//
AccountGroupName other = db.accountGroupNames().get(key);
if (other != null && other.getId().equals(groupId)) {
return VoidResult.INSTANCE;
}
// Otherwise, someone else has this identity.
//
throw new NameAlreadyUsedException();
}
group.setNameKey(key);
db.accountGroups().update(Collections.singleton(group));
AccountGroupName priorName = db.accountGroupNames().get(old);
if (priorName != null) {
db.accountGroupNames().delete(Collections.singleton(priorName));
}
groupCache.evict(group);
groupCache.evictAfterRename(old);
return VoidResult.INSTANCE;
}
}

View File

@@ -23,6 +23,7 @@ import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ProjectRight;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.NoSuchGroupException;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
@@ -43,6 +44,7 @@ class AddProjectRight extends Handler<ProjectDetail> {
private final ProjectDetailFactory.Factory projectDetailFactory;
private final ProjectControl.Factory projectControlFactory;
private final ProjectCache projectCache;
private final GroupCache groupCache;
private final ReviewDb db;
private final ApprovalTypes approvalTypes;
@@ -55,8 +57,8 @@ class AddProjectRight extends Handler<ProjectDetail> {
@Inject
AddProjectRight(final ProjectDetailFactory.Factory projectDetailFactory,
final ProjectControl.Factory projectControlFactory,
final ProjectCache projectCache, final ReviewDb db,
final ApprovalTypes approvalTypes,
final ProjectCache projectCache, final GroupCache groupCache,
final ReviewDb db, final ApprovalTypes approvalTypes,
@Assisted final Project.NameKey projectName,
@Assisted final ApprovalCategory.Id categoryId,
@@ -65,6 +67,7 @@ class AddProjectRight extends Handler<ProjectDetail> {
this.projectDetailFactory = projectDetailFactory;
this.projectControlFactory = projectControlFactory;
this.projectCache = projectCache;
this.groupCache = groupCache;
this.approvalTypes = approvalTypes;
this.db = db;
@@ -104,7 +107,7 @@ class AddProjectRight extends Handler<ProjectDetail> {
+ " or range " + min + ".." + max);
}
final AccountGroup group = db.accountGroups().get(groupName);
final AccountGroup group = groupCache.get(groupName);
if (group == null) {
throw new NoSuchGroupException(groupName);
}