| // Copyright (C) 2016 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.schema; |
| |
| import static com.google.common.base.Preconditions.checkState; |
| |
| import com.google.auto.value.AutoValue; |
| import com.google.common.base.Strings; |
| import com.google.common.collect.ListMultimap; |
| import com.google.common.collect.MultimapBuilder; |
| import com.google.gerrit.common.Nullable; |
| import com.google.gerrit.reviewdb.client.Account; |
| import com.google.gerrit.reviewdb.client.Project; |
| import com.google.gerrit.reviewdb.server.ReviewDb; |
| import com.google.gerrit.server.GerritPersonIdent; |
| import com.google.gerrit.server.account.AccountConfig; |
| import com.google.gerrit.server.account.InternalAccountUpdate; |
| import com.google.gerrit.server.account.ProjectWatches.NotifyType; |
| import com.google.gerrit.server.account.ProjectWatches.ProjectWatchKey; |
| import com.google.gerrit.server.config.AllUsersName; |
| import com.google.gerrit.server.extensions.events.GitReferenceUpdated; |
| import com.google.gerrit.server.git.GitRepositoryManager; |
| import com.google.gerrit.server.git.meta.MetaDataUpdate; |
| import com.google.gwtorm.jdbc.JdbcSchema; |
| import com.google.gwtorm.server.OrmDuplicateKeyException; |
| import com.google.gwtorm.server.OrmException; |
| import com.google.inject.Inject; |
| import com.google.inject.Provider; |
| import java.io.IOException; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.sql.Statement; |
| import java.util.Collection; |
| import java.util.EnumSet; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Set; |
| import org.eclipse.jgit.errors.ConfigInvalidException; |
| import org.eclipse.jgit.lib.BatchRefUpdate; |
| import org.eclipse.jgit.lib.NullProgressMonitor; |
| import org.eclipse.jgit.lib.ObjectInserter; |
| import org.eclipse.jgit.lib.ObjectReader; |
| import org.eclipse.jgit.lib.PersonIdent; |
| import org.eclipse.jgit.lib.Repository; |
| import org.eclipse.jgit.revwalk.RevWalk; |
| |
| public class Schema_139 extends SchemaVersion { |
| private static final String MSG = "Migrate project watches to git"; |
| |
| private final GitRepositoryManager repoManager; |
| private final AllUsersName allUsersName; |
| private final PersonIdent serverUser; |
| |
| @Inject |
| Schema_139( |
| Provider<Schema_138> prior, |
| GitRepositoryManager repoManager, |
| AllUsersName allUsersName, |
| @GerritPersonIdent PersonIdent serverUser) { |
| super(prior); |
| this.repoManager = repoManager; |
| this.allUsersName = allUsersName; |
| this.serverUser = serverUser; |
| } |
| |
| @Override |
| protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException { |
| ListMultimap<Account.Id, ProjectWatch> imports = |
| MultimapBuilder.hashKeys().arrayListValues().build(); |
| try (Statement stmt = ((JdbcSchema) db).getConnection().createStatement(); |
| ResultSet rs = |
| stmt.executeQuery( |
| "SELECT " |
| + "account_project_watches.account_id, " |
| + "project_name, " |
| + "filter, " |
| + "notify_abandoned_changes, " |
| + "notify_all_comments, " |
| + "notify_new_changes, " |
| + "notify_new_patch_sets, " |
| + "notify_submitted_changes " |
| + "FROM account_project_watches " |
| + "JOIN accounts ON account_project_watches.account_id=accounts.account_id")) { |
| while (rs.next()) { |
| Account.Id accountId = new Account.Id(rs.getInt(1)); |
| ProjectWatch.Builder b = |
| ProjectWatch.builder() |
| .project(new Project.NameKey(rs.getString(2))) |
| .filter(rs.getString(3)) |
| .notifyAbandonedChanges(toBoolean(rs.getString(4))) |
| .notifyAllComments(toBoolean(rs.getString(5))) |
| .notifyNewChanges(toBoolean(rs.getString(6))) |
| .notifyNewPatchSets(toBoolean(rs.getString(7))) |
| .notifySubmittedChanges(toBoolean(rs.getString(8))); |
| imports.put(accountId, b.build()); |
| } |
| } |
| |
| if (imports.isEmpty()) { |
| return; |
| } |
| |
| try (Repository git = repoManager.openRepository(allUsersName); |
| ObjectInserter inserter = getPackInserterFirst(git); |
| ObjectReader reader = inserter.newReader(); |
| RevWalk rw = new RevWalk(reader)) { |
| BatchRefUpdate bru = git.getRefDatabase().newBatchUpdate(); |
| bru.setRefLogIdent(serverUser); |
| bru.setRefLogMessage(MSG, false); |
| |
| for (Map.Entry<Account.Id, Collection<ProjectWatch>> e : imports.asMap().entrySet()) { |
| Map<ProjectWatchKey, Set<NotifyType>> projectWatches = new HashMap<>(); |
| for (ProjectWatch projectWatch : e.getValue()) { |
| ProjectWatchKey key = |
| ProjectWatchKey.create(projectWatch.project(), projectWatch.filter()); |
| if (projectWatches.containsKey(key)) { |
| throw new OrmDuplicateKeyException( |
| "Duplicate key for watched project: " + key.toString()); |
| } |
| Set<NotifyType> notifyValues = EnumSet.noneOf(NotifyType.class); |
| if (projectWatch.notifyAbandonedChanges()) { |
| notifyValues.add(NotifyType.ABANDONED_CHANGES); |
| } |
| if (projectWatch.notifyAllComments()) { |
| notifyValues.add(NotifyType.ALL_COMMENTS); |
| } |
| if (projectWatch.notifyNewChanges()) { |
| notifyValues.add(NotifyType.NEW_CHANGES); |
| } |
| if (projectWatch.notifyNewPatchSets()) { |
| notifyValues.add(NotifyType.NEW_PATCHSETS); |
| } |
| if (projectWatch.notifySubmittedChanges()) { |
| notifyValues.add(NotifyType.SUBMITTED_CHANGES); |
| } |
| projectWatches.put(key, notifyValues); |
| } |
| |
| try (MetaDataUpdate md = |
| new MetaDataUpdate(GitReferenceUpdated.DISABLED, allUsersName, git, bru)) { |
| md.getCommitBuilder().setAuthor(serverUser); |
| md.getCommitBuilder().setCommitter(serverUser); |
| md.setMessage(MSG); |
| |
| AccountConfig accountConfig = new AccountConfig(e.getKey(), allUsersName, git); |
| accountConfig.load(md); |
| accountConfig.setAccountUpdate( |
| InternalAccountUpdate.builder() |
| .deleteProjectWatches(accountConfig.getProjectWatches().keySet()) |
| .updateProjectWatches(projectWatches) |
| .build()); |
| accountConfig.commit(md, inserter, reader, rw); |
| } |
| } |
| inserter.flush(); |
| bru.execute(rw, NullProgressMonitor.INSTANCE); |
| } catch (IOException | ConfigInvalidException ex) { |
| throw new OrmException(ex); |
| } |
| } |
| |
| @AutoValue |
| abstract static class ProjectWatch { |
| abstract Project.NameKey project(); |
| |
| abstract @Nullable String filter(); |
| |
| abstract boolean notifyAbandonedChanges(); |
| |
| abstract boolean notifyAllComments(); |
| |
| abstract boolean notifyNewChanges(); |
| |
| abstract boolean notifyNewPatchSets(); |
| |
| abstract boolean notifySubmittedChanges(); |
| |
| static Builder builder() { |
| return new AutoValue_Schema_139_ProjectWatch.Builder(); |
| } |
| |
| @AutoValue.Builder |
| abstract static class Builder { |
| abstract Builder project(Project.NameKey project); |
| |
| abstract Builder filter(@Nullable String filter); |
| |
| abstract Builder notifyAbandonedChanges(boolean notifyAbandonedChanges); |
| |
| abstract Builder notifyAllComments(boolean notifyAllComments); |
| |
| abstract Builder notifyNewChanges(boolean notifyNewChanges); |
| |
| abstract Builder notifyNewPatchSets(boolean notifyNewPatchSets); |
| |
| abstract Builder notifySubmittedChanges(boolean notifySubmittedChanges); |
| |
| abstract ProjectWatch build(); |
| } |
| } |
| |
| private static boolean toBoolean(String v) { |
| checkState(!Strings.isNullOrEmpty(v)); |
| return v.equals("Y"); |
| } |
| } |