Merge "Add new API to lookup accounts by email"

This commit is contained in:
Edwin Kempin
2017-07-25 15:20:10 +00:00
committed by Gerrit Code Review
7 changed files with 132 additions and 6 deletions

View File

@@ -74,6 +74,7 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.OutputFormat; import com.google.gerrit.server.OutputFormat;
import com.google.gerrit.server.PatchSetUtil; import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.account.AccountCache; import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.Accounts;
import com.google.gerrit.server.account.GroupCache; import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.change.Abandon; import com.google.gerrit.server.change.Abandon;
import com.google.gerrit.server.change.ChangeResource; import com.google.gerrit.server.change.ChangeResource;
@@ -200,6 +201,7 @@ public abstract class AbstractDaemonTest {
@Inject protected AcceptanceTestRequestScope atrScope; @Inject protected AcceptanceTestRequestScope atrScope;
@Inject protected AccountCache accountCache; @Inject protected AccountCache accountCache;
@Inject protected AccountCreator accountCreator; @Inject protected AccountCreator accountCreator;
@Inject protected Accounts accounts;
@Inject protected AllProjectsName allProjects; @Inject protected AllProjectsName allProjects;
@Inject protected BatchUpdate.Factory batchUpdateFactory; @Inject protected BatchUpdate.Factory batchUpdateFactory;
@Inject protected ChangeData.Factory changeDataFactory; @Inject protected ChangeData.Factory changeDataFactory;

View File

@@ -38,6 +38,7 @@ import static org.junit.Assert.fail;
import com.google.common.collect.FluentIterable; import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.io.BaseEncoding; import com.google.common.io.BaseEncoding;
import com.google.common.util.concurrent.AtomicLongMap; import com.google.common.util.concurrent.AtomicLongMap;
@@ -696,6 +697,33 @@ public class AccountIT extends AbstractDaemonTest {
assertThat(byEmailCache.get("non-existing@example.com")).isEmpty(); assertThat(byEmailCache.get("non-existing@example.com")).isEmpty();
} }
@Test
public void lookUpByEmail() throws Exception {
// exact match with scheme "mailto:"
assertEmail(accounts.byEmail(admin.email), admin);
// exact match with other scheme
String email = "foo.bar@example.com";
externalIdsUpdateFactory
.create()
.insert(ExternalId.createWithEmail(ExternalId.Key.parse("foo:bar"), admin.id, email));
assertEmail(accounts.byEmail(email), admin);
// wrong case doesn't match
assertThat(accounts.byEmail(admin.email.toUpperCase(Locale.US))).isEmpty();
// prefix doesn't match
assertThat(accounts.byEmail(admin.email.substring(0, admin.email.indexOf('@')))).isEmpty();
// non-existing doesn't match
assertThat(accounts.byEmail("non-existing@example.com")).isEmpty();
// lookup several accounts by email at once
ImmutableSetMultimap<String, Account.Id> byEmails = accounts.byEmails(admin.email, user.email);
assertEmail(byEmails.get(admin.email), admin);
assertEmail(byEmails.get(user.email), user);
}
@Test @Test
public void putStatus() throws Exception { public void putStatus() throws Exception {
List<String> statuses = ImmutableList.of("OOO", "Busy"); List<String> statuses = ImmutableList.of("OOO", "Busy");

View File

@@ -14,13 +14,18 @@
package com.google.gerrit.server.account; package com.google.gerrit.server.account;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.ImmutableSetMultimap.toImmutableSetMultimap;
import static java.util.Comparator.comparing; import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet; import static java.util.stream.Collectors.toSet;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.RefNames; import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.account.externalids.ExternalIds;
import com.google.gerrit.server.config.AllUsersName; import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.GitRepositoryManager;
@@ -32,6 +37,7 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.stream.Stream; import java.util.stream.Stream;
@@ -50,17 +56,20 @@ public class Accounts {
private final GitRepositoryManager repoManager; private final GitRepositoryManager repoManager;
private final AllUsersName allUsersName; private final AllUsersName allUsersName;
private final OutgoingEmailValidator emailValidator; private final OutgoingEmailValidator emailValidator;
private final ExternalIds externalIds;
@Inject @Inject
Accounts( Accounts(
@GerritServerConfig Config cfg, @GerritServerConfig Config cfg,
GitRepositoryManager repoManager, GitRepositoryManager repoManager,
AllUsersName allUsersName, AllUsersName allUsersName,
OutgoingEmailValidator emailValidator) { OutgoingEmailValidator emailValidator,
ExternalIds externalIds) {
this.readFromGit = cfg.getBoolean("user", null, "readAccountsFromGit", false); this.readFromGit = cfg.getBoolean("user", null, "readAccountsFromGit", false);
this.repoManager = repoManager; this.repoManager = repoManager;
this.allUsersName = allUsersName; this.allUsersName = allUsersName;
this.emailValidator = emailValidator; this.emailValidator = emailValidator;
this.externalIds = externalIds;
} }
public Account get(ReviewDb db, Account.Id accountId) public Account get(ReviewDb db, Account.Id accountId)
@@ -89,6 +98,44 @@ public class Accounts {
return db.accounts().get(accountIds).toList(); return db.accounts().get(accountIds).toList();
} }
/**
* Returns the accounts with the given email.
*
* <p>Each email should belong to a single account only. This means if more than one account is
* returned there is an inconsistency in the external IDs.
*
* <p>The accounts are retrieved via the external ID cache. Each access to the external ID cache
* requires reading the SHA1 of the refs/meta/external-ids branch. If accounts for multiple emails
* are needed it is more efficient to use {@link #byEmails(String...)} as this method reads the
* SHA1 of the refs/meta/external-ids branch only once (and not once per email).
*
* @see #byEmails(String...)
*/
public ImmutableSet<Account.Id> byEmail(String email) throws IOException {
return externalIds.byEmail(email).stream().map(e -> e.accountId()).collect(toImmutableSet());
}
/**
* Returns the accounts for the given emails.
*
* <p>Each email should belong to a single account only. This means if more than one account for
* an email is returned there is an inconsistency in the external IDs.
*
* <p>The accounts are retrieved via the external ID cache. Each access to the external ID cache
* requires reading the SHA1 of the refs/meta/external-ids branch. If accounts for multiple emails
* are needed it is more efficient to use this method instead of {@link #byEmail(String)} as this
* method reads the SHA1 of the refs/meta/external-ids branch only once (and not once per email).
*
* @see #byEmail(String)
*/
public ImmutableSetMultimap<String, Account.Id> byEmails(String... emails) throws IOException {
return externalIds
.byEmails(emails)
.entries()
.stream()
.collect(toImmutableSetMultimap(Map.Entry::getKey, e -> e.getValue().accountId()));
}
/** /**
* Returns all accounts. * Returns all accounts.
* *

View File

@@ -14,6 +14,7 @@
package com.google.gerrit.server.account.externalids; package com.google.gerrit.server.account.externalids;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Account;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
import com.google.inject.Module; import com.google.inject.Module;
@@ -89,7 +90,7 @@ public class DisabledExternalIdCache implements ExternalIdCache {
} }
@Override @Override
public Set<ExternalId> byEmail(String email) throws IOException { public ImmutableSetMultimap<String, ExternalId> byEmails(String... emails) throws IOException {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
} }

View File

@@ -14,6 +14,8 @@
package com.google.gerrit.server.account.externalids; package com.google.gerrit.server.account.externalids;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Account;
import java.io.IOException; import java.io.IOException;
import java.util.Collection; import java.util.Collection;
@@ -21,7 +23,12 @@ import java.util.Collections;
import java.util.Set; import java.util.Set;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
/** Caches external IDs of all accounts */ /**
* Caches external IDs of all accounts.
*
* <p>On each cache access the SHA1 of the refs/meta/external-ids branch is read to verify that the
* cache is up to date.
*/
interface ExternalIdCache { interface ExternalIdCache {
void onCreate(ObjectId oldNotesRev, ObjectId newNotesRev, Collection<ExternalId> extId) void onCreate(ObjectId oldNotesRev, ObjectId newNotesRev, Collection<ExternalId> extId)
throws IOException; throws IOException;
@@ -75,7 +82,11 @@ interface ExternalIdCache {
Set<ExternalId> byAccount(Account.Id accountId) throws IOException; Set<ExternalId> byAccount(Account.Id accountId) throws IOException;
Set<ExternalId> byEmail(String email) throws IOException; ImmutableSetMultimap<String, ExternalId> byEmails(String... emails) throws IOException;
default ImmutableSet<ExternalId> byEmail(String email) throws IOException {
return byEmails(email).get(email);
}
default void onCreate(ObjectId oldNotesRev, ObjectId newNotesRev, ExternalId extId) default void onCreate(ObjectId oldNotesRev, ObjectId newNotesRev, ExternalId extId)
throws IOException { throws IOException {

View File

@@ -226,9 +226,14 @@ class ExternalIdCacheImpl implements ExternalIdCache {
} }
@Override @Override
public Set<ExternalId> byEmail(String email) throws IOException { public ImmutableSetMultimap<String, ExternalId> byEmails(String... emails) throws IOException {
try { try {
return extIdsByAccount.get(externalIdReader.readRevision()).byEmail().get(email); AllExternalIds allExternalIds = extIdsByAccount.get(externalIdReader.readRevision());
ImmutableSetMultimap.Builder<String, ExternalId> byEmails = ImmutableSetMultimap.builder();
for (String email : emails) {
byEmails.putAll(email, allExternalIds.byEmail().get(email));
}
return byEmails.build();
} catch (ExecutionException e) { } catch (ExecutionException e) {
throw new IOException("Cannot list external ids by email", e); throw new IOException("Cannot list external ids by email", e);
} }

View File

@@ -16,6 +16,7 @@ package com.google.gerrit.server.account.externalids;
import static java.util.stream.Collectors.toSet; import static java.util.stream.Collectors.toSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.gerrit.common.Nullable; import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Account;
import com.google.inject.Inject; import com.google.inject.Inject;
@@ -74,7 +75,38 @@ public class ExternalIds {
return byAccount(accountId).stream().filter(e -> e.key().isScheme(scheme)).collect(toSet()); return byAccount(accountId).stream().filter(e -> e.key().isScheme(scheme)).collect(toSet());
} }
/**
* Returns the external ID with the given email.
*
* <p>Each email should belong to a single external ID only. This means if more than one external
* ID is returned there is an inconsistency in the external IDs.
*
* <p>The external IDs are retrieved from the external ID cache. Each access to the external ID
* cache requires reading the SHA1 of the refs/meta/external-ids branch. If external IDs for
* multiple emails are needed it is more efficient to use {@link #byEmails(String...)} as this
* method reads the SHA1 of the refs/meta/external-ids branch only once (and not once per email).
*
* @see #byEmails(String...)
*/
public Set<ExternalId> byEmail(String email) throws IOException { public Set<ExternalId> byEmail(String email) throws IOException {
return externalIdCache.byEmail(email); return externalIdCache.byEmail(email);
} }
/**
* Returns the external IDs for the given emails.
*
* <p>Each email should belong to a single external ID only. This means if more than one external
* ID for an email is returned there is an inconsistency in the external IDs.
*
* <p>The external IDs are retrieved from the external ID cache. Each access to the external ID
* cache requires reading the SHA1 of the refs/meta/external-ids branch. If external IDs for
* multiple emails are needed it is more efficient to use this method instead of {@link
* #byEmail(String)} as this method reads the SHA1 of the refs/meta/external-ids branch only once
* (and not once per email).
*
* @see #byEmail(String)
*/
public ImmutableSetMultimap<String, ExternalId> byEmails(String... emails) throws IOException {
return externalIdCache.byEmails(emails);
}
} }