auditRecordData)
+ {
+ this.auditRecordData = auditRecordData;
+ return this;
+ }
+
+ public Builder setUsername(String username)
+ {
+ this.username = username;
+ return this;
+ }
+
+ public Builder setEntryDBId(Long entryDBId)
+ {
+ this.entryDBId = entryDBId;
+ return this;
+ }
+
+ public AuditRecord build()
+ {
+ return new AuditRecord(this);
+ }
+ }
+
+}
diff --git a/repository/src/main/java/org/alfresco/repo/audit/AuditRecordReporter.java b/repository/src/main/java/org/alfresco/repo/audit/AuditRecordReporter.java
new file mode 100644
index 0000000000..7240b7193a
--- /dev/null
+++ b/repository/src/main/java/org/alfresco/repo/audit/AuditRecordReporter.java
@@ -0,0 +1,37 @@
+/*
+ * #%L
+ * Alfresco Repository
+ * %%
+ * Copyright (C) 2025 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.alfresco.repo.audit;
+
+public interface AuditRecordReporter
+{
+ /**
+ * This method will report AuditRecord to Audit Storage using RepoEvent2
+ *
+ * @param auditRecord
+ * represent data that will be reported.
+ */
+ void reportAuditRecord(AuditRecord auditRecord);
+}
diff --git a/repository/src/main/java/org/alfresco/repo/audit/AuditRecordReporterImpl.java b/repository/src/main/java/org/alfresco/repo/audit/AuditRecordReporterImpl.java
new file mode 100644
index 0000000000..522d55d2e6
--- /dev/null
+++ b/repository/src/main/java/org/alfresco/repo/audit/AuditRecordReporterImpl.java
@@ -0,0 +1,40 @@
+/*
+ * #%L
+ * Alfresco Repository
+ * %%
+ * Copyright (C) 2025 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.alfresco.repo.audit;
+
+public class AuditRecordReporterImpl implements AuditRecordReporter
+{
+ /**
+ * This method intentionally has an empty implementation.
+ *
+ * This class provides a no-op implementation of {@link AuditRecordReporter}.
+ */
+ @Override
+ public void reportAuditRecord(AuditRecord auditRecord)
+ {
+ // No operation performed.
+ }
+}
diff --git a/repository/src/main/java/org/alfresco/repo/audit/AuditRecordUtils.java b/repository/src/main/java/org/alfresco/repo/audit/AuditRecordUtils.java
new file mode 100644
index 0000000000..4b2ba81721
--- /dev/null
+++ b/repository/src/main/java/org/alfresco/repo/audit/AuditRecordUtils.java
@@ -0,0 +1,104 @@
+/*
+ * #%L
+ * Alfresco Repository
+ * %%
+ * Copyright (C) 2025 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.alfresco.repo.audit;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.alfresco.service.cmr.repository.NodeRef;
+
+public final class AuditRecordUtils
+{
+ private AuditRecordUtils()
+ {
+ // This is a utility class and cannot be instantiated.
+ }
+
+ /**
+ * Generates an {@link AuditRecord.Builder} from flat audit data.
+ *
+ * This method:
+ *
+ * - Translates flat {@code key-value} pairs into a nested JSON structure.
+ * - Preloads the builder with the provided arguments.
+ * - Splits keys by {@code /} to build the nested structure.
+ * - Uses the root key as the application ID.
+ * - Assumes each key starts with the same root, constructed as {@code '/' + auditedApplicationName + '/'}, which is removed before splitting.
+ *
+ *
+ * @param data
+ * a map containing flat audit data as `key-value` pairs
+ * @param keyRootLength
+ * is a length of key root.
+ * @return a preloaded {@link AuditRecord.Builder}
+ */
+ public static AuditRecord.Builder generateAuditRecordBuilder(Map data, int keyRootLength)
+ {
+ var auditRecordBuilder = AuditRecord.builder();
+
+ var rootNode = createRootNode(data, keyRootLength);
+
+ auditRecordBuilder.setAuditRecordData(rootNode);
+
+ return auditRecordBuilder;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static HashMap createRootNode(Map data, int keyRootLength)
+ {
+ var rootNode = new HashMap();
+
+ data.forEach((k, v) -> {
+ var keys = k.substring(keyRootLength).split("/");
+
+ var current = rootNode;
+ for (int i = 0; i < keys.length - 1; i++)
+ {
+ current = (HashMap) current.computeIfAbsent(keys[i], newMap -> new HashMap());
+ }
+ current.put(keys[keys.length - 1], decodeValueByInstance(v));
+ });
+ return rootNode;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Serializable decodeValueByInstance(Serializable value)
+ {
+ if (value instanceof HashMap, ?>)
+ {
+ return createRootNode((HashMap) value, 0);
+ }
+ else if (value instanceof NodeRef)
+ {
+ return ((NodeRef) value).getId();
+ }
+ else
+ {
+ return value;
+ }
+ }
+}
diff --git a/repository/src/main/java/org/alfresco/repo/audit/model/AuditModelRegistry.java b/repository/src/main/java/org/alfresco/repo/audit/model/AuditModelRegistry.java
index 9cb9c713df..e0d02e6d26 100644
--- a/repository/src/main/java/org/alfresco/repo/audit/model/AuditModelRegistry.java
+++ b/repository/src/main/java/org/alfresco/repo/audit/model/AuditModelRegistry.java
@@ -58,6 +58,21 @@ public interface AuditModelRegistry
*/
public boolean isAuditEnabled();
+ /**
+ * Determines whether audit values should be stored in database. True
by default if not changed by property.
+ *
+ * @return true
if audit is enabled.
+ */
+ boolean isAuditingToDatabaseEnabled();
+
+ /**
+ * Determines whether audit values should be stored in audit storage.
+ *
+ * @return true
if auditing to Audit Storage is enabled.
+ *
+ */
+ boolean isAuditingToAuditStorageEnabled();
+
/**
* Get a map of all audit applications key by name
*
diff --git a/repository/src/main/java/org/alfresco/repo/audit/model/AuditModelRegistryImpl.java b/repository/src/main/java/org/alfresco/repo/audit/model/AuditModelRegistryImpl.java
index 0bf305ddd2..248ea8904e 100644
--- a/repository/src/main/java/org/alfresco/repo/audit/model/AuditModelRegistryImpl.java
+++ b/repository/src/main/java/org/alfresco/repo/audit/model/AuditModelRegistryImpl.java
@@ -85,6 +85,9 @@ public class AuditModelRegistryImpl extends AbstractPropertyBackedBean implement
{
/** The name of the global enablement property. */
public static final String PROPERTY_AUDIT_ENABLED = "audit.enabled";
+
+ private static final String AUDITING_TO_DATABASE = ".auditingToDatabase";
+ private static final String AUDITING_TO_AUDIT_STORAGE = ".auditingToAuditStorage";
/** The name of the strict loading flag. */
public static final String PROPERTY_AUDIT_CONFIG_STRICT = "audit.config.strict";
/** The XSD classpath location. */
@@ -249,6 +252,26 @@ public class AuditModelRegistryImpl extends AbstractPropertyBackedBean implement
return value != null && value.equalsIgnoreCase("true");
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isAuditingToDatabaseEnabled()
+ {
+ String value = getProperty(AUDIT_PROPERTY_AUDIT_ENABLED + AUDITING_TO_DATABASE);
+ return value == null || value.equalsIgnoreCase("true");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isAuditingToAuditStorageEnabled()
+ {
+ String value = getProperty(AUDIT_PROPERTY_AUDIT_ENABLED + AUDITING_TO_AUDIT_STORAGE);
+ return value != null && value.equalsIgnoreCase("true");
+ }
+
/**
* Enables audit and registers an audit model at a given URL. Does not register across the cluster and should only be used for unit test purposes.
*
@@ -296,6 +319,8 @@ public class AuditModelRegistryImpl extends AbstractPropertyBackedBean implement
// Default value for global enabled property
properties.put(AUDIT_PROPERTY_AUDIT_ENABLED, false);
+ properties.put(AUDIT_PROPERTY_AUDIT_ENABLED + AUDITING_TO_DATABASE, true);
+ properties.put(AUDIT_PROPERTY_AUDIT_ENABLED + AUDITING_TO_AUDIT_STORAGE, false);
// Let's search for config files in the appropriate places. The individual applications they contain can still
// be enabled/disabled by the bean properties
diff --git a/repository/src/main/java/org/alfresco/repo/event2/EventJSONSchema.java b/repository/src/main/java/org/alfresco/repo/event2/EventJSONSchema.java
index 49fe24c200..6bae6ecf76 100644
--- a/repository/src/main/java/org/alfresco/repo/event2/EventJSONSchema.java
+++ b/repository/src/main/java/org/alfresco/repo/event2/EventJSONSchema.java
@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
- * Copyright (C) 2005 - 2020 Alfresco Software Limited
+ * Copyright (C) 2005 - 2025 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
@@ -38,7 +38,7 @@ import org.alfresco.util.Pair;
*/
public enum EventJSONSchema
{
- NODE_CREATED_V1("nodeCreated", 1, EventType.NODE_CREATED), NODE_UPDATED_V1("nodeUpdated", 1, EventType.NODE_UPDATED), NODE_DELETED_V1("nodeDeleted", 1, EventType.NODE_DELETED), CHILD_ASSOC_CREATED_V1("childAssocCreated", 1, EventType.CHILD_ASSOC_CREATED), CHILD_ASSOC_DELETED_V1("childAssocDeleted", 1, EventType.CHILD_ASSOC_DELETED), PEER_ASSOC_CREATED_V1("peerAssocCreated", 1, EventType.PEER_ASSOC_CREATED), PEER_ASSOC_DELETED_V1("peerAssocDeleted", 1, EventType.PEER_ASSOC_DELETED), PERMISSION_UPDATED_V1("permissionUpdated", 1, EventType.PERMISSION_UPDATED);
+ NODE_CREATED_V1("nodeCreated", 1, EventType.NODE_CREATED), NODE_UPDATED_V1("nodeUpdated", 1, EventType.NODE_UPDATED), NODE_DELETED_V1("nodeDeleted", 1, EventType.NODE_DELETED), CHILD_ASSOC_CREATED_V1("childAssocCreated", 1, EventType.CHILD_ASSOC_CREATED), CHILD_ASSOC_DELETED_V1("childAssocDeleted", 1, EventType.CHILD_ASSOC_DELETED), PEER_ASSOC_CREATED_V1("peerAssocCreated", 1, EventType.PEER_ASSOC_CREATED), PEER_ASSOC_DELETED_V1("peerAssocDeleted", 1, EventType.PEER_ASSOC_DELETED), PERMISSION_UPDATED_V1("permissionUpdated", 1, EventType.PERMISSION_UPDATED), AUDIT_ENTRY_CREATED_V1("auditEntryCreated", 1, EventType.AUDIT_ENTRY_CREATED);
private static final String PREFIX = "https://api.alfresco.com/schema/event/repo/v";
diff --git a/repository/src/main/resources/alfresco/audit-services-context.xml b/repository/src/main/resources/alfresco/audit-services-context.xml
index 229703437a..0a80ea035a 100644
--- a/repository/src/main/resources/alfresco/audit-services-context.xml
+++ b/repository/src/main/resources/alfresco/audit-services-context.xml
@@ -37,8 +37,9 @@
+
-
+
@@ -108,5 +109,8 @@
-
-
\ No newline at end of file
+
+
+
+
+
diff --git a/repository/src/main/resources/alfresco/repository.properties b/repository/src/main/resources/alfresco/repository.properties
index e9b55c5c3a..1c88e94740 100644
--- a/repository/src/main/resources/alfresco/repository.properties
+++ b/repository/src/main/resources/alfresco/repository.properties
@@ -286,6 +286,8 @@ audit.alfresco-access.enabled=false
audit.alfresco-access.sub-actions.enabled=false
audit.cmischangelog.enabled=false
audit.dod5015.enabled=false
+audit.enabled.auditingToAuditStorage=false
+audit.enabled.auditingToDatabase=true
# Setting this flag to true will force startup failure when invalid audit configurations are detected
audit.config.strict=false
# Audit map filter for AccessAuditor - restricts recorded events to user driven events
diff --git a/repository/src/test/java/org/alfresco/AppContext02TestSuite.java b/repository/src/test/java/org/alfresco/AppContext02TestSuite.java
index 5d6da7f4b3..7fb04e39f6 100644
--- a/repository/src/test/java/org/alfresco/AppContext02TestSuite.java
+++ b/repository/src/test/java/org/alfresco/AppContext02TestSuite.java
@@ -48,6 +48,7 @@ import org.alfresco.util.testing.category.NonBuildTests;
org.alfresco.repo.audit.UserAuditFilterTest.class,
org.alfresco.repo.audit.AuditMethodInterceptorTest.class,
org.alfresco.repo.audit.access.AccessAuditorTest.class,
+ org.alfresco.repo.audit.AuditRecordUtilsTest.class,
// the following test will lock up the DB if run in the applicationContext_01 test suite
org.alfresco.repo.activities.feed.FeedNotifierTest.class,
diff --git a/repository/src/test/java/org/alfresco/repo/audit/AuditComponentTest.java b/repository/src/test/java/org/alfresco/repo/audit/AuditComponentTest.java
index b59ac8351c..76ce4a18a6 100644
--- a/repository/src/test/java/org/alfresco/repo/audit/AuditComponentTest.java
+++ b/repository/src/test/java/org/alfresco/repo/audit/AuditComponentTest.java
@@ -877,6 +877,7 @@ public class AuditComponentTest extends TestCase
auditModelRegistry.loadAuditModels();
auditModelRegistry.setProperty("audit.enabled", "true");
+ auditModelRegistry.setProperty("audit.enabled.auditingToDatabase", "true");
auditModelRegistry.setProperty("audit.app1.enabled", "true");
auditModelRegistry.setProperty("audit.filter.app1.default.enabled", "true");
diff --git a/repository/src/test/java/org/alfresco/repo/audit/AuditRecordUtilsTest.java b/repository/src/test/java/org/alfresco/repo/audit/AuditRecordUtilsTest.java
new file mode 100644
index 0000000000..009c544686
--- /dev/null
+++ b/repository/src/test/java/org/alfresco/repo/audit/AuditRecordUtilsTest.java
@@ -0,0 +1,123 @@
+/*
+ * #%L
+ * Alfresco Repository
+ * %%
+ * Copyright (C) 2025 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+
+package org.alfresco.repo.audit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.io.Serializable;
+import java.time.Instant;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Test;
+
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.namespace.QName;
+
+public class AuditRecordUtilsTest
+{
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testGenerateAuditRecordBuilderTest()
+ {
+ var testData = new HashMap();
+
+ testData.put("/alfresco-access/transaction/path", "/app:company_home");
+ testData.put("/alfresco-access/transaction/user", "admin");
+ testData.put("/alfresco-access/transaction/sub-actions", "updateNodeProperties");
+ var now = Instant.now();
+ testData.put("/alfresco-access/transaction/properties/from", (Serializable) Map.of(QName.createQName("modified"), Date.from(now)));
+ testData.put("/alfresco-access/transaction/properties/to", (Serializable) Map.of(QName.createQName("modified"), Date.from(now)));
+
+ var builder = AuditRecordUtils.generateAuditRecordBuilder(testData, "/alfresco-access/".length());
+ builder.setAuditRecordType("alfresco-access");
+ var auditRecord = builder.build();
+
+ assertNotNull(auditRecord);
+ assertEquals("alfresco-access", auditRecord.getAuditApplicationId());
+
+ var auditData = auditRecord.getAuditData();
+ assertEquals(1, auditData.size());
+
+ var transaction = (HashMap) auditData.get("transaction");
+ assertNotNull(transaction);
+ assertEquals(4, transaction.size());
+ assertEquals(testData.get("/alfresco-access/transaction/path"), transaction.get("path"));
+ assertEquals(testData.get("/alfresco-access/transaction/user"), transaction.get("user"));
+ assertEquals(testData.get("/alfresco-access/transaction/sub-actions"), transaction.get("sub-actions"));
+
+ var properties = (HashMap) transaction.get("properties");
+ assertNotNull(properties);
+ assertEquals(2, properties.size());
+ assertEquals(testData.get("/alfresco-access/transaction/properties/from"), properties.get("from"));
+ assertEquals(testData.get("/alfresco-access/transaction/properties/to"), properties.get("to"));
+
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testGenerateAuditRecordBuilderTestNodeRef()
+ {
+ var testData = new HashMap();
+ var expectedValue = new HashMap();
+
+ expectedValue.put("nodeRef", new NodeRef("workspace://SpacesStore/bfa612e6-1a02-46a0-a612-e61a02e6a036"));
+ expectedValue.put("objectId", "bfa612e6-1a02-46a0-a612-e61a02e6a036;1.0");
+
+ testData.put("/CMISChangeLog/CREATED/result/value", expectedValue);
+
+ var builder = AuditRecordUtils.generateAuditRecordBuilder(testData, "/CMISChangeLog/".length());
+ builder.setAuditRecordType("CMISChangeLog");
+ var auditRecord = builder.build();
+
+ assertNotNull(auditRecord);
+
+ assertEquals("CMISChangeLog", auditRecord.getAuditApplicationId());
+
+ var auditData = auditRecord.getAuditData();
+ assertEquals(1, auditData.size());
+
+ var created = (HashMap) auditData.get("CREATED");
+ assertNotNull(created);
+
+ assertEquals(1, created.size());
+ var result = (HashMap) created.get("result");
+ assertNotNull(result);
+ assertEquals(1, result.size());
+
+ var resultValue = (HashMap) result.get("value");
+ assertNotNull(resultValue);
+ assertEquals(2, resultValue.size());
+
+ var expectedNodeRef = (NodeRef) expectedValue.get("nodeRef");
+ assertEquals(expectedNodeRef.getId(), resultValue.get("nodeRef"));
+ assertEquals(expectedValue.get("objectId"), resultValue.get("objectId"));
+
+ }
+}
diff --git a/repository/src/test/java/org/alfresco/repo/audit/AuditTestSuite.java b/repository/src/test/java/org/alfresco/repo/audit/AuditTestSuite.java
index 754f3412f8..b9c7c6bad8 100644
--- a/repository/src/test/java/org/alfresco/repo/audit/AuditTestSuite.java
+++ b/repository/src/test/java/org/alfresco/repo/audit/AuditTestSuite.java
@@ -50,6 +50,7 @@ public class AuditTestSuite extends TestSuite
suite.addTestSuite(UserAuditFilterTest.class);
suite.addTestSuite(AuditMethodInterceptorTest.class);
+ suite.addTest(new JUnit4TestAdapter(AuditRecordUtilsTest.class));
suite.addTest(new JUnit4TestAdapter(PropertyAuditFilterTest.class));
suite.addTest(new JUnit4TestAdapter(AccessAuditorTest.class));