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

asf-gitbox-commits pushed a commit to branch UNOMI-997-migration-tenants
in repository https://gitbox.apache.org/repos/asf/unomi.git


The following commit(s) were added to refs/heads/UNOMI-997-migration-tenants by 
this push:
     new 4c5d673cb UNOMI-997: Keep create audit when redeploying migrated defs
4c5d673cb is described below

commit 4c5d673cb29c22c90ccf2fea6f7080f10eefdfd4
Author: Serge Huber <[email protected]>
AuthorDate: Wed Oct 7 08:50:57 2026 +0200

    UNOMI-997: Keep create audit when redeploying migrated defs
    
    Migration now sets create audit on systemitems. Bundle redeploy used to
    overwrite those fields with nulls on update; preserve them on save.
---
 .../migration/Migrate16xToCurrentVersionIT.java    | 17 +++++------
 .../cache/AbstractMultiTypeCachingService.java     | 24 ++++++++++------
 .../cache/AbstractMultiTypeCachingServiceTest.java | 33 ++++++++++++++++++++++
 .../initialize_tenant_and_audit_fields.painless    |  7 +++--
 4 files changed, 62 insertions(+), 19 deletions(-)

diff --git 
a/itests/src/test/java/org/apache/unomi/itests/migration/Migrate16xToCurrentVersionIT.java
 
b/itests/src/test/java/org/apache/unomi/itests/migration/Migrate16xToCurrentVersionIT.java
index 3a66818f5..63e0755ef 100644
--- 
a/itests/src/test/java/org/apache/unomi/itests/migration/Migrate16xToCurrentVersionIT.java
+++ 
b/itests/src/test/java/org/apache/unomi/itests/migration/Migrate16xToCurrentVersionIT.java
@@ -611,7 +611,7 @@ public class Migrate16xToCurrentVersionIT extends BaseIT {
                 }
 
                 // Check audit metadata
-                checkAuditMetadata(source);
+                checkAuditMetadata(source, itemId);
             }
         }
     }
@@ -619,22 +619,23 @@ public class Migrate16xToCurrentVersionIT extends BaseIT {
     /**
      * Helper method to check audit metadata fields
      * @param source The document source containing the metadata
+     * @param itemId Document id included in assertion messages
      */
-    private void checkAuditMetadata(JsonNode source) {
-        Assert.assertNotNull("Created by should be set", 
source.get("createdBy"));
+    private void checkAuditMetadata(JsonNode source, String itemId) {
+        Assert.assertNotNull("Created by should be set for " + itemId, 
source.get("createdBy"));
         String createdBy = source.get("createdBy").asText();
         // After migration, documents may be refreshed by bundles during 
startup,
         // which changes createdBy from system-migration-4.0.0 to system-bundle
         // Both are valid - migration sets it, bundles may refresh it
         boolean isValidCreatedBy = "system-migration-4.0.0".equals(createdBy) 
|| "system-bundle".equals(createdBy);
-        Assert.assertTrue("Created by should be system-migration-4.0.0 or 
system-bundle, but was: " + createdBy, isValidCreatedBy);
-        Assert.assertNotNull("Creation date should be set", 
source.get("creationDate"));
-        Assert.assertNotNull("Last modified by should be set", 
source.get("lastModifiedBy"));
+        Assert.assertTrue("Created by should be system-migration-4.0.0 or 
system-bundle for " + itemId + ", but was: " + createdBy, isValidCreatedBy);
+        Assert.assertNotNull("Creation date should be set for " + itemId, 
source.get("creationDate"));
+        Assert.assertNotNull("Last modified by should be set for " + itemId, 
source.get("lastModifiedBy"));
         String lastModifiedBy = source.get("lastModifiedBy").asText();
         // Similarly, lastModifiedBy may be updated by bundles after migration
         boolean isValidLastModifiedBy = 
"system-migration-4.0.0".equals(lastModifiedBy) || 
"system-bundle".equals(lastModifiedBy);
-        Assert.assertTrue("Last modified by should be system-migration-4.0.0 
or system-bundle, but was: " + lastModifiedBy, isValidLastModifiedBy);
-        Assert.assertNotNull("Last modification date should be set", 
source.get("lastModificationDate"));
+        Assert.assertTrue("Last modified by should be system-migration-4.0.0 
or system-bundle for " + itemId + ", but was: " + lastModifiedBy, 
isValidLastModifiedBy);
+        Assert.assertNotNull("Last modification date should be set for " + 
itemId, source.get("lastModificationDate"));
     }
 
     /**
diff --git 
a/services-common/src/main/java/org/apache/unomi/services/common/cache/AbstractMultiTypeCachingService.java
 
b/services-common/src/main/java/org/apache/unomi/services/common/cache/AbstractMultiTypeCachingService.java
index f1a7ea87c..427298a62 100644
--- 
a/services-common/src/main/java/org/apache/unomi/services/common/cache/AbstractMultiTypeCachingService.java
+++ 
b/services-common/src/main/java/org/apache/unomi/services/common/cache/AbstractMultiTypeCachingService.java
@@ -932,22 +932,28 @@ public abstract class AbstractMultiTypeCachingService 
extends AbstractContextAwa
         Class<T> itemClass = (Class<T>) item.getClass();
         T existingItem = persistenceService.load(itemId, itemClass);
 
-        boolean itemExists = false;
+        // Bundle JSON definitions have no create audit. If persistence 
already has create
+        // fields (e.g. from migration), copy them onto the incoming item 
before save so a
+        // later auditUpdate does not wipe them with nulls (UNOMI-997).
         if (existingItem != null) {
-            // Item exists in persistence, check if it has audit metadata
-            itemExists = existingItem.getCreatedBy() != null && 
existingItem.getCreationDate() != null;
-        } else {
-            // Item doesn't exist in persistence, check if current item has 
audit metadata (might be a reload from cache)
-            itemExists = item.getCreatedBy() != null && item.getCreationDate() 
!= null;
+            if (item.getCreatedBy() == null && existingItem.getCreatedBy() != 
null) {
+                item.setCreatedBy(existingItem.getCreatedBy());
+            }
+            if (item.getCreationDate() == null && 
existingItem.getCreationDate() != null) {
+                item.setCreationDate(existingItem.getCreationDate());
+            }
+            if (item.getVersion() == null && existingItem.getVersion() != 
null) {
+                item.setVersion(existingItem.getVersion());
+            }
         }
 
+        boolean hasCreateAudit = item.getCreatedBy() != null && 
item.getCreationDate() != null;
+
         // Set audit metadata for bundle-deployed items
         if (auditService != null) {
-            if (itemExists) {
-                // Item exists, this is an update
+            if (hasCreateAudit) {
                 auditService.auditUpdate(item, "system-bundle");
             } else {
-                // New item, this is a create
                 auditService.auditCreate(item, "system-bundle");
             }
         }
diff --git 
a/services-common/src/test/java/org/apache/unomi/services/common/cache/AbstractMultiTypeCachingServiceTest.java
 
b/services-common/src/test/java/org/apache/unomi/services/common/cache/AbstractMultiTypeCachingServiceTest.java
index 57923a771..174609ad4 100644
--- 
a/services-common/src/test/java/org/apache/unomi/services/common/cache/AbstractMultiTypeCachingServiceTest.java
+++ 
b/services-common/src/test/java/org/apache/unomi/services/common/cache/AbstractMultiTypeCachingServiceTest.java
@@ -21,6 +21,7 @@ import org.apache.unomi.api.Item;
 import org.apache.unomi.api.services.ExecutionContextManager;
 import org.apache.unomi.api.services.cache.CacheableTypeConfig;
 import org.apache.unomi.api.services.cache.MultiTypeCacheService;
+import org.apache.unomi.api.tenants.AuditService;
 import org.apache.unomi.api.tenants.Tenant;
 import org.apache.unomi.api.tenants.TenantService;
 import org.apache.unomi.persistence.spi.PersistenceService;
@@ -62,6 +63,9 @@ public class AbstractMultiTypeCachingServiceTest {
     @Mock
     private TenantService tenantService;
 
+    @Mock
+    private AuditService auditService;
+
     private TestCachingServiceImpl testCachingService;
 
     // Simple test class that implements Serializable
@@ -194,6 +198,7 @@ public class AbstractMultiTypeCachingServiceTest {
         testCachingService.setContextManager(contextManager);
         testCachingService.setCacheService(cacheService);
         testCachingService.setTenantService(tenantService);
+        testCachingService.setAuditService(auditService);
         testCachingService.makeConfigPersistable();
 
         // Mock tenant service to return tenant list
@@ -467,4 +472,32 @@ public class AbstractMultiTypeCachingServiceTest {
         assertFalse("saveItem() must not proceed until the concurrent refresh 
released its write lock",
             refreshStillRunningWhenSaveReturned.get());
     }
+
+    /**
+     * Bundle redeploy loads JSON without create audit. If persistence already 
has create fields
+     * (for example after migration), saveItem must keep them instead of 
overwriting with nulls.
+     */
+    @Test
+    public void testSaveItemPreservesMigratedCreateAuditOnBundleRedeploy() {
+        Date creationDate = new Date(1_700_000_000_000L);
+        TestItem existing = new TestItem("item1");
+        existing.setCreatedBy("system-migration-4.0.0");
+        existing.setCreationDate(creationDate);
+        existing.setVersion(3L);
+        when(persistenceService.load("item1", 
TestItem.class)).thenReturn(existing);
+
+        TestItem fromBundle = new TestItem("item1");
+        // Simulate a bundle JSON definition that has no create audit (Item 
may default dates).
+        fromBundle.setCreatedBy(null);
+        fromBundle.setCreationDate(null);
+
+        testCachingService.callSaveItem(fromBundle);
+
+        ArgumentCaptor<TestItem> saved = 
ArgumentCaptor.forClass(TestItem.class);
+        verify(persistenceService).save(saved.capture());
+        assertEquals("system-migration-4.0.0", 
saved.getValue().getCreatedBy());
+        assertEquals(creationDate, saved.getValue().getCreationDate());
+        verify(auditService).auditUpdate(same(fromBundle), 
eq("system-bundle"));
+        verify(auditService, never()).auditCreate(any(), anyString());
+    }
 }
diff --git 
a/tools/shell-commands/src/main/resources/requestBody/4.0.0/initialize_tenant_and_audit_fields.painless
 
b/tools/shell-commands/src/main/resources/requestBody/4.0.0/initialize_tenant_and_audit_fields.painless
index 63521c208..5e4591fdc 100644
--- 
a/tools/shell-commands/src/main/resources/requestBody/4.0.0/initialize_tenant_and_audit_fields.painless
+++ 
b/tools/shell-commands/src/main/resources/requestBody/4.0.0/initialize_tenant_and_audit_fields.painless
@@ -57,9 +57,12 @@ if (ctx._id != null) {
     }
 }
 
-// Update audit fields (not for systemitems or geonames)
+// Update audit fields (skip geonames — not asserted and high volume).
+// systemitems must get audit here: UNOMI-997 puts shipped defs under the 
system
+// tenant before Unomi starts, so bundle deploy treats them as updates and 
would
+// otherwise leave createdBy unset on docs that never had it.
 def indexLower = ctx._index != null ? ctx._index.toLowerCase() : '';
-if (!indexLower.endsWith('-systemitems') && 
!indexLower.endsWith('-geonameentry')) {
+if (!indexLower.endsWith('-geonameentry') && 
!indexLower.endsWith('-geonames')) {
     // Handle creation date based on item type
     if (ctx._source.creationDate == null) {
         if (params.itemType == 'profile' && ctx._source.properties != null) {

Reply via email to