| // Copyright (C) 2023 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.googlesource.gerrit.owners.common; |
| |
| import com.google.common.cache.RemovalNotification; |
| import com.google.gerrit.entities.RefNames; |
| import com.google.gerrit.extensions.annotations.PluginName; |
| import com.google.gerrit.extensions.events.GitBatchRefUpdateListener; |
| import com.google.gerrit.extensions.registration.DynamicSet; |
| import com.google.gerrit.server.cache.CacheModule; |
| import com.google.gerrit.server.cache.CacheRemovalListener; |
| import com.google.gerrit.server.config.AllUsersName; |
| import com.google.inject.Inject; |
| import com.google.inject.Module; |
| import com.google.inject.Singleton; |
| import com.google.inject.TypeLiteral; |
| import java.util.Objects; |
| import java.util.Optional; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.ExecutionException; |
| |
| public interface PathOwnersEntriesCache { |
| String CACHE_NAME = "path_owners_entries"; |
| |
| static Module module() { |
| return new CacheModule() { |
| @Override |
| protected void configure() { |
| cache(CACHE_NAME, Key.class, new TypeLiteral<Optional<OwnersConfig>>() {}); |
| bind(PathOwnersEntriesCache.class).to(PathOwnersEntriesCacheImpl.class); |
| DynamicSet.bind(binder(), GitBatchRefUpdateListener.class) |
| .to(OwnersRefUpdateListener.class); |
| DynamicSet.bind(binder(), CacheRemovalListener.class).to(OwnersCacheRemovalListener.class); |
| } |
| }; |
| } |
| |
| Optional<OwnersConfig> get( |
| String project, String branch, String path, Callable<Optional<OwnersConfig>> loader) |
| throws ExecutionException; |
| |
| void invalidate(String project, String branch); |
| |
| void invalidateIndexKey(Key key); |
| |
| class Key { |
| final String project; |
| final String branch; |
| final String path; |
| |
| Key(String project, String branch, String path) { |
| this.project = project; |
| this.branch = branch; |
| this.path = path; |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(branch, path, project); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| |
| if (obj == null) { |
| return false; |
| } |
| |
| if (getClass() != obj.getClass()) { |
| return false; |
| } |
| |
| Key other = (Key) obj; |
| return Objects.equals(branch, other.branch) |
| && Objects.equals(path, other.path) |
| && Objects.equals(project, other.project); |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder builder = new StringBuilder(); |
| builder.append("Key [project="); |
| builder.append(project); |
| builder.append(", branch="); |
| builder.append(branch); |
| builder.append(", path="); |
| builder.append(path); |
| builder.append("]"); |
| return builder.toString(); |
| } |
| } |
| |
| @Singleton |
| class OwnersRefUpdateListener implements GitBatchRefUpdateListener { |
| private final PathOwnersEntriesCache cache; |
| private final String allUsersName; |
| |
| @Inject |
| OwnersRefUpdateListener(PathOwnersEntriesCache cache, AllUsersName allUsersName) { |
| this.cache = cache; |
| this.allUsersName = allUsersName.get(); |
| } |
| |
| @Override |
| public void onGitBatchRefUpdate(GitBatchRefUpdateListener.Event event) { |
| String projectName = event.getProjectName(); |
| if (!allUsersName.equals(projectName)) { |
| event.getUpdatedRefs().stream() |
| .filter(refUpdate -> supportedEvent(refUpdate.getRefName())) |
| .forEach(refUpdate -> cache.invalidate(projectName, refUpdate.getRefName())); |
| } |
| } |
| |
| private boolean supportedEvent(String refName) { |
| return (refName.equals(RefNames.REFS_CONFIG) || !RefNames.isGerritRef(refName)); |
| } |
| } |
| |
| @Singleton |
| class OwnersCacheRemovalListener implements CacheRemovalListener<Key, PathOwnersEntry> { |
| private final PathOwnersEntriesCache cache; |
| private final String cacheName; |
| |
| @Inject |
| OwnersCacheRemovalListener(@PluginName String pluginName, PathOwnersEntriesCache cache) { |
| this.cache = cache; |
| this.cacheName = String.format("%s.%s", pluginName, CACHE_NAME); |
| } |
| |
| @Override |
| public void onRemoval( |
| String pluginName, |
| String cacheName, |
| RemovalNotification<Key, PathOwnersEntry> notification) { |
| if (!this.cacheName.equals(cacheName)) { |
| return; |
| } |
| |
| cache.invalidateIndexKey(notification.getKey()); |
| } |
| } |
| } |