Java tutorial
// Copyright (C) 2015 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; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.base.CharMatcher; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Splitter; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Iterables; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.reviewdb.client.RefNames; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.config.AllUsersName; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.index.change.ChangeField; import com.google.gerrit.server.index.change.ChangeIndexer; import com.google.gerrit.server.project.NoSuchChangeException; import com.google.gerrit.server.query.change.ChangeData; import com.google.gerrit.server.query.change.InternalChangeQuery; import com.google.gwtorm.server.ListResultSet; import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.ResultSet; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; import org.eclipse.jgit.lib.BatchRefUpdate; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefDatabase; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.transport.ReceiveCommand; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; @Singleton public class StarredChangesUtil { private static final Logger log = LoggerFactory.getLogger(StarredChangesUtil.class); private static final String DEFAULT_LABEL = "star"; public static final ImmutableSortedSet<String> DEFAULT_LABELS = ImmutableSortedSet.of(DEFAULT_LABEL); private final GitRepositoryManager repoManager; private final AllUsersName allUsers; private final Provider<ReviewDb> dbProvider; private final PersonIdent serverIdent; private final ChangeIndexer indexer; private final Provider<InternalChangeQuery> queryProvider; @Inject StarredChangesUtil(GitRepositoryManager repoManager, AllUsersName allUsers, Provider<ReviewDb> dbProvider, @GerritPersonIdent PersonIdent serverIdent, ChangeIndexer indexer, Provider<InternalChangeQuery> queryProvider) { this.repoManager = repoManager; this.allUsers = allUsers; this.dbProvider = dbProvider; this.serverIdent = serverIdent; this.indexer = indexer; this.queryProvider = queryProvider; } public void star(Account.Id accountId, Project.NameKey project, Change.Id changeId) throws OrmException { try (Repository repo = repoManager.openRepository(allUsers)) { String refName = RefNames.refsStarredChanges(changeId, accountId); ObjectId oldObjectId = getObjectId(repo, refName); SortedSet<String> labels = readLabels(repo, oldObjectId); labels.add(DEFAULT_LABEL); updateLabels(repo, refName, oldObjectId, labels); indexer.index(dbProvider.get(), project, changeId); } catch (IOException e) { throw new OrmException( String.format("Star change %d for account %d failed", changeId.get(), accountId.get()), e); } } public void unstar(Account.Id accountId, Project.NameKey project, Change.Id changeId) throws OrmException { try (Repository repo = repoManager.openRepository(allUsers); RevWalk rw = new RevWalk(repo)) { RefUpdate u = repo.updateRef(RefNames.refsStarredChanges(changeId, accountId)); u.setForceUpdate(true); u.setRefLogIdent(serverIdent); u.setRefLogMessage("Unstar change " + changeId.get(), true); RefUpdate.Result result = u.delete(); switch (result) { case FORCED: indexer.index(dbProvider.get(), project, changeId); return; case FAST_FORWARD: case IO_FAILURE: case LOCK_FAILURE: case NEW: case NOT_ATTEMPTED: case NO_CHANGE: case REJECTED: case REJECTED_CURRENT_BRANCH: case RENAMED: default: throw new OrmException(String.format("Unstar change %d for account %d failed: %s", changeId.get(), accountId.get(), result.name())); } } catch (IOException e) { throw new OrmException( String.format("Unstar change %d for account %d failed", changeId.get(), accountId.get()), e); } } public void unstarAll(Project.NameKey project, Change.Id changeId) throws OrmException, NoSuchChangeException { try (Repository repo = repoManager.openRepository(allUsers); RevWalk rw = new RevWalk(repo)) { BatchRefUpdate batchUpdate = repo.getRefDatabase().newBatchUpdate(); batchUpdate.setAllowNonFastForwards(true); batchUpdate.setRefLogIdent(serverIdent); batchUpdate.setRefLogMessage("Unstar change " + changeId.get(), true); for (Account.Id accountId : byChangeFromIndex(changeId)) { String refName = RefNames.refsStarredChanges(changeId, accountId); Ref ref = repo.getRefDatabase().getRef(refName); batchUpdate.addCommand(new ReceiveCommand(ref.getObjectId(), ObjectId.zeroId(), refName)); } batchUpdate.execute(rw, NullProgressMonitor.INSTANCE); for (ReceiveCommand command : batchUpdate.getCommands()) { if (command.getResult() != ReceiveCommand.Result.OK) { throw new IOException(String.format("Unstar change %d failed, ref %s could not be deleted: %s", changeId.get(), command.getRefName(), command.getResult())); } } indexer.index(dbProvider.get(), project, changeId); } catch (IOException e) { throw new OrmException(String.format("Unstar change %d failed", changeId.get()), e); } } public Set<Account.Id> byChange(Change.Id changeId) throws OrmException { return FluentIterable.from(getRefNames(RefNames.refsStarredChangesPrefix(changeId))) .transform(new Function<String, Account.Id>() { @Override public Account.Id apply(String refPart) { return Account.Id.parse(refPart); } }).toSet(); } public Set<Account.Id> byChangeFromIndex(Change.Id changeId) throws OrmException, NoSuchChangeException { Set<String> fields = ImmutableSet.of(ChangeField.ID.getName(), ChangeField.STARREDBY.getName()); List<ChangeData> changeData = queryProvider.get().setRequestedFields(fields).byLegacyChangeId(changeId); if (changeData.size() != 1) { throw new NoSuchChangeException(changeId); } return changeData.get(0).starredBy(); } @Deprecated public ResultSet<Change.Id> queryFromIndex(final Account.Id accountId) { try { Set<String> fields = ImmutableSet.of(ChangeField.ID.getName()); List<ChangeData> changeData = queryProvider.get().setRequestedFields(fields).byIsStarred(accountId); return new ListResultSet<>( FluentIterable.from(changeData).transform(new Function<ChangeData, Change.Id>() { @Override public Change.Id apply(ChangeData cd) { return cd.getId(); } }).toList()); } catch (OrmException | RuntimeException e) { log.warn(String.format("Cannot query starred changes for account %d", accountId.get()), e); List<Change.Id> empty = Collections.emptyList(); return new ListResultSet<>(empty); } } private Set<String> getRefNames(String prefix) throws OrmException { try (Repository repo = repoManager.openRepository(allUsers)) { RefDatabase refDb = repo.getRefDatabase(); return refDb.getRefs(prefix).keySet(); } catch (IOException e) { throw new OrmException(e); } } private static ObjectId getObjectId(Repository repo, String refName) throws IOException { Ref ref = repo.exactRef(refName); return ref != null ? ref.getObjectId() : ObjectId.zeroId(); } private static TreeSet<String> readLabels(Repository repo, ObjectId id) throws IOException { if (ObjectId.zeroId().equals(id)) { return new TreeSet<>(); } try (ObjectReader reader = repo.newObjectReader()) { ObjectLoader obj = reader.open(id, Constants.OBJ_BLOB); TreeSet<String> labels = new TreeSet<>(); Iterables.addAll(labels, Splitter.on(CharMatcher.whitespace()).omitEmptyStrings() .split(new String(obj.getCachedBytes(Integer.MAX_VALUE), UTF_8))); return labels; } } public static ObjectId writeLabels(Repository repo, SortedSet<String> labels) throws IOException { SortedSet<String> invalidLabels = validateLabels(labels); if (!invalidLabels.isEmpty()) { throw new IllegalArgumentException( String.format("Invalid star labels: %s", Joiner.on(", ").join(labels))); } try (ObjectInserter oi = repo.newObjectInserter()) { ObjectId id = oi.insert(Constants.OBJ_BLOB, Joiner.on("\n").join(labels).getBytes(UTF_8)); oi.flush(); return id; } } private static SortedSet<String> validateLabels(Set<String> labels) { if (labels == null) { return ImmutableSortedSet.of(); } SortedSet<String> invalidLabels = new TreeSet<>(); for (String label : labels) { if (CharMatcher.whitespace().matchesAnyOf(label)) { invalidLabels.add(label); } } return invalidLabels; } private void updateLabels(Repository repo, String refName, ObjectId oldObjectId, SortedSet<String> labels) throws IOException, OrmException { try (RevWalk rw = new RevWalk(repo)) { RefUpdate u = repo.updateRef(refName); u.setExpectedOldObjectId(oldObjectId); u.setForceUpdate(true); u.setNewObjectId(writeLabels(repo, labels)); u.setRefLogIdent(serverIdent); u.setRefLogMessage("Update star labels", true); RefUpdate.Result result = u.update(rw); switch (result) { case NEW: case FORCED: case NO_CHANGE: case FAST_FORWARD: return; case IO_FAILURE: case LOCK_FAILURE: case NOT_ATTEMPTED: case REJECTED: case REJECTED_CURRENT_BRANCH: case RENAMED: throw new OrmException( String.format("Update star labels on ref %s failed: %s", refName, result.name())); } } } }