This is an automated email from the ASF dual-hosted git repository.

bruno-roustant pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr-sandbox.git


The following commit(s) were added to refs/heads/main by this push:
     new 0d007c3  Add isIndexEncrypted flag and encryption listener. (#128)
0d007c3 is described below

commit 0d007c3589011e1fab04d6d48e7f60786b287d99
Author: Bruno Roustant <[email protected]>
AuthorDate: Fri May 22 15:23:55 2026 +0200

    Add isIndexEncrypted flag and encryption listener. (#128)
---
 .../solr/encryption/EncryptionDirectory.java       |  46 ++-
 .../encryption/EncryptionDirectoryFactory.java     |  29 +-
 .../solr/encryption/EncryptionMergePolicy.java     |   7 +-
 .../solr/encryption/EncryptionUpdateLog.java       |   2 +-
 .../encryption/EncryptionBackupRepositoryTest.java |  16 +-
 .../solr/encryption/EncryptionDirectoryTest.java   |  14 +-
 .../EncryptionMergePolicyFactoryTest.java          |  22 +-
 .../solr/encryption/EncryptionMergePolicyTest.java | 388 +++++++++++----------
 .../encryption/EncryptionRequestHandlerTest.java   |  15 +-
 9 files changed, 325 insertions(+), 214 deletions(-)

diff --git 
a/encryption/src/main/java/org/apache/solr/encryption/EncryptionDirectory.java 
b/encryption/src/main/java/org/apache/solr/encryption/EncryptionDirectory.java
index fe4cd26..cb364b2 100644
--- 
a/encryption/src/main/java/org/apache/solr/encryption/EncryptionDirectory.java
+++ 
b/encryption/src/main/java/org/apache/solr/encryption/EncryptionDirectory.java
@@ -90,6 +90,8 @@ public class EncryptionDirectory extends FilterDirectory {
 
   protected final KeySupplier keySupplier;
 
+  protected final EncryptionListener encryptionListener;
+
   /** Cache of the latest commit user data. */
   protected volatile CommitUserData commitUserData;
 
@@ -100,14 +102,20 @@ public class EncryptionDirectory extends FilterDirectory {
    * Creates an {@link EncryptionDirectory} which wraps a delegate {@link 
Directory} to encrypt/decrypt
    * files on the fly.
    *
-   * @param encrypterFactory creates {@link AesCtrEncrypter}.
-   * @param keySupplier      provides the key secrets and determines which 
files should be encrypted.
+   * @param encrypterFactory   creates {@link AesCtrEncrypter}.
+   * @param keySupplier        provides the key secrets and determines which 
files should be encrypted.
+   * @param encryptionListener notified when the index is encrypted.
    */
-  public EncryptionDirectory(Directory delegate, AesCtrEncrypterFactory 
encrypterFactory, KeySupplier keySupplier)
+  public EncryptionDirectory(
+      Directory delegate,
+      AesCtrEncrypterFactory encrypterFactory,
+      KeySupplier keySupplier,
+      EncryptionListener encryptionListener)
     throws IOException {
     super(delegate);
     this.encrypterFactory = encrypterFactory;
     this.keySupplier = keySupplier;
+    this.encryptionListener = encryptionListener;
     commitUserData = readLatestCommitUserData();
   }
 
@@ -148,6 +156,7 @@ public class EncryptionDirectory extends FilterDirectory {
     try {
       String keyRef = getActiveKeyRefFromCommit(getLatestCommitData().data);
       if (keyRef != null) {
+        encryptionListener.onEncryption();
         // Get the key secret first. If it fails, we do not write anything.
         byte[] keySecret = getKeySecret(keyRef);
         // The IndexOutput has to be wrapped to be encrypted with the key.
@@ -173,10 +182,17 @@ public class EncryptionDirectory extends FilterDirectory {
   /**
    * Forces this {@link EncryptionDirectory} to read the user data of the 
latest commit, to refresh its cache.
    */
-  public void forceReadCommitUserData() {
+  public void clearCachedCommitUserData() {
     shouldReadCommitUserData = true;
   }
 
+  /**
+   * Clears the cached encryption status for logs.
+   */
+  public void clearCachedEncryptionStatus() {
+    encryptionListener.clearEncryptionStatus();
+  }
+
   /**
    * Gets the user data from the latest commit, potentially reading the latest 
commit if the cache is stale.
    */
@@ -255,6 +271,7 @@ public class EncryptionDirectory extends FilterDirectory {
     try {
       String keyRef = getKeyRefForReading(indexInput);
       if (keyRef != null) {
+        encryptionListener.onEncryption();
         // The IndexInput has to be wrapped to be decrypted with the key.
         indexInput = new DecryptingIndexInput(indexInput, 
getKeySecret(keyRef), encrypterFactory);
       }
@@ -371,4 +388,25 @@ public class EncryptionDirectory extends FilterDirectory {
       keyCookies = getKeyCookiesFromCommit(data);
     }
   }
+
+  /**
+   * Notified when the index is encrypted.
+   */
+  public interface EncryptionListener {
+
+    EncryptionListener NO_LISTENER = new EncryptionListener() {
+
+      @Override
+      public void onEncryption() {
+      }
+
+      @Override
+      public void clearEncryptionStatus() {
+      }
+    };
+
+    void onEncryption();
+
+    void clearEncryptionStatus();
+  }
 }
diff --git 
a/encryption/src/main/java/org/apache/solr/encryption/EncryptionDirectoryFactory.java
 
b/encryption/src/main/java/org/apache/solr/encryption/EncryptionDirectoryFactory.java
index 4232723..5d12933 100644
--- 
a/encryption/src/main/java/org/apache/solr/encryption/EncryptionDirectoryFactory.java
+++ 
b/encryption/src/main/java/org/apache/solr/encryption/EncryptionDirectoryFactory.java
@@ -24,6 +24,7 @@ import org.apache.solr.common.util.NamedList;
 import org.apache.solr.core.DirectoryFactory;
 import org.apache.solr.core.MMapDirectoryFactory;
 import org.apache.solr.core.SolrCore;
+import org.apache.solr.encryption.EncryptionDirectory.EncryptionListener;
 import org.apache.solr.encryption.crypto.AesCtrEncrypterFactory;
 import org.apache.solr.encryption.crypto.CipherAesCtrEncrypter;
 
@@ -74,9 +75,20 @@ public class EncryptionDirectoryFactory extends 
MMapDirectoryFactory {
    */
   static final String PROPERTY_INNER_ENCRYPTION_DIRECTORY_FACTORY = 
"innerEncryptionDirectoryFactory";
 
+  private final EncryptionListener encryptionListener = new 
EncryptionListener() {
+    @Override
+    public void onEncryption() {
+      indexEncrypted = true;
+    }
+    @Override
+    public void clearEncryptionStatus() {
+      indexEncrypted = false;
+    }
+  };
   private KeySupplier keySupplier;
   private AesCtrEncrypterFactory encrypterFactory;
   private InnerFactory innerFactory;
+  private volatile boolean indexEncrypted;
 
   public EncryptionDirectoryFactory() {}
 
@@ -145,6 +157,15 @@ public class EncryptionDirectoryFactory extends 
MMapDirectoryFactory {
     return keySupplier;
   }
 
+  /**
+   * Returns whether the index is encrypted.
+   * This flag is set when the {@link EncryptionDirectory} opens an 
output/input stream that
+   * requires encryption.
+   */
+  public boolean isIndexEncrypted() {
+    return indexEncrypted;
+  }
+
   public static EncryptionDirectoryFactory getFactory(SolrCore core) {
     if (!(core.getDirectoryFactory() instanceof EncryptionDirectoryFactory)) {
       throw new SolrException(SolrException.ErrorCode.SERVICE_UNAVAILABLE,
@@ -157,7 +178,7 @@ public class EncryptionDirectoryFactory extends 
MMapDirectoryFactory {
 
   @Override
   protected Directory create(String path, LockFactory lockFactory, DirContext 
dirContext) throws IOException {
-    return innerFactory.create(super.create(path, lockFactory, dirContext), 
getEncrypterFactory(), getKeySupplier());
+    return innerFactory.create(super.create(path, lockFactory, dirContext), 
getEncrypterFactory(), getKeySupplier(), encryptionListener);
   }
 
   @Override
@@ -178,7 +199,11 @@ public class EncryptionDirectoryFactory extends 
MMapDirectoryFactory {
    * Visible for tests only - Inner factory that creates {@link 
EncryptionDirectory} instances.
    */
   interface InnerFactory {
-    EncryptionDirectory create(Directory delegate, AesCtrEncrypterFactory 
encrypterFactory, KeySupplier keySupplier)
+    EncryptionDirectory create(
+        Directory delegate,
+        AesCtrEncrypterFactory encrypterFactory,
+        KeySupplier keySupplier,
+        EncryptionListener encryptionListener)
       throws IOException;
   }
 }
diff --git 
a/encryption/src/main/java/org/apache/solr/encryption/EncryptionMergePolicy.java
 
b/encryption/src/main/java/org/apache/solr/encryption/EncryptionMergePolicy.java
index e915c31..75287c3 100644
--- 
a/encryption/src/main/java/org/apache/solr/encryption/EncryptionMergePolicy.java
+++ 
b/encryption/src/main/java/org/apache/solr/encryption/EncryptionMergePolicy.java
@@ -74,7 +74,12 @@ public class EncryptionMergePolicy extends FilterMergePolicy 
{
     // Make sure the EncryptionDirectory does not keep its cache for the 
commit user data.
     // It must read the latest commit user data to get the latest active key, 
so below the
     // segments with old key (to re-encrypt) are always accurate.
-    encryptionDir.forceReadCommitUserData();
+    encryptionDir.clearCachedCommitUserData();
+    // Also clear the encryption status for logs if the index becomes 
cleartext after rewriting
+    // the segments.
+    if (activeKeyId == null) {
+      encryptionDir.clearCachedEncryptionStatus();
+    }
     List<SegmentCommitInfo> segmentsWithOldKeyId = 
encryptionDir.getSegmentsWithOldKeyId(segmentInfos, activeKeyId);
     if (segmentsWithOldKeyId.isEmpty()) {
       return null;
diff --git 
a/encryption/src/main/java/org/apache/solr/encryption/EncryptionUpdateLog.java 
b/encryption/src/main/java/org/apache/solr/encryption/EncryptionUpdateLog.java
index db3a706..b67b0a4 100644
--- 
a/encryption/src/main/java/org/apache/solr/encryption/EncryptionUpdateLog.java
+++ 
b/encryption/src/main/java/org/apache/solr/encryption/EncryptionUpdateLog.java
@@ -98,7 +98,7 @@ public class EncryptionUpdateLog extends UpdateLog {
     String activeKeyRef;
     EncryptionDirectory directory = directorySupplier.get();
     try {
-      directory.forceReadCommitUserData();
+      directory.clearCachedCommitUserData();
       latestCommitData = directory.getLatestCommitData().data;
       activeKeyRef = getActiveKeyRefFromCommit(latestCommitData);
       for (TransactionLog log : logs) {
diff --git 
a/encryption/src/test/java/org/apache/solr/encryption/EncryptionBackupRepositoryTest.java
 
b/encryption/src/test/java/org/apache/solr/encryption/EncryptionBackupRepositoryTest.java
index 9f36a19..42ac87f 100644
--- 
a/encryption/src/test/java/org/apache/solr/encryption/EncryptionBackupRepositoryTest.java
+++ 
b/encryption/src/test/java/org/apache/solr/encryption/EncryptionBackupRepositoryTest.java
@@ -182,7 +182,11 @@ public class EncryptionBackupRepositoryTest extends 
AbstractBackupRepositoryTest
         AesCtrEncrypterFactory encrypterFactory = random().nextBoolean() ? 
CipherAesCtrEncrypter.FACTORY : LightAesCtrEncrypter.FACTORY;
         KeySupplier keySupplier = new TestingKeySupplier.Factory().create();
         try (Directory fsSourceDir = FSDirectory.open(sourcePath);
-             Directory encSourceDir = new TestEncryptionDirectory(fsSourceDir, 
encrypterFactory, keySupplier);
+             Directory encSourceDir = new TestEncryptionDirectory(
+                 fsSourceDir,
+                 encrypterFactory,
+                 keySupplier,
+                 EncryptionDirectory.EncryptionListener.NO_LISTENER);
              Directory destinationDir = 
FSDirectory.open(createTempDir().toAbsolutePath())) {
             String fileName = "source-file";
             String content = "content";
@@ -331,9 +335,13 @@ public class EncryptionBackupRepositoryTest extends 
AbstractBackupRepositoryTest
      */
     private static class TestEncryptionDirectory extends EncryptionDirectory {
 
-        TestEncryptionDirectory(Directory delegate, AesCtrEncrypterFactory 
encrypterFactory, KeySupplier keySupplier)
-                throws IOException {
-            super(delegate, encrypterFactory, keySupplier);
+        TestEncryptionDirectory(
+            Directory delegate,
+            AesCtrEncrypterFactory encrypterFactory,
+            KeySupplier keySupplier,
+            EncryptionListener encryptionListener)
+            throws IOException {
+            super(delegate, encrypterFactory, keySupplier, encryptionListener);
         }
 
         @Override
diff --git 
a/encryption/src/test/java/org/apache/solr/encryption/EncryptionDirectoryTest.java
 
b/encryption/src/test/java/org/apache/solr/encryption/EncryptionDirectoryTest.java
index f278701..709eb0c 100644
--- 
a/encryption/src/test/java/org/apache/solr/encryption/EncryptionDirectoryTest.java
+++ 
b/encryption/src/test/java/org/apache/solr/encryption/EncryptionDirectoryTest.java
@@ -262,8 +262,10 @@ public class EncryptionDirectoryTest extends 
SolrCloudTestCase {
     @Override
     public EncryptionDirectory create(Directory delegate,
                                       AesCtrEncrypterFactory encrypterFactory,
-                                      KeySupplier keySupplier) throws 
IOException {
-      MockEncryptionDirectory mockDir = new MockEncryptionDirectory(delegate, 
encrypterFactory, keySupplier);
+                                      KeySupplier keySupplier,
+                                      EncryptionDirectory.EncryptionListener 
encryptionListener)
+        throws IOException {
+      MockEncryptionDirectory mockDir = new MockEncryptionDirectory(delegate, 
encrypterFactory, keySupplier, encryptionListener);
       mockDirs.add(mockDir);
       return mockDir;
     }
@@ -273,9 +275,13 @@ public class EncryptionDirectoryTest extends 
SolrCloudTestCase {
 
     final KeySupplier keySupplier;
 
-    MockEncryptionDirectory(Directory delegate, AesCtrEncrypterFactory 
encrypterFactory, KeySupplier keySupplier)
+    MockEncryptionDirectory(
+        Directory delegate,
+        AesCtrEncrypterFactory encrypterFactory,
+        KeySupplier keySupplier,
+        EncryptionListener encryptionListener)
       throws IOException {
-      super(delegate, encrypterFactory, keySupplier);
+      super(delegate, encrypterFactory, keySupplier, encryptionListener);
       this.keySupplier = keySupplier;
     }
 
diff --git 
a/encryption/src/test/java/org/apache/solr/encryption/EncryptionMergePolicyFactoryTest.java
 
b/encryption/src/test/java/org/apache/solr/encryption/EncryptionMergePolicyFactoryTest.java
index 78d2ff2..7d517a2 100644
--- 
a/encryption/src/test/java/org/apache/solr/encryption/EncryptionMergePolicyFactoryTest.java
+++ 
b/encryption/src/test/java/org/apache/solr/encryption/EncryptionMergePolicyFactoryTest.java
@@ -30,6 +30,7 @@ import org.apache.lucene.store.MMapDirectory;
 import org.apache.lucene.tests.util.LuceneTestCase;
 import org.apache.solr.core.SolrResourceLoader;
 import org.apache.solr.encryption.crypto.LightAesCtrEncrypter;
+import org.apache.solr.encryption.EncryptionDirectory.EncryptionListener;
 import org.apache.solr.index.MergePolicyFactoryArgs;
 import org.apache.solr.index.TieredMergePolicyFactory;
 import org.junit.Test;
@@ -38,6 +39,7 @@ import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Collectors;
 
 import static 
org.apache.solr.encryption.TestingEncryptionRequestHandler.MOCK_COOKIE_PARAMS;
@@ -80,9 +82,22 @@ public class EncryptionMergePolicyFactoryTest extends 
LuceneTestCase {
   @Test
   public void testSegmentReencryption() throws Exception {
     KeySupplier keySupplier = new TestingKeySupplier.Factory().create();
-    try (Directory dir = new EncryptionDirectory(new 
MMapDirectory(createTempDir(), FSLockFactory.getDefault()),
-                                                 LightAesCtrEncrypter.FACTORY,
-                                                 keySupplier)) {
+    AtomicBoolean indexEncrypted = new AtomicBoolean(false);
+    EncryptionListener encryptionListener = new EncryptionListener() {
+      @Override
+      public void onEncryption() {
+        indexEncrypted.set(true);
+      }
+      @Override
+      public void clearEncryptionStatus() {
+        indexEncrypted.set(false);
+      }
+    };
+    try (Directory dir = new EncryptionDirectory(
+        new MMapDirectory(createTempDir(), FSLockFactory.getDefault()),
+        LightAesCtrEncrypter.FACTORY,
+        keySupplier,
+        encryptionListener)) {
       IndexWriterConfig iwc = new IndexWriterConfig(new WhitespaceAnalyzer());
       iwc.setMergeScheduler(new ConcurrentMergeScheduler());
       iwc.setMergePolicy(createMergePolicy());
@@ -125,6 +140,7 @@ public class EncryptionMergePolicyFactoryTest extends 
LuceneTestCase {
         assertTrue(segmentNames.isEmpty());
       }
     }
+    assertTrue(indexEncrypted.get());
   }
 
   private void commit(IndexWriter writer, KeySupplier keySupplier, String... 
keyIds) throws IOException {
diff --git 
a/encryption/src/test/java/org/apache/solr/encryption/EncryptionMergePolicyTest.java
 
b/encryption/src/test/java/org/apache/solr/encryption/EncryptionMergePolicyTest.java
index 88cd3b2..0e61184 100644
--- 
a/encryption/src/test/java/org/apache/solr/encryption/EncryptionMergePolicyTest.java
+++ 
b/encryption/src/test/java/org/apache/solr/encryption/EncryptionMergePolicyTest.java
@@ -45,213 +45,219 @@ import static 
org.apache.solr.encryption.TestingKeySupplier.KEY_ID_2;
  */
 public class EncryptionMergePolicyTest extends LuceneTestCase {
 
-    private final Path tempDir = createTempDir();
-    private final KeySupplier keySupplier = new 
TestingKeySupplier.Factory().create();
-    private final AesCtrEncrypterFactory encrypterFactory = 
LightAesCtrEncrypter.FACTORY;
-
-    @Test
-    public void testNoReencryptionWhenNoKeyChange() throws Exception {
-        try (Directory dir = new EncryptionDirectory(
-                new MMapDirectory(tempDir, FSLockFactory.getDefault()),
-                encrypterFactory,
-                keySupplier)) {
-            
-            IndexWriterConfig iwc = new IndexWriterConfig(new 
WhitespaceAnalyzer());
-            iwc.setMergePolicy(createMergePolicy());
-            
-            try (IndexWriter writer = new IndexWriter(dir, iwc)) {
-                // Create initial segments with KEY_ID_1.
-                commit(writer, keySupplier, KEY_ID_1);
-                int numSegments = 3;
-                for (int i = 0; i < numSegments; ++i) {
-                    writer.addDocument(new Document());
-                    commit(writer, keySupplier, KEY_ID_1);
-                }
-                
-                Set<String> initialSegmentNames = readSegmentNames(dir);
-                assertEquals(numSegments, initialSegmentNames.size());
-
-                // Force merge with MAX_VALUE should not trigger reencryption.
-                writer.forceMerge(Integer.MAX_VALUE);
-                commit(writer, keySupplier, KEY_ID_1);
-                
-                // Verify segments remain unchanged.
-                assertEquals(initialSegmentNames, readSegmentNames(dir));
-            }
+  private final Path tempDir = createTempDir();
+  private final KeySupplier keySupplier = new 
TestingKeySupplier.Factory().create();
+  private final AesCtrEncrypterFactory encrypterFactory = 
LightAesCtrEncrypter.FACTORY;
+  private final EncryptionDirectory.EncryptionListener encryptionListener = 
EncryptionDirectory.EncryptionListener.NO_LISTENER;
+
+  @Test
+  public void testNoReencryptionWhenNoKeyChange() throws Exception {
+    try (Directory dir = new EncryptionDirectory(
+        new MMapDirectory(tempDir, FSLockFactory.getDefault()),
+        encrypterFactory,
+        keySupplier,
+        encryptionListener)) {
+
+      IndexWriterConfig iwc = new IndexWriterConfig(new WhitespaceAnalyzer());
+      iwc.setMergePolicy(createMergePolicy());
+
+      try (IndexWriter writer = new IndexWriter(dir, iwc)) {
+        // Create initial segments with KEY_ID_1.
+        commit(writer, keySupplier, KEY_ID_1);
+        int numSegments = 3;
+        for (int i = 0; i < numSegments; ++i) {
+          writer.addDocument(new Document());
+          commit(writer, keySupplier, KEY_ID_1);
         }
+
+        Set<String> initialSegmentNames = readSegmentNames(dir);
+        assertEquals(numSegments, initialSegmentNames.size());
+
+        // Force merge with MAX_VALUE should not trigger reencryption.
+        writer.forceMerge(Integer.MAX_VALUE);
+        commit(writer, keySupplier, KEY_ID_1);
+
+        // Verify segments remain unchanged.
+        assertEquals(initialSegmentNames, readSegmentNames(dir));
+      }
     }
+  }
+
+  @Test
+  public void testReencryptionWithKeyChange() throws Exception {
+    try (Directory dir = new EncryptionDirectory(
+        new MMapDirectory(tempDir, FSLockFactory.getDefault()),
+        encrypterFactory,
+        keySupplier,
+        encryptionListener)) {
 
-    @Test
-    public void testReencryptionWithKeyChange() throws Exception {
-        try (Directory dir = new EncryptionDirectory(
-                new MMapDirectory(tempDir, FSLockFactory.getDefault()),
-                encrypterFactory,
-                keySupplier)) {
-            
-            IndexWriterConfig iwc = new IndexWriterConfig(new 
WhitespaceAnalyzer());
-            iwc.setMergePolicy(createMergePolicy());
-            
-            try (IndexWriter writer = new IndexWriter(dir, iwc)) {
-                // Create initial segments with KEY_ID_1.
-                commit(writer, keySupplier, KEY_ID_1);
-                int numSegments = 3;
-                for (int i = 0; i < numSegments; ++i) {
-                    writer.addDocument(new Document());
-                    commit(writer, keySupplier, KEY_ID_1);
-                }
-                
-                Set<String> initialSegmentNames = readSegmentNames(dir);
-                assertEquals(numSegments, initialSegmentNames.size());
-
-                // Change active key to KEY_ID_2.
-                commit(writer, keySupplier, KEY_ID_1, KEY_ID_2);
-
-                // Force merge with MAX_VALUE should trigger reencryption.
-                writer.forceMerge(Integer.MAX_VALUE);
-                commit(writer, keySupplier, KEY_ID_1, KEY_ID_2);
-                
-                // Verify all segments have been rewritten.
-                Set<String> newSegmentNames = readSegmentNames(dir);
-                assertEquals(initialSegmentNames.size(), 
newSegmentNames.size());
-                assertNotEquals(initialSegmentNames, newSegmentNames);
-                newSegmentNames.retainAll(initialSegmentNames);
-                assertTrue(newSegmentNames.isEmpty());
-            }
+      IndexWriterConfig iwc = new IndexWriterConfig(new WhitespaceAnalyzer());
+      iwc.setMergePolicy(createMergePolicy());
+
+      try (IndexWriter writer = new IndexWriter(dir, iwc)) {
+        // Create initial segments with KEY_ID_1.
+        commit(writer, keySupplier, KEY_ID_1);
+        int numSegments = 3;
+        for (int i = 0; i < numSegments; ++i) {
+          writer.addDocument(new Document());
+          commit(writer, keySupplier, KEY_ID_1);
         }
+
+        Set<String> initialSegmentNames = readSegmentNames(dir);
+        assertEquals(numSegments, initialSegmentNames.size());
+
+        // Change active key to KEY_ID_2.
+        commit(writer, keySupplier, KEY_ID_1, KEY_ID_2);
+
+        // Force merge with MAX_VALUE should trigger reencryption.
+        writer.forceMerge(Integer.MAX_VALUE);
+        commit(writer, keySupplier, KEY_ID_1, KEY_ID_2);
+
+        // Verify all segments have been rewritten.
+        Set<String> newSegmentNames = readSegmentNames(dir);
+        assertEquals(initialSegmentNames.size(), newSegmentNames.size());
+        assertNotEquals(initialSegmentNames, newSegmentNames);
+        newSegmentNames.retainAll(initialSegmentNames);
+        assertTrue(newSegmentNames.isEmpty());
+      }
     }
+  }
+
+  @Test
+  public void testNoReencryptionWithNonMaxValueForceMerge() throws Exception {
+    try (Directory dir = new EncryptionDirectory(
+        new MMapDirectory(tempDir, FSLockFactory.getDefault()),
+        encrypterFactory,
+        keySupplier,
+        encryptionListener)) {
+
+      IndexWriterConfig iwc = new IndexWriterConfig(new WhitespaceAnalyzer());
+      iwc.setMergePolicy(createMergePolicy());
 
-    @Test
-    public void testNoReencryptionWithNonMaxValueForceMerge() throws Exception 
{
-        try (Directory dir = new EncryptionDirectory(
-                new MMapDirectory(tempDir, FSLockFactory.getDefault()),
-                encrypterFactory,
-                keySupplier)) {
-            
-            IndexWriterConfig iwc = new IndexWriterConfig(new 
WhitespaceAnalyzer());
-            iwc.setMergePolicy(createMergePolicy());
-            
-            try (IndexWriter writer = new IndexWriter(dir, iwc)) {
-                // Create initial segments with KEY_ID_1.
-                commit(writer, keySupplier, KEY_ID_1);
-                int numSegments = 3;
-                for (int i = 0; i < numSegments; ++i) {
-                    writer.addDocument(new Document());
-                    commit(writer, keySupplier, KEY_ID_1);
-                }
-                
-                Set<String> initialSegmentNames = readSegmentNames(dir);
-                assertEquals(numSegments, initialSegmentNames.size());
-
-                // Change active key to KEY_ID_2.
-                commit(writer, keySupplier, KEY_ID_1, KEY_ID_2);
-
-                // Force merge with non-MAX_VALUE should not trigger 
reencryption.
-                writer.forceMerge(10);
-                commit(writer, keySupplier, KEY_ID_1, KEY_ID_2);
-                
-                // Verify segments remain unchanged.
-                assertEquals(initialSegmentNames, readSegmentNames(dir));
-            }
+      try (IndexWriter writer = new IndexWriter(dir, iwc)) {
+        // Create initial segments with KEY_ID_1.
+        commit(writer, keySupplier, KEY_ID_1);
+        int numSegments = 3;
+        for (int i = 0; i < numSegments; ++i) {
+          writer.addDocument(new Document());
+          commit(writer, keySupplier, KEY_ID_1);
         }
+
+        Set<String> initialSegmentNames = readSegmentNames(dir);
+        assertEquals(numSegments, initialSegmentNames.size());
+
+        // Change active key to KEY_ID_2.
+        commit(writer, keySupplier, KEY_ID_1, KEY_ID_2);
+
+        // Force merge with non-MAX_VALUE should not trigger reencryption.
+        writer.forceMerge(10);
+        commit(writer, keySupplier, KEY_ID_1, KEY_ID_2);
+
+        // Verify segments remain unchanged.
+        assertEquals(initialSegmentNames, readSegmentNames(dir));
+      }
     }
+  }
 
-    @Test
-    public void testEmptyIndex() throws Exception {
-        try (Directory dir = new EncryptionDirectory(
-                new MMapDirectory(tempDir, FSLockFactory.getDefault()),
-                encrypterFactory,
-                keySupplier)) {
-            
-            IndexWriterConfig iwc = new IndexWriterConfig(new 
WhitespaceAnalyzer());
-            iwc.setMergePolicy(createMergePolicy());
-            
-            try (IndexWriter writer = new IndexWriter(dir, iwc)) {
-                // Create empty index with KEY_ID_1.
-                commit(writer, keySupplier, KEY_ID_1);
-                
-                // Change active key to KEY_ID_2.
-                commit(writer, keySupplier, KEY_ID_1, KEY_ID_2);
-
-                // Force merge with MAX_VALUE should not trigger any merges.
-                writer.forceMerge(Integer.MAX_VALUE);
-                commit(writer, keySupplier, KEY_ID_1, KEY_ID_2);
-                
-                // Verify no segments exist.
-                assertEquals(0, readSegmentNames(dir).size());
-            }
-        }
+  @Test
+  public void testEmptyIndex() throws Exception {
+    try (Directory dir = new EncryptionDirectory(
+        new MMapDirectory(tempDir, FSLockFactory.getDefault()),
+        encrypterFactory,
+        keySupplier,
+        encryptionListener)) {
+
+      IndexWriterConfig iwc = new IndexWriterConfig(new WhitespaceAnalyzer());
+      iwc.setMergePolicy(createMergePolicy());
+
+      try (IndexWriter writer = new IndexWriter(dir, iwc)) {
+        // Create empty index with KEY_ID_1.
+        commit(writer, keySupplier, KEY_ID_1);
+
+        // Change active key to KEY_ID_2.
+        commit(writer, keySupplier, KEY_ID_1, KEY_ID_2);
+
+        // Force merge with MAX_VALUE should not trigger any merges.
+        writer.forceMerge(Integer.MAX_VALUE);
+        commit(writer, keySupplier, KEY_ID_1, KEY_ID_2);
+
+        // Verify no segments exist.
+        assertEquals(0, readSegmentNames(dir).size());
+      }
     }
+  }
+
+  @Test
+  public void testPartiallyEncryptedSegments() throws Exception {
+    try (Directory dir = new EncryptionDirectory(
+        new MMapDirectory(tempDir, FSLockFactory.getDefault()),
+        encrypterFactory,
+        keySupplier,
+        encryptionListener)) {
+
+      IndexWriterConfig iwc = new IndexWriterConfig(new WhitespaceAnalyzer());
+      iwc.setMergePolicy(createMergePolicy());
+
+      try (IndexWriter writer = new IndexWriter(dir, iwc)) {
+        // Create segments with mixed encryption states.
+        commit(writer, keySupplier, KEY_ID_1);
 
-    @Test
-    public void testPartiallyEncryptedSegments() throws Exception {
-        try (Directory dir = new EncryptionDirectory(
-                new MMapDirectory(tempDir, FSLockFactory.getDefault()),
-                encrypterFactory,
-                keySupplier)) {
-            
-            IndexWriterConfig iwc = new IndexWriterConfig(new 
WhitespaceAnalyzer());
-            iwc.setMergePolicy(createMergePolicy());
-            
-            try (IndexWriter writer = new IndexWriter(dir, iwc)) {
-                // Create segments with mixed encryption states.
-                commit(writer, keySupplier, KEY_ID_1);
-                
-                // Add some documents with KEY_ID_1.
-                for (int i = 0; i < 3; i++) {
-                    Document doc = new Document();
-                    doc.add(new StringField("id", String.valueOf(i), 
Field.Store.YES));
-                    writer.addDocument(doc);
-                    commit(writer, keySupplier, KEY_ID_1);
-                }
-
-                Set<String> key1SegmentNames = readSegmentNames(dir);
-                assertEquals(3, key1SegmentNames.size());
-
-                // Change to KEY_ID_2.
-                commit(writer, keySupplier, KEY_ID_1, KEY_ID_2);
-                
-                // Add more documents with KEY_ID_2.
-                for (int i = 3; i < 6; i++) {
-                    Document doc = new Document();
-                    doc.add(new StringField("id", String.valueOf(i), 
Field.Store.YES));
-                    writer.addDocument(doc);
-                    commit(writer, keySupplier, KEY_ID_1, KEY_ID_2);
-                }
-                
-                Set<String> key2NewSegmentNames = readSegmentNames(dir);
-                key2NewSegmentNames.removeAll(key1SegmentNames);
-                assertEquals(3, key2NewSegmentNames.size());
-
-                // Force merge with MAX_VALUE should trigger reencryption of 
old segments.
-                writer.forceMerge(Integer.MAX_VALUE);
-                commit(writer, keySupplier, KEY_ID_1, KEY_ID_2);
-                
-                // Verify only and all key1 segments have been rewritten.
-                Set<String> finalSegmentNames = readSegmentNames(dir);
-                assertEquals(6, finalSegmentNames.size());
-                assertTrue(finalSegmentNames.containsAll(key2NewSegmentNames));
-                
assertTrue(finalSegmentNames.stream().noneMatch(key1SegmentNames::contains));
-            }
+        // Add some documents with KEY_ID_1.
+        for (int i = 0; i < 3; i++) {
+          Document doc = new Document();
+          doc.add(new StringField("id", String.valueOf(i), Field.Store.YES));
+          writer.addDocument(doc);
+          commit(writer, keySupplier, KEY_ID_1);
         }
-    }
 
-    private MergePolicy createMergePolicy() {
-        return new EncryptionMergePolicy(new TieredMergePolicy());
-    }
+        Set<String> key1SegmentNames = readSegmentNames(dir);
+        assertEquals(3, key1SegmentNames.size());
 
-    private void commit(IndexWriter writer, KeySupplier keySupplier, String... 
keyIds) throws IOException {
-        Map<String, String> commitData = new HashMap<>();
-        for (String keyId : keyIds) {
-            EncryptionUtil.setNewActiveKeyIdInCommit(keyId, 
keySupplier.getKeyCookie(keyId, MOCK_COOKIE_PARAMS), commitData);
+        // Change to KEY_ID_2.
+        commit(writer, keySupplier, KEY_ID_1, KEY_ID_2);
+
+        // Add more documents with KEY_ID_2.
+        for (int i = 3; i < 6; i++) {
+          Document doc = new Document();
+          doc.add(new StringField("id", String.valueOf(i), Field.Store.YES));
+          writer.addDocument(doc);
+          commit(writer, keySupplier, KEY_ID_1, KEY_ID_2);
         }
-        writer.setLiveCommitData(commitData.entrySet());
-        writer.commit();
+
+        Set<String> key2NewSegmentNames = readSegmentNames(dir);
+        key2NewSegmentNames.removeAll(key1SegmentNames);
+        assertEquals(3, key2NewSegmentNames.size());
+
+        // Force merge with MAX_VALUE should trigger reencryption of old 
segments.
+        writer.forceMerge(Integer.MAX_VALUE);
+        commit(writer, keySupplier, KEY_ID_1, KEY_ID_2);
+
+        // Verify only and all key1 segments have been rewritten.
+        Set<String> finalSegmentNames = readSegmentNames(dir);
+        assertEquals(6, finalSegmentNames.size());
+        assertTrue(finalSegmentNames.containsAll(key2NewSegmentNames));
+        
assertTrue(finalSegmentNames.stream().noneMatch(key1SegmentNames::contains));
+      }
     }
+  }
 
-    private Set<String> readSegmentNames(Directory dir) throws IOException {
-        SegmentInfos segmentInfos = SegmentInfos.readLatestCommit(dir);
-        return segmentInfos.asList().stream()
-                .map(sci -> sci.info.name)
-                .collect(Collectors.toSet());
+  private MergePolicy createMergePolicy() {
+    return new EncryptionMergePolicy(new TieredMergePolicy());
+  }
+
+  private void commit(IndexWriter writer, KeySupplier keySupplier, String... 
keyIds) throws IOException {
+    Map<String, String> commitData = new HashMap<>();
+    for (String keyId : keyIds) {
+      EncryptionUtil.setNewActiveKeyIdInCommit(keyId, 
keySupplier.getKeyCookie(keyId, MOCK_COOKIE_PARAMS), commitData);
     }
+    writer.setLiveCommitData(commitData.entrySet());
+    writer.commit();
+  }
+
+  private Set<String> readSegmentNames(Directory dir) throws IOException {
+    SegmentInfos segmentInfos = SegmentInfos.readLatestCommit(dir);
+    return segmentInfos.asList().stream()
+        .map(sci -> sci.info.name)
+        .collect(Collectors.toSet());
+  }
 } 
\ No newline at end of file
diff --git 
a/encryption/src/test/java/org/apache/solr/encryption/EncryptionRequestHandlerTest.java
 
b/encryption/src/test/java/org/apache/solr/encryption/EncryptionRequestHandlerTest.java
index 0dcf9bc..bcabe01 100644
--- 
a/encryption/src/test/java/org/apache/solr/encryption/EncryptionRequestHandlerTest.java
+++ 
b/encryption/src/test/java/org/apache/solr/encryption/EncryptionRequestHandlerTest.java
@@ -27,6 +27,7 @@ import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.embedded.JettySolrRunner;
 import org.apache.solr.encryption.crypto.AesCtrEncrypterFactory;
+import org.apache.solr.encryption.EncryptionDirectory.EncryptionListener;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrQueryRequestBase;
 import org.apache.solr.response.SolrQueryResponse;
@@ -393,16 +394,22 @@ public class EncryptionRequestHandlerTest extends 
SolrCloudTestCase {
     @Override
     public EncryptionDirectory create(Directory delegate,
                                       AesCtrEncrypterFactory encrypterFactory,
-                                      KeySupplier keySupplier) throws 
IOException {
-      return new MockEncryptionDirectory(delegate, encrypterFactory, 
keySupplier);
+                                      KeySupplier keySupplier,
+                                      EncryptionListener encryptionListener)
+        throws IOException {
+      return new MockEncryptionDirectory(delegate, encrypterFactory, 
keySupplier, encryptionListener);
     }
   }
 
   private static class MockEncryptionDirectory extends EncryptionDirectory {
 
-    MockEncryptionDirectory(Directory delegate, AesCtrEncrypterFactory 
encrypterFactory, KeySupplier keySupplier)
+    MockEncryptionDirectory(
+        Directory delegate,
+        AesCtrEncrypterFactory encrypterFactory,
+        KeySupplier keySupplier,
+        EncryptionListener encryptionListener)
       throws IOException {
-      super(delegate, encrypterFactory, keySupplier);
+      super(delegate, encrypterFactory, keySupplier, encryptionListener);
     }
 
     @Override


Reply via email to