| // Copyright (C) 2020 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 static com.google.common.collect.ImmutableSet.toImmutableSet; |
| |
| import com.google.auto.value.AutoValue; |
| import com.google.common.base.Enums; |
| import com.google.common.base.Strings; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.gerrit.common.UsedAt; |
| import com.google.gerrit.entities.Account; |
| import com.google.gerrit.entities.NotifyConfig; |
| import com.google.gerrit.entities.Project; |
| import com.google.gerrit.proto.Protos; |
| import com.google.gerrit.server.cache.proto.Cache; |
| import com.google.gerrit.server.cache.serialize.CacheSerializer; |
| import com.google.gerrit.server.cache.serialize.ObjectIdConverter; |
| import com.google.gerrit.server.config.CachedPreferences; |
| import java.time.Instant; |
| import java.util.Map; |
| import java.util.Optional; |
| import org.eclipse.jgit.lib.ObjectId; |
| |
| /** Details of an account that are cached persistently in {@link AccountCache}. */ |
| @UsedAt(UsedAt.Project.GOOGLE) |
| @AutoValue |
| public abstract class CachedAccountDetails { |
| @AutoValue |
| public abstract static class Key { |
| public static Key create(Account.Id accountId, ObjectId id) { |
| return new AutoValue_CachedAccountDetails_Key(accountId, id.copy()); |
| } |
| |
| /** Identifier of the account. */ |
| public abstract Account.Id accountId(); |
| |
| /** |
| * Git revision at which the account was loaded. Corresponds to a revision on the account ref |
| * ({@code refs/users/<sharded-id>}). |
| */ |
| public abstract ObjectId id(); |
| |
| /** Serializer used to read this entity from and write it to a persistent storage. */ |
| public enum Serializer implements CacheSerializer<Key> { |
| INSTANCE; |
| |
| @Override |
| public byte[] serialize(Key object) { |
| return Protos.toByteArray( |
| Cache.AccountKeyProto.newBuilder() |
| .setAccountId(object.accountId().get()) |
| .setId(ObjectIdConverter.create().toByteString(object.id())) |
| .build()); |
| } |
| |
| @Override |
| public Key deserialize(byte[] in) { |
| Cache.AccountKeyProto proto = Protos.parseUnchecked(Cache.AccountKeyProto.parser(), in); |
| return Key.create( |
| Account.id(proto.getAccountId()), |
| ObjectIdConverter.create().fromByteString(proto.getId())); |
| } |
| } |
| } |
| |
| /** Essential attributes of the account, such as name or registration time. */ |
| public abstract Account account(); |
| |
| /** Projects that the user has configured to watch. */ |
| public abstract ImmutableMap< |
| ProjectWatches.ProjectWatchKey, ImmutableSet<NotifyConfig.NotifyType>> |
| projectWatches(); |
| |
| /** Preferences that this user has. Serialized as Git-config style string. */ |
| public abstract CachedPreferences preferences(); |
| |
| public static CachedAccountDetails create( |
| Account account, |
| ImmutableMap<ProjectWatches.ProjectWatchKey, ImmutableSet<NotifyConfig.NotifyType>> |
| projectWatches, |
| CachedPreferences preferences) { |
| return new AutoValue_CachedAccountDetails(account, projectWatches, preferences); |
| } |
| |
| /** Serializer used to read this entity from and write it to a persistent storage. */ |
| public enum Serializer implements CacheSerializer<CachedAccountDetails> { |
| INSTANCE; |
| |
| @Override |
| public byte[] serialize(CachedAccountDetails cachedAccountDetails) { |
| Cache.AccountDetailsProto.Builder serialized = Cache.AccountDetailsProto.newBuilder(); |
| // We don't care about the difference of empty strings and null in the Account entity. |
| Account account = cachedAccountDetails.account(); |
| Cache.AccountProto.Builder accountProto = |
| Cache.AccountProto.newBuilder() |
| .setId(account.id().get()) |
| .setRegisteredOn(account.registeredOn().toEpochMilli()) |
| .setInactive(account.inactive()) |
| .setFullName(Strings.nullToEmpty(account.fullName())) |
| .setDisplayName(Strings.nullToEmpty(account.displayName())) |
| .setPreferredEmail(Strings.nullToEmpty(account.preferredEmail())) |
| .setStatus(Strings.nullToEmpty(account.status())) |
| .setMetaId(Strings.nullToEmpty(account.metaId())) |
| .setUniqueTag(Strings.nullToEmpty(account.uniqueTag())); |
| serialized.setAccount(accountProto); |
| |
| for (Map.Entry<ProjectWatches.ProjectWatchKey, ImmutableSet<NotifyConfig.NotifyType>> watch : |
| cachedAccountDetails.projectWatches().entrySet()) { |
| Cache.ProjectWatchProto.Builder proto = |
| Cache.ProjectWatchProto.newBuilder().setProject(watch.getKey().project().get()); |
| if (watch.getKey().filter() != null) { |
| proto.setFilter(watch.getKey().filter()); |
| } |
| watch |
| .getValue() |
| .forEach( |
| n -> |
| proto.addNotifyType( |
| Enums.stringConverter(NotifyConfig.NotifyType.class).reverse().convert(n))); |
| serialized.addProjectWatchProto(proto); |
| } |
| |
| Optional<Cache.CachedPreferencesProto> cachedPreferencesProto = |
| cachedAccountDetails.preferences().nonEmptyConfig(); |
| if (cachedPreferencesProto.isPresent()) { |
| serialized.setUserPreferences(cachedPreferencesProto.get()); |
| } |
| return Protos.toByteArray(serialized.build()); |
| } |
| |
| @Override |
| public CachedAccountDetails deserialize(byte[] in) { |
| Cache.AccountDetailsProto proto = |
| Protos.parseUnchecked(Cache.AccountDetailsProto.parser(), in); |
| Account.Builder builder = |
| Account.builder( |
| Account.id(proto.getAccount().getId()), |
| Instant.ofEpochMilli(proto.getAccount().getRegisteredOn())) |
| .setFullName(Strings.emptyToNull(proto.getAccount().getFullName())) |
| .setDisplayName(Strings.emptyToNull(proto.getAccount().getDisplayName())) |
| .setPreferredEmail(Strings.emptyToNull(proto.getAccount().getPreferredEmail())) |
| .setInactive(proto.getAccount().getInactive()) |
| .setStatus(Strings.emptyToNull(proto.getAccount().getStatus())) |
| .setMetaId(Strings.emptyToNull(proto.getAccount().getMetaId())) |
| .setUniqueTag(Strings.emptyToNull(proto.getAccount().getUniqueTag())); |
| if (Strings.isNullOrEmpty(builder.uniqueTag())) { |
| builder.setUniqueTag(builder.metaId()); |
| } |
| Account account = builder.build(); |
| |
| ImmutableMap.Builder<ProjectWatches.ProjectWatchKey, ImmutableSet<NotifyConfig.NotifyType>> |
| projectWatches = ImmutableMap.builder(); |
| proto.getProjectWatchProtoList().stream() |
| .forEach( |
| p -> |
| projectWatches.put( |
| ProjectWatches.ProjectWatchKey.create( |
| Project.nameKey(p.getProject()), p.getFilter()), |
| p.getNotifyTypeList().stream() |
| .map(e -> Enums.stringConverter(NotifyConfig.NotifyType.class).convert(e)) |
| .collect(toImmutableSet()))); |
| |
| return CachedAccountDetails.create( |
| account, |
| projectWatches.build(), |
| CachedPreferences.fromCachedPreferencesProto(proto.getUserPreferences())); |
| } |
| } |
| } |