Line data Source code
1 : // Copyright (C) 2009 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.account; 16 : 17 : import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME; 18 : 19 : import com.google.common.cache.CacheLoader; 20 : import com.google.common.cache.LoadingCache; 21 : import com.google.common.collect.ImmutableMap; 22 : import com.google.common.collect.Sets; 23 : import com.google.common.flogger.FluentLogger; 24 : import com.google.gerrit.entities.Account; 25 : import com.google.gerrit.entities.RefNames; 26 : import com.google.gerrit.exceptions.StorageException; 27 : import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory; 28 : import com.google.gerrit.server.account.externalids.ExternalIds; 29 : import com.google.gerrit.server.cache.CacheModule; 30 : import com.google.gerrit.server.config.AllUsersName; 31 : import com.google.gerrit.server.config.CachedPreferences; 32 : import com.google.gerrit.server.config.DefaultPreferencesCache; 33 : import com.google.gerrit.server.git.GitRepositoryManager; 34 : import com.google.gerrit.server.logging.Metadata; 35 : import com.google.gerrit.server.logging.TraceContext; 36 : import com.google.gerrit.server.logging.TraceContext.TraceTimer; 37 : import com.google.gerrit.server.util.time.TimeUtil; 38 : import com.google.inject.Inject; 39 : import com.google.inject.Module; 40 : import com.google.inject.Singleton; 41 : import com.google.inject.name.Named; 42 : import java.io.IOException; 43 : import java.util.Collections; 44 : import java.util.Map; 45 : import java.util.Optional; 46 : import java.util.Set; 47 : import java.util.concurrent.ExecutionException; 48 : import org.eclipse.jgit.lib.ObjectId; 49 : import org.eclipse.jgit.lib.Ref; 50 : import org.eclipse.jgit.lib.Repository; 51 : 52 : /** Caches important (but small) account state to avoid database hits. */ 53 : @Singleton 54 : public class AccountCacheImpl implements AccountCache { 55 152 : private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 56 : 57 : private static final String BYID_AND_REV_NAME = "accounts"; 58 : 59 : public static Module module() { 60 152 : return new CacheModule() { 61 : @Override 62 : protected void configure() { 63 152 : persist(BYID_AND_REV_NAME, CachedAccountDetails.Key.class, CachedAccountDetails.class) 64 152 : .version(1) 65 152 : .keySerializer(CachedAccountDetails.Key.Serializer.INSTANCE) 66 152 : .valueSerializer(CachedAccountDetails.Serializer.INSTANCE) 67 152 : .loader(Loader.class); 68 : 69 152 : bind(AccountCacheImpl.class); 70 152 : bind(AccountCache.class).to(AccountCacheImpl.class); 71 152 : } 72 : }; 73 : } 74 : 75 : private final ExternalIds externalIds; 76 : private final LoadingCache<CachedAccountDetails.Key, CachedAccountDetails> accountDetailsCache; 77 : private final GitRepositoryManager repoManager; 78 : private final AllUsersName allUsersName; 79 : private final DefaultPreferencesCache defaultPreferenceCache; 80 : private final ExternalIdKeyFactory externalIdKeyFactory; 81 : 82 : @Inject 83 : AccountCacheImpl( 84 : ExternalIds externalIds, 85 : @Named(BYID_AND_REV_NAME) 86 : LoadingCache<CachedAccountDetails.Key, CachedAccountDetails> accountDetailsCache, 87 : GitRepositoryManager repoManager, 88 : AllUsersName allUsersName, 89 : DefaultPreferencesCache defaultPreferenceCache, 90 152 : ExternalIdKeyFactory externalIdKeyFactory) { 91 152 : this.externalIds = externalIds; 92 152 : this.accountDetailsCache = accountDetailsCache; 93 152 : this.repoManager = repoManager; 94 152 : this.allUsersName = allUsersName; 95 152 : this.defaultPreferenceCache = defaultPreferenceCache; 96 152 : this.externalIdKeyFactory = externalIdKeyFactory; 97 152 : } 98 : 99 : @Override 100 : public AccountState getEvenIfMissing(Account.Id accountId) { 101 151 : return get(accountId).orElse(missing(accountId)); 102 : } 103 : 104 : @Override 105 : public Optional<AccountState> get(Account.Id accountId) { 106 151 : return Optional.ofNullable(get(Collections.singleton(accountId)).getOrDefault(accountId, null)); 107 : } 108 : 109 : @Override 110 : public AccountState getFromMetaId(Account.Id id, ObjectId metaId) { 111 : try { 112 1 : CachedAccountDetails.Key key = CachedAccountDetails.Key.create(id, metaId); 113 : 114 1 : CachedAccountDetails accountDetails = accountDetailsCache.get(key); 115 1 : return AccountState.forCachedAccount(accountDetails, CachedPreferences.EMPTY, externalIds); 116 0 : } catch (IOException | ExecutionException e) { 117 0 : throw new StorageException(e); 118 : } 119 : } 120 : 121 : @Override 122 : public Map<Account.Id, AccountState> get(Set<Account.Id> accountIds) { 123 : try { 124 151 : try (Repository allUsers = repoManager.openRepository(allUsersName)) { 125 : // Get the default preferences for this Gerrit host 126 151 : Ref ref = allUsers.exactRef(RefNames.REFS_USERS_DEFAULT); 127 : CachedPreferences defaultPreferences = 128 151 : ref != null 129 2 : ? defaultPreferenceCache.get(ref.getObjectId()) 130 151 : : DefaultPreferencesCache.EMPTY; 131 : 132 151 : Set<CachedAccountDetails.Key> keys = 133 151 : Sets.newLinkedHashSetWithExpectedSize(accountIds.size()); 134 151 : for (Account.Id id : accountIds) { 135 151 : Ref userRef = allUsers.exactRef(RefNames.refsUsers(id)); 136 151 : if (userRef == null) { 137 43 : continue; 138 : } 139 151 : keys.add(CachedAccountDetails.Key.create(id, userRef.getObjectId())); 140 151 : } 141 151 : ImmutableMap.Builder<Account.Id, AccountState> result = ImmutableMap.builder(); 142 : for (Map.Entry<CachedAccountDetails.Key, CachedAccountDetails> account : 143 151 : accountDetailsCache.getAll(keys).entrySet()) { 144 151 : result.put( 145 151 : account.getKey().accountId(), 146 151 : AccountState.forCachedAccount(account.getValue(), defaultPreferences, externalIds)); 147 151 : } 148 151 : return result.build(); 149 : } 150 0 : } catch (IOException | ExecutionException e) { 151 0 : throw new StorageException(e); 152 : } 153 : } 154 : 155 : @Override 156 : public Optional<AccountState> getByUsername(String username) { 157 : try { 158 72 : return externalIds 159 72 : .get(externalIdKeyFactory.create(SCHEME_USERNAME, username)) 160 72 : .map(e -> get(e.accountId())) 161 72 : .orElseGet(Optional::empty); 162 0 : } catch (IOException e) { 163 0 : logger.atWarning().withCause(e).log("Cannot load AccountState for username %s", username); 164 0 : return Optional.empty(); 165 : } 166 : } 167 : 168 : private AccountState missing(Account.Id accountId) { 169 151 : Account.Builder account = Account.builder(accountId, TimeUtil.now()); 170 151 : account.setActive(false); 171 151 : return AccountState.forAccount(account.build()); 172 : } 173 : 174 : @Singleton 175 : static class Loader extends CacheLoader<CachedAccountDetails.Key, CachedAccountDetails> { 176 : private final GitRepositoryManager repoManager; 177 : private final AllUsersName allUsersName; 178 : 179 : @Inject 180 152 : Loader(GitRepositoryManager repoManager, AllUsersName allUsersName) { 181 152 : this.repoManager = repoManager; 182 152 : this.allUsersName = allUsersName; 183 152 : } 184 : 185 : @Override 186 : public CachedAccountDetails load(CachedAccountDetails.Key key) throws Exception { 187 151 : try (TraceTimer ignored = 188 151 : TraceContext.newTimer( 189 151 : "Loading account", Metadata.builder().accountId(key.accountId().get()).build()); 190 151 : Repository repo = repoManager.openRepository(allUsersName)) { 191 151 : AccountConfig cfg = new AccountConfig(key.accountId(), allUsersName, repo).load(key.id()); 192 151 : Account account = 193 151 : cfg.getLoadedAccount() 194 151 : .orElseThrow(() -> new AccountNotFoundException(key.accountId() + " not found")); 195 151 : return CachedAccountDetails.create( 196 151 : account, cfg.getProjectWatches(), cfg.asCachedPreferences()); 197 : } 198 : } 199 : } 200 : 201 : /** Signals that the account was not found in the primary storage. */ 202 : private static class AccountNotFoundException extends Exception { 203 : private static final long serialVersionUID = 1L; 204 : 205 : public AccountNotFoundException(String message) { 206 0 : super(message); 207 0 : } 208 : } 209 : }