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) {