Line data Source code
1 : // Copyright (C) 2014 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.notedb; 16 : 17 : import static java.util.Objects.requireNonNull; 18 : 19 : import com.google.common.annotations.VisibleForTesting; 20 : import com.google.common.collect.ImmutableSet; 21 : import com.google.gerrit.common.Nullable; 22 : import com.google.gerrit.common.UsedAt; 23 : import com.google.gerrit.entities.Change; 24 : import com.google.gerrit.entities.Project; 25 : import com.google.gerrit.exceptions.StorageException; 26 : import com.google.gerrit.metrics.Timer0; 27 : import com.google.gerrit.server.config.AllUsersName; 28 : import com.google.gerrit.server.config.GerritImportedServerIds; 29 : import com.google.gerrit.server.config.GerritServerId; 30 : import com.google.gerrit.server.git.GitRepositoryManager; 31 : import com.google.gerrit.server.notedb.ChangeNotesCommit.ChangeNotesRevWalk; 32 : import com.google.gerrit.server.project.NoSuchChangeException; 33 : import com.google.inject.Inject; 34 : import com.google.inject.Provider; 35 : import com.google.inject.Singleton; 36 : import java.io.IOException; 37 : import java.util.concurrent.atomic.AtomicBoolean; 38 : import org.eclipse.jgit.errors.ConfigInvalidException; 39 : import org.eclipse.jgit.lib.ObjectId; 40 : import org.eclipse.jgit.lib.Ref; 41 : import org.eclipse.jgit.lib.Repository; 42 : 43 : /** View of contents at a single ref related to some change. * */ 44 : public abstract class AbstractChangeNotes<T> { 45 : @VisibleForTesting 46 : @Singleton 47 : @UsedAt(UsedAt.Project.PLUGIN_CHECKS) 48 : public static class Args { 49 : // TODO(dborowitz): Some less smelly way of disabling NoteDb in tests. 50 : public final AtomicBoolean failOnLoadForTest; 51 : public final ChangeNoteJson changeNoteJson; 52 : public final GitRepositoryManager repoManager; 53 : public final AllUsersName allUsers; 54 : public final NoteDbMetrics metrics; 55 : public final String serverId; 56 : public final ImmutableSet<String> importedServerIds; 57 : 58 : // Providers required to avoid dependency cycles. 59 : 60 : // ChangeNoteCache -> Args 61 : public final Provider<ChangeNotesCache> cache; 62 : 63 : @Inject 64 : Args( 65 : GitRepositoryManager repoManager, 66 : AllUsersName allUsers, 67 : ChangeNoteJson changeNoteJson, 68 : NoteDbMetrics metrics, 69 : Provider<ChangeNotesCache> cache, 70 : @GerritServerId String serverId, 71 152 : @GerritImportedServerIds ImmutableSet<String> importedServerIds) { 72 152 : this.failOnLoadForTest = new AtomicBoolean(); 73 152 : this.repoManager = repoManager; 74 152 : this.allUsers = allUsers; 75 152 : this.changeNoteJson = changeNoteJson; 76 152 : this.metrics = metrics; 77 152 : this.cache = cache; 78 152 : this.serverId = serverId; 79 152 : this.importedServerIds = importedServerIds; 80 152 : } 81 : } 82 : 83 : /** An {@link AutoCloseable} for parsing a single commit into ChangeNotesCommits. */ 84 : public static class LoadHandle implements AutoCloseable { 85 : private final Repository repo; 86 : private final ObjectId id; 87 : private ChangeNotesRevWalk rw; 88 : 89 103 : private LoadHandle(Repository repo, @Nullable ObjectId id) { 90 103 : this.repo = requireNonNull(repo); 91 : 92 103 : if (ObjectId.zeroId().equals(id)) { 93 0 : id = null; 94 103 : } else if (id != null) { 95 103 : id = id.copy(); 96 : } 97 103 : this.id = id; 98 103 : } 99 : 100 : public ChangeNotesRevWalk walk() { 101 103 : if (rw == null) { 102 103 : rw = ChangeNotesCommit.newRevWalk(repo); 103 : } 104 103 : return rw; 105 : } 106 : 107 : @Nullable 108 : public ObjectId id() { 109 103 : return id; 110 : } 111 : 112 : @Override 113 : public void close() { 114 103 : if (rw != null) { 115 103 : rw.close(); 116 : } 117 103 : } 118 : } 119 : 120 : protected final Args args; 121 : private final Change.Id changeId; 122 : 123 : private ObjectId revision; 124 : private boolean loaded; 125 : 126 103 : protected AbstractChangeNotes(Args args, Change.Id changeId, @Nullable ObjectId metaSha1) { 127 103 : this.args = requireNonNull(args); 128 103 : this.changeId = requireNonNull(changeId); 129 103 : this.revision = metaSha1; 130 103 : } 131 : 132 : protected AbstractChangeNotes(Args args, Change.Id changeId) { 133 0 : this(args, changeId, null); 134 0 : } 135 : 136 : public Change.Id getChangeId() { 137 103 : return changeId; 138 : } 139 : 140 : /** Returns revision of the metadata that was loaded. */ 141 : public ObjectId getRevision() { 142 19 : return revision; 143 : } 144 : 145 : public T load() { 146 103 : try (Repository repo = args.repoManager.openRepository(getProjectName())) { 147 103 : load(repo); 148 103 : return self(); 149 1 : } catch (IOException e) { 150 1 : throw new StorageException(e); 151 : } 152 : } 153 : 154 : public T load(Repository repo) { 155 103 : if (loaded) { 156 103 : return self(); 157 : } 158 : 159 103 : if (args.failOnLoadForTest.get()) { 160 0 : throw new StorageException("Reading from NoteDb is disabled"); 161 : } 162 103 : try (Timer0.Context timer = args.metrics.readLatency.start(); 163 : // Call openHandle even if reading is disabled, to trigger 164 : // auto-rebuilding before this object may get passed to a ChangeUpdate. 165 103 : LoadHandle handle = openHandle(repo, revision)) { 166 103 : revision = handle.id(); 167 103 : onLoad(handle); 168 103 : loaded = true; 169 1 : } catch (ConfigInvalidException | IOException e) { 170 1 : throw new StorageException(e); 171 103 : } 172 103 : return self(); 173 : } 174 : 175 : @Nullable 176 : protected ObjectId readRef(Repository repo) throws IOException { 177 103 : Ref ref = repo.getRefDatabase().exactRef(getRefName()); 178 103 : return ref != null ? ref.getObjectId() : null; 179 : } 180 : 181 : /** 182 : * Open a handle for reading this entity from a repository. 183 : * 184 : * <p>Implementations may override this method to provide auto-rebuilding behavior. 185 : * 186 : * @param repo open repository. 187 : * @param id SHA1 of the entity to read from the repository. The SHA1 is not sanity checked and is 188 : * assumed to be valid. If null, lookup SHA1 from the /meta ref. 189 : * @return handle for reading the entity. 190 : * @throws NoSuchChangeException change does not exist. 191 : * @throws IOException a repo-level error occurred. 192 : */ 193 : protected LoadHandle openHandle(Repository repo, @Nullable ObjectId id) 194 : throws NoSuchChangeException, IOException { 195 103 : if (id == null) { 196 103 : id = readRef(repo); 197 : } 198 : 199 103 : return new LoadHandle(repo, id); 200 : } 201 : 202 : public T reload() { 203 0 : loaded = false; 204 0 : return load(); 205 : } 206 : 207 : @Nullable 208 : public ObjectId loadRevision() { 209 8 : if (loaded) { 210 8 : return getRevision(); 211 : } 212 0 : try (Repository repo = args.repoManager.openRepository(getProjectName())) { 213 0 : Ref ref = repo.getRefDatabase().exactRef(getRefName()); 214 0 : return ref != null ? ref.getObjectId() : null; 215 0 : } catch (IOException e) { 216 0 : throw new StorageException(e); 217 : } 218 : } 219 : 220 : /** Load default values for any instance variables when NoteDb is disabled. */ 221 : protected abstract void loadDefaults(); 222 : 223 : /** 224 : * Returns the NameKey for the project where the notes should be stored, which is not necessarily 225 : * the same as the change's project. 226 : */ 227 : public abstract Project.NameKey getProjectName(); 228 : 229 : /** Returns name of the reference storing this configuration. */ 230 : protected abstract String getRefName(); 231 : 232 : /** Set up the metadata, parsing any state from the loaded revision. */ 233 : protected abstract void onLoad(LoadHandle handle) 234 : throws NoSuchChangeException, IOException, ConfigInvalidException; 235 : 236 : @SuppressWarnings("unchecked") 237 : protected final T self() { 238 103 : return (T) this; 239 : } 240 : }