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

rmaucher pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat-jakartaee-migration.git


The following commit(s) were added to refs/heads/main by this push:
     new d542652  Associate cache entries with profile and tool version used
d542652 is described below

commit d54265227edf5794891411ff482840662ec63bf1
Author: remm <[email protected]>
AuthorDate: Wed Sep 9 11:30:53 2026 +0200

    Associate cache entries with profile and tool version used
    
    Testing GLM for code review.
---
 CHANGES.md                                         |  2 +
 .../apache/tomcat/jakartaee/MigrationCache.java    | 49 ++++++++++++--
 .../tomcat/jakartaee/MigrationCacheTest.java       | 77 ++++++++++++++++++++++
 3 files changed, 121 insertions(+), 7 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index dae9ba0..9f26f72 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -11,6 +11,8 @@
 - Improve processing of relevant version numbers in manifests. (remm/markt)
 - Avoid shallow copy style issue when converting manifests attributes. (remm)
 - Make file extension check more robust. (markt)
+- Avoid shallow copy style issue when converting manifests attributes. (remm)
+- Associate cache entries with profile and tool version used. (remm)
 
 ## 1.0.12
 - Add Maven Wrapper Plugin to manage the Maven wrapper. (markt)
diff --git a/src/main/java/org/apache/tomcat/jakartaee/MigrationCache.java 
b/src/main/java/org/apache/tomcat/jakartaee/MigrationCache.java
index 499a8db..de79083 100644
--- a/src/main/java/org/apache/tomcat/jakartaee/MigrationCache.java
+++ b/src/main/java/org/apache/tomcat/jakartaee/MigrationCache.java
@@ -55,11 +55,15 @@ import java.util.regex.Pattern;
  * <h2>Cache Key</h2>
  * <p>Each cache entry is keyed by a SHA-256 hash computed from:</p>
  * <ul>
- *   <li>The migration profile name (e.g., "TOMCAT", "EE")</li>
+ *   <li>The version of the migration tool (a different version may convert
+ *       the same content differently)</li>
+ *   <li>The name and definition (source, target and pattern) of the
+ *       migration profile (e.g., "TOMCAT", "EE")</li>
  *   <li>The pre-conversion archive content (as bytes)</li>
  * </ul>
- * <p>This ensures that the same archive converted with different profiles
- * produces different cache entries.</p>
+ * <p>This ensures that the same archive converted with different profiles,
+ * or with a different version of the tool, produces different cache
+ * entries.</p>
  *
  * <h2>Metadata Format</h2>
  * <p>The {@code cache-metadata.txt} file tracks access times for cache 
pruning:</p>
@@ -316,8 +320,11 @@ public class MigrationCache {
     }
 
     /**
-     * Compute SHA-256 hash of the given bytes combined with the profile name.
-     * The profile is included to ensure different profiles produce different 
cache entries.
+     * Compute SHA-256 hash of the given bytes combined with the version of
+     * the tool and the profile definition.
+     * The tool version is included because a new version may convert the
+     * same content differently. The profile is included to ensure different
+     * profiles produce different cache entries.
      *
      * @param bytes the bytes to hash
      * @param profile the migration profile
@@ -327,8 +334,7 @@ public class MigrationCache {
     private String computeHash(byte[] bytes, EESpecProfile profile) throws 
IOException {
         try {
             MessageDigest digest = MessageDigest.getInstance("SHA-256");
-            // Include profile name in hash to differentiate between profiles
-            
digest.update(profile.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8));
+            digest.update(getHashKeyData(profile));
             digest.update(bytes);
             byte[] hashBytes = digest.digest();
 
@@ -343,6 +349,35 @@ public class MigrationCache {
         }
     }
 
+    /**
+     * Build the keying data that, together with the pre-conversion content,
+     * determines the cache hash. Cache entries outlive the process that
+     * created them so the key must include every input that determines the
+     * conversion output:
+     * <ul>
+     *   <li>the tool version - so entries created by an older version of the
+     *       tool are never used by a newer one (the two may convert the same
+     *       content differently and the version is embedded in manifest
+     *       attributes)</li>
+     *   <li>the profile definition, in addition to the profile name - so
+     *       that two EESpecProfile implementations that share a name but use
+     *       different conversion definitions never collide</li>
+     * </ul>
+     * This must be kept consistent with any other implementation that
+     * computes cache hashes.
+     *
+     * @param profile the migration profile
+     * @return the keying data as UTF-8 bytes
+     */
+    static byte[] getHashKeyData(EESpecProfile profile) {
+        // Note that Pattern.toString() returns the pattern source and is
+        // deterministic
+        String key = Info.getVersion() + '-' + profile.toString() + '-' +
+                profile.getSource() + '-' + profile.getTarget() + '-' +
+                profile.getPattern();
+        return key.getBytes(java.nio.charset.StandardCharsets.UTF_8);
+    }
+
     /**
      * Clear the cache directory.
      *
diff --git a/src/test/java/org/apache/tomcat/jakartaee/MigrationCacheTest.java 
b/src/test/java/org/apache/tomcat/jakartaee/MigrationCacheTest.java
index df5dcf3..bbdadc5 100644
--- a/src/test/java/org/apache/tomcat/jakartaee/MigrationCacheTest.java
+++ b/src/test/java/org/apache/tomcat/jakartaee/MigrationCacheTest.java
@@ -25,6 +25,7 @@ import java.io.OutputStream;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.time.LocalDate;
+import java.util.regex.Pattern;
 
 import org.apache.commons.io.FileUtils;
 import org.junit.After;
@@ -388,6 +389,82 @@ public class MigrationCacheTest {
         assertTrue("Should be cache hit for same profile", entry3.exists());
     }
 
+    /**
+     * Two profiles that report the same name (toString()) but have different
+     * conversion definitions must not share cache entries. Without the
+     * profile definition (source, target, pattern) in the hash, one would
+     * corrupt the other's results. This is a regression test for that
+     * behaviour - previously only the profile name was part of the hash.
+     *
+     * @throws Exception if the test fails
+     */
+    @Test
+    public void testCacheDifferentProfilesWithSameName() throws Exception {
+        MigrationCache cache = new MigrationCache(tempCacheDir, 30);
+
+        byte[] sourceData = "test source 
content".getBytes(StandardCharsets.UTF_8);
+        byte[] convertedData = "converted 
content".getBytes(StandardCharsets.UTF_8);
+
+        EESpecProfile profileA = new EESpecProfile() {
+            @Override
+            public String getSource() {
+                return "javax";
+            }
+
+            @Override
+            public String getTarget() {
+                return "jakarta";
+            }
+
+            @Override
+            public Pattern getPattern() {
+                return Pattern.compile("javax([/\\.](servlet))");
+            }
+
+            @Override
+            public String toString() {
+                // Deliberately the same name as the other profile
+                return "SAME-NAME";
+            }
+        };
+
+        EESpecProfile profileB = new EESpecProfile() {
+            @Override
+            public String getSource() {
+                return "jakarta";
+            }
+
+            @Override
+            public String getTarget() {
+                return "javax";
+            }
+
+            @Override
+            public Pattern getPattern() {
+                return Pattern.compile("jakarta([/\\.](servlet))");
+            }
+
+            @Override
+            public String toString() {
+                // Deliberately the same name as the other profile
+                return "SAME-NAME";
+            }
+        };
+
+        // Store with one profile
+        CacheEntry entry1 = cache.getCacheEntry(sourceData, profileA);
+        try (OutputStream os = entry1.beginStore()) {
+            os.write(convertedData);
+        }
+        entry1.commitStore();
+
+        // The same name but a different definition - must not be served
+        // from the cache
+        CacheEntry entry2 = cache.getCacheEntry(sourceData, profileB);
+        assertFalse("Profile with same name but different definition must not 
use another profile's cache entry",
+                entry2.exists());
+    }
+
     @Test
     public void testCacheCorruptMetadata() throws Exception {
         // Create a corrupt metadata file


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to