Merge branch 'stable-2.15' into stable-2.16

* stable-2.15:
  Add support for NoteDb

Change-Id: I676633e5f075afb353e8f164152482aa72a52ebe
diff --git a/README.md b/README.md
index 1ed0bf5..e14491a 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,6 @@
 # Rename project plugin for Gerrit Code Review
 
-This plugin currently supports Gerrit version 2.14.X and 2.15.X with changes in reviewDb, noteDB is not yet supported.
+This plugin currently supports Gerrit version 2.14.X and 2.15.X with changes in reviewDb.
+Also supported is the noteDb alternative for Gerrit versions 2.15.X and above.
 
 For more information, see: `src/main/resources/Documentation/about.md`
diff --git a/src/main/java/com/googlesource/gerrit/plugins/renameproject/RenameProject.java b/src/main/java/com/googlesource/gerrit/plugins/renameproject/RenameProject.java
index ab34fd5..2a49cf0 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/renameproject/RenameProject.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/renameproject/RenameProject.java
@@ -185,7 +185,8 @@
     }
   }
 
-  List<Change.Id> getChanges(ProjectResource rsrc, ProgressMonitor pm) throws OrmException {
+  List<Change.Id> getChanges(ProjectResource rsrc, ProgressMonitor pm)
+      throws OrmException, IOException {
     pm.beginTask("Retrieving the list of changes from DB");
     Project.NameKey oldProjectKey = rsrc.getNameKey();
     return dbHandler.getChangeIds(oldProjectKey);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/renameproject/database/DatabaseRenameHandler.java b/src/main/java/com/googlesource/gerrit/plugins/renameproject/database/DatabaseRenameHandler.java
index 6995b84..01da143 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/renameproject/database/DatabaseRenameHandler.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/renameproject/database/DatabaseRenameHandler.java
@@ -25,6 +25,10 @@
 import com.google.gerrit.server.account.AccountsUpdate;
 import com.google.gerrit.server.account.ProjectWatches.NotifyType;
 import com.google.gerrit.server.account.ProjectWatches.ProjectWatchKey;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.notedb.ChangeNotes.Factory.ChangeNotesResult;
+import com.google.gerrit.server.notedb.NotesMigration;
 import com.google.gerrit.server.query.account.InternalAccountQuery;
 import com.google.gwtorm.jdbc.JdbcSchema;
 import com.google.gwtorm.server.OrmException;
@@ -40,9 +44,11 @@
 import java.sql.Statement;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Stream;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -52,22 +58,35 @@
   private static final Logger log = LoggerFactory.getLogger(DatabaseRenameHandler.class);
 
   private final SchemaFactory<ReviewDb> schemaFactory;
+  private final ChangeNotes.Factory schemaFactoryNoteDb;
+  private final GitRepositoryManager repoManager;
   private final Provider<InternalAccountQuery> accountQueryProvider;
   private final Provider<AccountsUpdate> accountsUpdateProvider;
+  private NotesMigration migration;
 
   @Inject
   public DatabaseRenameHandler(
       SchemaFactory<ReviewDb> schemaFactory,
+      ChangeNotes.Factory schemaFactoryNoteDb,
+      GitRepositoryManager repoManager,
+      NotesMigration migration,
       Provider<InternalAccountQuery> accountQueryProvider,
       @ServerInitiated Provider<AccountsUpdate> accountsUpdateProvider) {
     this.accountQueryProvider = accountQueryProvider;
     this.schemaFactory = schemaFactory;
+    this.schemaFactoryNoteDb = schemaFactoryNoteDb;
+    this.repoManager = repoManager;
     this.accountsUpdateProvider = accountsUpdateProvider;
+    this.migration = migration;
   }
 
-  public List<Change.Id> getChangeIds(Project.NameKey oldProjectKey) throws OrmException {
+  public List<Change.Id> getChangeIds(Project.NameKey oldProjectKey)
+      throws OrmException, IOException {
     log.debug("Starting to retrieve changes from the DB for project {}", oldProjectKey.get());
-    return getChangeIdsFromReviewDb(oldProjectKey, schemaFactory.open());
+    ReviewDb db = schemaFactory.open();
+    return (isNoteDb())
+        ? getChangeIdsFromNoteDb(oldProjectKey, db)
+        : getChangeIdsFromReviewDb(oldProjectKey, db);
   }
 
   private List<Change.Id> getChangeIdsFromReviewDb(Project.NameKey oldProjectKey, ReviewDb db)
@@ -83,7 +102,7 @@
         changeIds.add(changeId);
       }
       log.debug(
-          "Number of changes related to the project {} are {}",
+          "Number of changes in reviewDb related to the project {} are {}",
           oldProjectKey.get(),
           changeIds.size());
       return changeIds;
@@ -92,14 +111,38 @@
     }
   }
 
+  private List<Change.Id> getChangeIdsFromNoteDb(Project.NameKey oldProjectKey, ReviewDb db)
+      throws IOException {
+    List<Change.Id> changeIds = new ArrayList<>();
+    Stream<ChangeNotesResult> changes =
+        schemaFactoryNoteDb.scan(repoManager.openRepository(oldProjectKey), db, oldProjectKey);
+    Iterator<ChangeNotesResult> iterator = changes.iterator();
+    while (iterator.hasNext()) {
+      ChangeNotesResult change = iterator.next();
+      changeIds.add(change.id());
+    }
+    log.debug(
+        "Number of changes in noteDb related to the project {} are {}",
+        oldProjectKey.get(),
+        changeIds.size());
+    return changeIds;
+  }
+
+  private boolean isNoteDb() {
+    return migration.disableChangeReviewDb();
+  }
+
   public List<Change.Id> rename(
       List<Change.Id> changes,
       Project.NameKey oldProjectKey,
       Project.NameKey newProjectKey,
       ProgressMonitor pm)
-      throws OrmException {
+      throws OrmException, IOException {
     pm.beginTask("Updating changes in the database");
-    return renameInReviewDb(changes, oldProjectKey, newProjectKey, schemaFactory.open());
+    ReviewDb db = schemaFactory.open();
+    return (isNoteDb())
+        ? renameInNoteDb(changes, oldProjectKey, newProjectKey)
+        : renameInReviewDb(changes, oldProjectKey, newProjectKey, db);
   }
 
   private List<Change.Id> renameInReviewDb(
@@ -112,7 +155,7 @@
     try (Statement stmt = conn.createStatement()) {
       conn.setAutoCommit(false);
       try {
-        log.debug("Updating the changes in the DB related to project {}", oldProjectKey.get());
+        log.debug("Updating the changes in reviewDb related to project {}", oldProjectKey.get());
         for (Change.Id cd : changes) {
           stmt.addBatch(
               "update changes set dest_project_name='"
@@ -125,7 +168,7 @@
         updateWatchEntries(oldProjectKey, newProjectKey);
         conn.commit();
         log.debug(
-            "Successfully updated the changes in the DB related to project {}",
+            "Successfully updated the changes in reviewDb related to project {}",
             oldProjectKey.get());
         return changes;
       } finally {
@@ -134,7 +177,7 @@
     } catch (SQLException e) {
       try {
         log.error(
-            "Failed to update changes in the DB for the project {}, rolling back the operation.",
+            "Failed to update changes in reviewDb for the project {}, rolling back the operation.",
             oldProjectKey.get());
         conn.rollback();
       } catch (SQLException ex) {
@@ -144,6 +187,30 @@
     }
   }
 
+  private List<Change.Id> renameInNoteDb(
+      List<Change.Id> changes, Project.NameKey oldProjectKey, Project.NameKey newProjectKey)
+      throws OrmException {
+    log.debug("Updating the changes in noteDb related to project {}", oldProjectKey.get());
+    try {
+      updateWatchEntries(oldProjectKey, newProjectKey);
+    } catch (OrmException e) {
+      log.error(
+          "Failed to update changes in noteDb for the project {}, rolling back the operation.",
+          oldProjectKey.get());
+      try {
+        updateWatchEntries(newProjectKey, oldProjectKey);
+      } catch (OrmException ex) {
+        log.error("Failed to revert watched projects after catching {}", e.getMessage());
+        throw ex;
+      }
+      throw e;
+    }
+
+    log.debug(
+        "Successfully updated the changes in noteDb related to project {}", oldProjectKey.get());
+    return changes;
+  }
+
   private void updateWatchEntries(Project.NameKey oldProjectKey, Project.NameKey newProjectKey)
       throws OrmException {
     for (AccountState a : accountQueryProvider.get().byWatchedProject(oldProjectKey)) {