Compare commits

...

67 Commits

Author SHA1 Message Date
alfresco-build
af995f1087 [maven-release-plugin][skip ci] prepare release 25.3.0.56 2025-10-09 09:23:04 +00:00
Sayan Bhattacharya
2cfcd3dfa7 ACS-9990 changed extract metadata overwrite policy to EAGER (#3562) 2025-10-09 13:18:25 +05:30
alfresco-build
89e09b0162 [maven-release-plugin][skip ci] prepare for next development iteration 2025-10-08 17:09:34 +00:00
alfresco-build
495808b172 [maven-release-plugin][skip ci] prepare release 25.3.0.55 2025-10-08 17:09:31 +00:00
cezary-witkowski
57060af84b [ACS-10454] Bump AOS to fix "Edit in Microsoft Office" error (#3607) 2025-10-08 17:58:19 +02:00
alfresco-build
60261aafd1 [maven-release-plugin][skip ci] prepare for next development iteration 2025-10-07 07:55:25 +00:00
alfresco-build
8dad225394 [maven-release-plugin][skip ci] prepare release 25.3.0.54 2025-10-07 07:55:23 +00:00
Debjit Chattopadhyay
5cc21c55e7 MNT-24776 adding if-else conditionals to avoid null values
MNT-24776 adding if-else conditionals to avoid null values
2025-10-07 12:38:14 +05:30
alfresco-build
c71aaf7537 [maven-release-plugin][skip ci] prepare for next development iteration 2025-10-06 16:27:33 +00:00
alfresco-build
b7d16ac915 [maven-release-plugin][skip ci] prepare release 25.3.0.53 2025-10-06 16:27:31 +00:00
cezary-witkowski
1a436b06e4 [ACS-10454] Bump AOS to fix "Edit in Microsoft Office" error (#3603) 2025-10-06 17:17:28 +02:00
Debjit Chattopadhyay
be02be5a8b MNT-24776 adding if-else conditionals to avoid null values 2025-10-06 19:01:58 +05:30
alfresco-build
a674e574c5 [maven-release-plugin][skip ci] prepare for next development iteration 2025-10-05 00:10:04 +00:00
alfresco-build
aacaa62ff9 [maven-release-plugin][skip ci] prepare release 25.3.0.52 2025-10-05 00:10:02 +00:00
Alfresco CI User
371bd1543d [force] Force release for 2025-10-05. 2025-10-05 00:05:04 +00:00
alfresco-build
4cb16f046f [maven-release-plugin][skip ci] prepare for next development iteration 2025-10-03 11:43:25 +00:00
alfresco-build
2fb7de9ace [maven-release-plugin][skip ci] prepare release 25.3.0.51 2025-10-03 11:43:23 +00:00
Gerard Olenski
ed972c79d7 ACS-10427 Bump ATS 4.2.2 (#3600) 2025-10-03 12:53:22 +02:00
alfresco-build
0f3e2dc4cc [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-28 00:09:54 +00:00
alfresco-build
4e7d0ccae3 [maven-release-plugin][skip ci] prepare release 25.3.0.50 2025-09-28 00:09:51 +00:00
Alfresco CI User
1b5636a339 [force] Force release for 2025-09-28. 2025-09-28 00:05:07 +00:00
alfresco-build
164ce720af [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-26 11:49:16 +00:00
alfresco-build
258738e3dd [maven-release-plugin][skip ci] prepare release 25.3.0.49 2025-09-26 11:49:14 +00:00
Debjit Chattopadhyay
fefd937c89 Reverting MNT-24776 from master branch as this change is affecting other scenarios.
Reverting MNT-24776 from master branch as this change is affecting other scenarios.
2025-09-26 16:33:04 +05:30
alfresco-build
91f9467a99 [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-26 10:18:22 +00:00
alfresco-build
97b1515f7c [maven-release-plugin][skip ci] prepare release 25.3.0.48 2025-09-26 10:18:21 +00:00
Debjit Chattopadhyay
7f235f1e2b Revert MNT-24776 as this fix is affecting other scenarios. 2025-09-26 15:39:24 +05:30
Tiago Salvado
109bdeee0f [ACS-9940] Bump spring security version to 6.4.11 (#3592) 2025-09-26 10:32:06 +01:00
alfresco-build
7c97f49574 [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-26 09:13:07 +00:00
alfresco-build
2088b8b553 [maven-release-plugin][skip ci] prepare release 25.3.0.47 2025-09-26 09:13:04 +00:00
Piotr Żurek
280a873cb6 ACS-9665 add event generation extensions (#3593) 2025-09-26 10:25:10 +02:00
alfresco-build
9683c18448 [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-25 11:38:33 +00:00
alfresco-build
21b36a7100 [maven-release-plugin][skip ci] prepare release 25.3.0.46 2025-09-25 11:38:31 +00:00
Tiago Salvado
96481daae1 [ACS-10155] Bump spring version to 6.2.11 (#3589) 2025-09-25 11:46:17 +01:00
alfresco-build
7ef573699b [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-23 16:38:26 +00:00
alfresco-build
a000df7ceb [maven-release-plugin][skip ci] prepare release 25.3.0.45 2025-09-23 16:38:24 +00:00
dependabot[bot]
4a22735120 Bump org.alfresco:acs-event-model from 1.0.9 to 1.0.11 (#3576)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-23 17:51:34 +02:00
alfresco-build
94d84799be [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-23 11:25:48 +00:00
alfresco-build
754776e30c [maven-release-plugin][skip ci] prepare release 25.3.0.44 2025-09-23 11:25:45 +00:00
cezary-witkowski
28b8bb85e4 [ACS-10041] Repository - CPU spikes and OOM errors with SQL Server 2019 (#3574) 2025-09-23 12:39:13 +02:00
alfresco-build
4910028d51 [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-23 08:07:57 +00:00
alfresco-build
75d0825295 [maven-release-plugin][skip ci] prepare release 25.3.0.43 2025-09-23 08:07:55 +00:00
Gerard Olenski
964cedaebd ACS-10169 Bump ATS (#3584) 2025-09-23 09:23:04 +02:00
alfresco-build
2bda7d7231 [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-22 13:53:31 +00:00
alfresco-build
82d316d802 [maven-release-plugin][skip ci] prepare release 25.3.0.42 2025-09-22 13:53:29 +00:00
Gerard Olenski
1840d1056d ACS-10195 bump tika (#3583) 2025-09-22 14:44:33 +02:00
alfresco-build
334e8c84df [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-22 11:56:19 +00:00
alfresco-build
24309cf4b6 [maven-release-plugin][skip ci] prepare release 25.3.0.41 2025-09-22 11:56:17 +00:00
Tiago Salvado
2d28742a94 [ACS-10166] Include qname and namespace in NodeIdsWhichReferenceContentUrl query (#3579) 2025-09-22 11:42:16 +01:00
alfresco-build
d6503ac1de [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-22 09:11:37 +00:00
alfresco-build
73ef1ed9ff [maven-release-plugin][skip ci] prepare release 25.3.0.40 2025-09-22 09:11:35 +00:00
Gerard Olenski
d3bc9e2b60 ACS-10159 Bump ATS (#3580) 2025-09-22 10:12:24 +02:00
alfresco-build
174186d1ff [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-21 00:09:21 +00:00
alfresco-build
57331afe8f [maven-release-plugin][skip ci] prepare release 25.3.0.39 2025-09-21 00:09:18 +00:00
Alfresco CI User
7c87595b0c [force] Force release for 2025-09-21. 2025-09-21 00:05:02 +00:00
alfresco-build
b7191b175e [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-17 09:27:11 +00:00
alfresco-build
39b19d1ceb [maven-release-plugin][skip ci] prepare release 25.3.0.38 2025-09-17 09:27:09 +00:00
Kacper Magdziarz
9e23b99078 [ACS-9736] Add new bean for intercepting current audit entries. (#3402)
This introduces a new audit record reporting mechanism for intercepting current audit entries. The implementation adds the ability to report audit data to audit storage in addition to the traditional database storage.

Key changes:

- Added new audit record classes and utilities for structured audit data handling
- Introduced configurable audit destination settings (database vs audit storage)
- Created audit record reporter interface and implementation
2025-09-17 10:40:32 +02:00
alfresco-build
ac36ac07e8 [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-16 08:59:50 +00:00
alfresco-build
cc10339577 [maven-release-plugin][skip ci] prepare release 25.3.0.37 2025-09-16 08:59:48 +00:00
SanjoyHyland2025
8aa975fbc3 Merge pull request #3571 from Alfresco/fix/MNT-25186_category_tags_filter_resolve
MNT-25186: fixed the tags and category filters for elasticsearch
2025-09-16 13:44:57 +05:30
alfresco-build
01620b75ff [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-14 00:09:07 +00:00
alfresco-build
9327218f17 [maven-release-plugin][skip ci] prepare release 25.3.0.36 2025-09-14 00:09:04 +00:00
Alfresco CI User
b57373fbe3 [force] Force release for 2025-09-14. 2025-09-14 00:04:51 +00:00
Sanjoy Das
613fb458b9 fix build failed 2025-09-12 14:25:05 +05:30
Sanjoy Das
31cd97b9d2 MNT-25186: fixed the tags and category filters for elasticsearch 2025-09-12 11:11:28 +05:30
alfresco-build
255fe46c8e [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-11 11:54:10 +00:00
66 changed files with 1420 additions and 242 deletions

View File

@@ -1242,7 +1242,7 @@
"filename": "repository/src/main/resources/alfresco/repository.properties",
"hashed_secret": "1459a56410378e4d3ab470eff570e5eae1742762",
"is_verified": false,
"line_number": 312,
"line_number": 314,
"is_secret": false
},
{
@@ -1250,7 +1250,7 @@
"filename": "repository/src/main/resources/alfresco/repository.properties",
"hashed_secret": "84551ae5442affc9f1a2d3b4c86ae8b24860149d",
"is_verified": false,
"line_number": 771,
"line_number": 773,
"is_secret": false
}
],
@@ -1845,5 +1845,5 @@
}
]
},
"generated_at": "2025-06-09T16:43:14Z"
"generated_at": "2025-07-23T08:25:11Z"
}

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-amps</artifactId>
<version>25.3.0.35</version>
<version>25.3.0.56</version>
</parent>
<modules>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-community-parent</artifactId>
<version>25.3.0.35</version>
<version>25.3.0.56</version>
</parent>
<modules>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-automation-community-repo</artifactId>
<version>25.3.0.35</version>
<version>25.3.0.56</version>
</parent>
<build>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-community-parent</artifactId>
<version>25.3.0.35</version>
<version>25.3.0.56</version>
</parent>
<modules>

View File

@@ -15,6 +15,13 @@
<parameter property="end" jdbcType="BIGINT" javaType="java.lang.Long"/>
</parameterMap>
<parameterMap id="parameter_NodeIdsWhichReferenceContentUrl" type="map">
<parameter property="contentUrlShort" jdbcType="VARCHAR" javaType="java.lang.String"/>
<parameter property="contentUrlCrc" jdbcType="BIGINT" javaType="java.lang.Long"/>
<parameter property="localName" jdbcType="VARCHAR" javaType="java.lang.String"/>
<parameter property="uri" jdbcType="VARCHAR" javaType="java.lang.String"/>
</parameterMap>
<resultMap id="result_NodeRefEntity" type="org.alfresco.module.org_alfresco_module_rm.query.NodeRefEntity">
<result property="row" column="row" jdbcType="BIGINT" javaType="java.lang.Long"/>
<result property="protocol" column="protocol" jdbcType="VARCHAR" javaType="java.lang.String"/>
@@ -55,18 +62,21 @@
<!-- Get list of node ids which reference given content url -->
<select id="select_NodeIdsWhichReferenceContentUrl"
parameterType="ContentUrl"
parameterMap="parameter_NodeIdsWhichReferenceContentUrl"
resultMap="result_NodeIds">
select
p.node_id
from
alf_content_url cu
LEFT OUTER JOIN alf_content_data cd ON (cd.content_url_id = cu.id)
LEFT OUTER JOIN alf_node_properties p ON (p.long_value = cd.id)
WHERE
content_url_short = #{contentUrlShort} and
content_url_crc = #{contentUrlCrc}
left outer join alf_content_data cd ON (cd.content_url_id = cu.id)
left outer join alf_node_properties p ON (p.long_value = cd.id)
left outer join alf_qname q ON (q.id = p.qname_id)
left outer join alf_namespace n ON (n.id = q.ns_id)
where
cu.content_url_short = ? and
cu.content_url_crc = ? and
q.local_name = ? and
n.uri = ?
</select>
<select id="select_RecordFoldersWithSchedules"

View File

@@ -8,7 +8,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-community-repo-parent</artifactId>
<version>25.3.0.35</version>
<version>25.3.0.56</version>
</parent>
<properties>

View File

@@ -39,6 +39,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.alfresco.model.ContentModel;
import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
import org.alfresco.repo.domain.contentdata.ContentUrlEntity;
import org.alfresco.repo.domain.node.NodeDAO;
@@ -191,13 +192,19 @@ public class RecordsManagementQueryDAOImpl implements RecordsManagementQueryDAO,
ContentUrlEntity contentUrlEntity = new ContentUrlEntity();
contentUrlEntity.setContentUrl(contentUrl.toLowerCase());
Map<String, Object> params = new HashMap<>(4);
params.put("contentUrlShort", contentUrlEntity.getContentUrlShort());
params.put("contentUrlCrc", contentUrlEntity.getContentUrlCrc());
params.put("localName", ContentModel.PROP_CONTENT.getLocalName());
params.put("uri", ContentModel.PROP_CONTENT.getNamespaceURI());
if (logger.isDebugEnabled())
{
logger.debug("Executing query " + SELECT_NODE_IDS_WHICH_REFERENCE_CONTENT_URL);
}
// Get all the node ids which reference the given content url
List<Long> nodeIds = template.selectList(SELECT_NODE_IDS_WHICH_REFERENCE_CONTENT_URL, contentUrlEntity);
List<Long> nodeIds = template.selectList(SELECT_NODE_IDS_WHICH_REFERENCE_CONTENT_URL, params);
if (logger.isDebugEnabled())
{

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-community-repo-parent</artifactId>
<version>25.3.0.35</version>
<version>25.3.0.56</version>
</parent>
<build>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>25.3.0.35</version>
<version>25.3.0.56</version>
</parent>
<modules>

View File

@@ -8,7 +8,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-amps</artifactId>
<version>25.3.0.35</version>
<version>25.3.0.56</version>
</parent>
<properties>

View File

@@ -134,6 +134,25 @@ function doclist_main()
logger.log("doclist.lib.js - NodeRef: " + parsedArgs.nodeRef + " Query: " + query);
favourites = sanitizeJunkFavouriteKeys(favourites);
if(query === null)
{
return {
luceneQuery: "",
paging: {
totalRecords: 0,
startIndex: 0
},
container: parsedArgs.rootNode,
parent: null,
onlineEditing: utils.moduleInstalled("org.alfresco.module.vti"),
itemCount: {
folders: 0,
documents: 0
},
items: [],
customJSON: slingshotDocLib.getJSON()
};
}
if(Object.keys(favourites).length === 0 && query === null)
{

View File

@@ -233,15 +233,15 @@ var Filters =
filterParams.query = "+ID:\"" + parsedArgs.nodeRef + "\"";
break;
case "tag":
// Remove any trailing "/" character
if (filterData.charAt(filterData.length - 1) == "/")
{
filterData = filterData.slice(0, -1);
}
filterQuery = this.constructPathQuery(parsedArgs);
filterParams.query = filterQuery + " +PATH:\"/cm:taggable/cm:" + search.ISO9075Encode(filterData) + "/member\"";
break;
case "tag":
// Remove any trailing "/" character
if (filterData.charAt(filterData.length - 1) == "/")
{
filterData = filterData.slice(0, -1);
}
filterQuery = this.constructPathQuery(parsedArgs);
filterParams.query = filterQuery + " +TAG:\"" + search.ISO9075Encode(filterData) + "\"";
break;
case "category":
// Remove any trailing "/" character
@@ -249,8 +249,15 @@ var Filters =
{
filterData = filterData.slice(0, -1);
}
filterQuery = this.constructPathQuery(parsedArgs);
filterParams.query = filterQuery + " +PATH:\"/cm:categoryRoot/cm:generalclassifiable" + Filters.iso9075EncodePath(filterData) + "/member\"";
var categoryNodeRef = this.getCategoryNodeRef(filterData);
if (categoryNodeRef && search.findNode(categoryNodeRef) != null) {
filterParams.query = filterQuery + ' +@cm\\:categories:"' + categoryNodeRef + '"';
} else {
logger.warn("category filter: skipping invalid category node : " + categoryNodeRef);
}
filterParams.language = "fts-alfresco";
break;
case "aspect":
@@ -271,11 +278,24 @@ var Filters =
{
filterParams.query += " " + (Filters.TYPE_MAP[parsedArgs.type] || "");
}
logger.warn("Final Query : " + filterParams.query);
return filterParams;
},
constructPathQuery: function constructPathQuery(parsedArgs)
getCategoryNodeRef: function(categoryName) {
var results = search.luceneSearch(
'PATH:"/cm:categoryRoot/cm:generalclassifiable//*" AND @cm\\:name:"' + categoryName + '"'
);
if (results && results.length > 0) {
return results[0].nodeRef.toString();
}
logger.warn("Category not found: " + categoryName);
return null;
},
constructPathQuery: function(parsedArgs)
{
var pathQuery = "";
if (parsedArgs.libraryRoot != companyhome || parsedArgs.nodeRef != "alfresco://company/home")

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>25.3.0.35</version>
<version>25.3.0.56</version>
</parent>
<dependencies>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>25.3.0.35</version>
<version>25.3.0.56</version>
</parent>
<properties>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>25.3.0.35</version>
<version>25.3.0.56</version>
</parent>
<dependencies>

View File

@@ -9,6 +9,6 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-packaging</artifactId>
<version>25.3.0.35</version>
<version>25.3.0.56</version>
</parent>
</project>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-packaging</artifactId>
<version>25.3.0.35</version>
<version>25.3.0.56</version>
</parent>
<properties>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>25.3.0.35</version>
<version>25.3.0.56</version>
</parent>
<modules>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-packaging</artifactId>
<version>25.3.0.35</version>
<version>25.3.0.56</version>
</parent>
<modules>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-tests</artifactId>
<version>25.3.0.35</version>
<version>25.3.0.56</version>
</parent>
<organization>

View File

@@ -9,7 +9,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-tests</artifactId>
<version>25.3.0.35</version>
<version>25.3.0.56</version>
</parent>
<developers>

View File

@@ -9,7 +9,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-tests</artifactId>
<version>25.3.0.35</version>
<version>25.3.0.56</version>
</parent>
<developers>

View File

@@ -8,7 +8,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-tests</artifactId>
<version>25.3.0.35</version>
<version>25.3.0.56</version>
</parent>
<properties>

View File

@@ -9,7 +9,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-tests</artifactId>
<version>25.3.0.35</version>
<version>25.3.0.56</version>
</parent>
<developers>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-packaging</artifactId>
<version>25.3.0.35</version>
<version>25.3.0.56</version>
</parent>
<properties>

32
pom.xml
View File

@@ -2,7 +2,7 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>alfresco-community-repo</artifactId>
<version>25.3.0.35</version>
<version>25.3.0.56</version>
<packaging>pom</packaging>
<name>Alfresco Community Repo Parent</name>
@@ -51,14 +51,14 @@
<dependency.alfresco-server-root.version>7.0.2</dependency.alfresco-server-root.version>
<dependency.activiti-engine.version>5.23.0</dependency.activiti-engine.version>
<dependency.activiti.version>5.23.0</dependency.activiti.version>
<dependency.alfresco-transform-core.version>5.2.1</dependency.alfresco-transform-core.version>
<dependency.alfresco-transform-service.version>4.2.1</dependency.alfresco-transform-service.version>
<dependency.alfresco-transform-core.version>5.2.2</dependency.alfresco-transform-core.version>
<dependency.alfresco-transform-service.version>4.2.2</dependency.alfresco-transform-service.version>
<dependency.alfresco-greenmail.version>7.1</dependency.alfresco-greenmail.version>
<dependency.acs-event-model.version>1.0.5</dependency.acs-event-model.version>
<dependency.acs-event-model.version>1.0.11</dependency.acs-event-model.version>
<dependency.aspectj.version>1.9.22.1</dependency.aspectj.version>
<dependency.spring.version>6.2.8</dependency.spring.version>
<dependency.spring-security.version>6.3.9</dependency.spring-security.version>
<dependency.spring.version>6.2.11</dependency.spring.version>
<dependency.spring-security.version>6.4.11</dependency.spring-security.version>
<dependency.antlr.version>3.5.3</dependency.antlr.version>
<dependency.jackson.version>2.17.2</dependency.jackson.version>
<dependency.cxf.version>4.1.2</dependency.cxf.version>
@@ -82,7 +82,7 @@
<dependency.slf4j.version>2.0.16</dependency.slf4j.version>
<dependency.log4j.version>2.25.1</dependency.log4j.version>
<dependency.groovy.version>3.0.25</dependency.groovy.version>
<dependency.tika.version>2.9.2</dependency.tika.version>
<dependency.tika.version>3.2.3</dependency.tika.version>
<dependency.truezip.version>7.7.10</dependency.truezip.version>
<dependency.poi.version>5.4.0</dependency.poi.version>
<dependency.jboss.logging.version>3.5.0.Final</dependency.jboss.logging.version>
@@ -115,7 +115,7 @@
<dependency.jakarta-json-path.version>2.9.0</dependency.jakarta-json-path.version>
<dependency.json-smart.version>2.5.2</dependency.json-smart.version>
<alfresco.googledrive.version>4.1.0</alfresco.googledrive.version>
<alfresco.aos-module.version>3.3.0</alfresco.aos-module.version>
<alfresco.aos-module.version>3.4.0</alfresco.aos-module.version>
<alfresco.api-explorer.version>25.2.0</alfresco.api-explorer.version> <!-- Also in alfresco-enterprise-share -->
<alfresco.maven-plugin.version>2.2.0</alfresco.maven-plugin.version>
@@ -154,7 +154,7 @@
<connection>scm:git:https://github.com/Alfresco/alfresco-community-repo.git</connection>
<developerConnection>scm:git:https://github.com/Alfresco/alfresco-community-repo.git</developerConnection>
<url>https://github.com/Alfresco/alfresco-community-repo</url>
<tag>25.3.0.35</tag>
<tag>25.3.0.56</tag>
</scm>
<distributionManagement>
@@ -170,6 +170,12 @@
<dependencyManagement>
<dependencies>
<!-- v1.10 has 0BSD license it must be consulted with Legal -->
<dependency>
<groupId>org.tukaani</groupId>
<artifactId>xz</artifactId>
<version>1.9</version>
</dependency>
<!-- Jakarta... -->
<dependency>
<groupId>jakarta.xml.bind</groupId>
@@ -1125,16 +1131,10 @@
<exclude>jakarta.xml.soap:jakarta.xml.soap-api:(, 2.0.1)</exclude>
<exclude>jakarta.jws:jakarta.jws-api:(, 3.0.0)</exclude>
<!-- Enforce ban bouncycastle dependencies other than specified under <includes> section-->
<exclude>org.bouncycastle</exclude>
<exclude>org.bouncycastle:(,1.81)</exclude>
<!-- Enforce one version of Jaxb-->
<exclude>com.sun.xml.bind</exclude>
</excludes>
<includes>
<include>org.bouncycastle:bcprov-jdk18on:[1.78.1,)</include>
<include>org.bouncycastle:bcmail-jdk18on:[1.78.1,)</include>
<include>org.bouncycastle:bcpkix-jdk18on:[1.78.1,)</include>
<include>org.bouncycastle:bcutil-jdk18on:[1.78.1,)</include>
</includes>
</bannedDependencies>
</rules>
<fail>true</fail>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>25.3.0.35</version>
<version>25.3.0.56</version>
</parent>
<dependencies>

View File

@@ -51,6 +51,14 @@ function main()
}
}
var contentChanged = false;
if (itemKind === "node") {
contentChanged = metadataExtractAction.isContentChanged(itemId,repoFormData);
}
if(logger.isLoggingEnabled() && contentChanged) {
logger.log("Content has been changed");
}
var persistedObject = null;
try
{
@@ -83,9 +91,50 @@ function main()
return;
}
if (itemKind === "node") {
checkAndExtractNodeMetadata(persistedObject, itemId, contentChanged);
}
model.persistedObject = persistedObject.toString();
model.message = "Successfully persisted form for item [" + itemKind + "]" + itemId;
}
function checkAndExtractNodeMetadata(persistedObject, itemId, isContentChanged) {
var nodeRefStr = toNodeRefString(persistedObject, itemId);
var node = search.findNode(nodeRefStr);
if (node == null) {
if (logger.isLoggingEnabled()) {
logger.log("Node not found: " + nodeRefStr);
}
} else if(isContentChanged) {
extractMetadata(node, isContentChanged);
} else {
if (logger.isLoggingEnabled()) {
logger.log("Content not changed, skipping metadata extraction for node: " + nodeRefStr);
}
}
}
function extractMetadata(file, isContentChanged) {
var emAction = metadataExtractAction.create(isContentChanged);
if (emAction) {
// readOnly=false, newTransaction=false
emAction.execute(file, false, false);
}
}
function toNodeRefString(persistedObject, itemId) {
// Prefer the NodeRef returned by saveForm (when kind=node).
if (persistedObject instanceof Packages.org.alfresco.service.cmr.repository.NodeRef) {
return persistedObject.toString();
}
// If the client passed a full noderef, keep it.
if (itemId && itemId.indexOf("://") !== -1) {
return itemId;
}
// Otherwise assume SpacesStore UUID.
return "workspace://SpacesStore/" + itemId;
}
main();

View File

@@ -1,85 +1,92 @@
<#macro renderParent node indent=" ">
<#escape x as jsonUtils.encodeJSONString(x)>
${indent}"parent":
${indent}{
<#if (node != rootNode) && node.parent??>
<@renderParent node.parent indent+" " />
</#if>
${indent}"type": "${node.typeShort}",
${indent}"isContainer": ${node.isContainer?string},
${indent}"name": "${node.properties.name!""}",
${indent}"title": "${node.properties.title!""}",
${indent}"description": "${node.properties.description!""}",
<#if node.properties.modified??>${indent}"modified": "${xmldate(node.properties.modified)}",</#if>
<#if node.properties.modifier??>${indent}"modifier": "${node.properties.modifier}",</#if>
${indent}"displayPath": "${node.displayPath!""}",
${indent}"qnamePath": "${node.qnamePath!""}",
<#if node.aspects??>
${indent}"aspects":
${indent}[
<#list node.aspects as aspect>
"${shortQName(aspect)}"
<#if aspect_has_next>,</#if>
</#list>
${indent}],
</#if>
${indent}"nodeRef": "${node.nodeRef}"
${indent}},
</#escape>
</#macro>
<#macro pickerResultsJSON results>
<#escape x as jsonUtils.encodeJSONString(x)>
{
"data":
{
<#if parent??>
<@renderParent parent />
</#if>
"items":
[
<#list results as row>
<#if row.item.hasPermission("Read")>
{
"type": "${row.item.typeShort}",
"parentType": "${row.item.parentTypeShort!""}",
"isContainer": ${row.item.isContainer?string},
<#if row.container??>"container": "${row.container!""}",</#if>
"name": "${row.item.properties.name!""}",
<#if row.item.aspects??>
"aspects": [
<#list row.item.aspects as aspect>
"${shortQName(aspect)}"
<#if aspect_has_next>,</#if>
</#list>
],
</#if>
"title":<#if row.item.properties["lnk:title"]??>"${row.item.properties["lnk:title"]}",
<#elseif row.item.properties["ia:whatEvent"]??>"${row.item.properties["ia:whatEvent"]}",
<#else>"${row.item.properties.title!""}",</#if>
"description": "${row.item.properties.description!""}",
<#if row.item.properties.modified??>"modified": "${xmldate(row.item.properties.modified)}",</#if>
<#if row.item.properties.modifier??>"modifier": "${row.item.properties.modifier}",</#if>
<#if row.item.siteShortName??>"site": "${row.item.siteShortName}",</#if>
<#if row.item.properties["ia:fromDate"]??>"fromDate": "${xmldate(row.item.properties["ia:fromDate"])}",</#if>
"displayPath": "${row.item.displayPath!""}",
"qnamePath": "${row.item.qnamePath!""}",
<#if row.item.typeShort != "cm:person" && row.item.typeShort != "cm:authorityContainer">
"userAccess":
{
"create": ${row.item.hasPermission("CreateChildren")?string},
"edit": ${row.item.hasPermission("Write")?string},
"delete": ${row.item.hasPermission("Delete")?string}
},
</#if>
"nodeRef": "${row.item.nodeRef}"<#if row.selectable?exists>,
"selectable" : ${row.selectable?string}</#if>
}<#if row_has_next>,</#if>
</#if>
</#list>
]
}
}
</#escape>
<#macro renderParent node indent=" ">
<#escape x as jsonUtils.encodeJSONString(x)>
${indent}"parent":
${indent}{
<#if (node != rootNode) && node.parent??>
<@renderParent node.parent indent+" " />
</#if>
${indent}"type": "${node.typeShort}",
${indent}"isContainer": ${node.isContainer?string},
${indent}"name": "${node.properties.name!""}",
${indent}"title": "${node.properties.title!""}",
${indent}"description": "${node.properties.description!""}",
<#if node.properties.modified??>${indent}"modified": "${xmldate(node.properties.modified)}",</#if>
<#if node.properties.modifier??>${indent}"modifier": "${node.properties.modifier}",</#if>
${indent}"displayPath": "${node.displayPath!""}",
${indent}"qnamePath": "${node.qnamePath!""}",
<#if node.aspects??>
${indent}"aspects":
${indent}[
<#list node.aspects as aspect>
"${shortQName(aspect)}"
<#if aspect_has_next>,</#if>
</#list>
${indent}],
</#if>
${indent}"nodeRef": "${node.nodeRef}"
${indent}},
</#escape>
</#macro>
<#macro pickerResultsJSON results>
<#escape x as jsonUtils.encodeJSONString(x)>
{
"data":
{
<#if parent??>
<@renderParent parent />
</#if>
"items":
[
<#list results as row>
{
"type": "${row.item.typeShort}",
"parentType": "${row.item.parentTypeShort!""}",
"isContainer": ${row.item.isContainer?string},
<#if row.container??>"container": "${row.container!""}",</#if>
<#if row.item.properties?? && row.item.properties.name??>
"name": "${row.item.properties.name!""}",
<#else>
"name": "${(row.item.name)!row.item?string!""}",
</#if>
<#if row.item.aspects??>
"aspects": [
<#list row.item.aspects as aspect>
"${shortQName(aspect)}"
<#if aspect_has_next>,</#if>
</#list>
],
</#if>
<#if row.item.properties??>
"title":<#if row.item.properties["lnk:title"]??>"${row.item.properties["lnk:title"]}",
<#elseif row.item.properties["ia:whatEvent"]??>"${row.item.properties["ia:whatEvent"]}",
<#else>"${row.item.properties.title!""}",</#if>
"description": "${row.item.properties.description!""}",
<#else>
"title": "${(row.item.name)!row.item?string!""}",
"description": "",
</#if>
<#if row.item.properties.modified??>"modified": "${xmldate(row.item.properties.modified)}",</#if>
<#if row.item.properties.modifier??>"modifier": "${row.item.properties.modifier}",</#if>
<#if row.item.siteShortName??>"site": "${row.item.siteShortName}",</#if>
<#if row.item.properties["ia:fromDate"]??>"fromDate": "${xmldate(row.item.properties["ia:fromDate"])}",</#if>
"displayPath": "${row.item.displayPath!""}",
"qnamePath": "${row.item.qnamePath!""}",
<#if row.item.typeShort != "cm:person" && row.item.typeShort != "cm:authorityContainer">
"userAccess":
{
"create": ${row.item.hasPermission("CreateChildren")?string},
"edit": ${row.item.hasPermission("Write")?string},
"delete": ${row.item.hasPermission("Delete")?string}
},
</#if>
"nodeRef": "${row.item.nodeRef}"<#if row.selectable?exists>,
"selectable" : ${row.selectable?string}</#if>
}<#if row_has_next>,</#if>
</#list>
]
}
}
</#escape>
</#macro>

View File

@@ -2,7 +2,7 @@ function extractMetadata(file)
{
// Extract metadata - via repository action for now.
// This should use the MetadataExtracter API to fetch properties, allowing for possible failures.
var emAction = actions.create("extract-metadata");
var emAction = metadataExtractAction.create(true);
if (emAction != null)
{
// Call using readOnly = false, newTransaction = false

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>25.3.0.35</version>
<version>25.3.0.56</version>
</parent>
<dependencies>

View File

@@ -0,0 +1,67 @@
/*
* #%L
* Alfresco Repository
* %%
* 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
* 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 <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.repo.action.evaluator;
import java.util.List;
import org.alfresco.service.cmr.action.ActionCondition;
import org.alfresco.service.cmr.action.ParameterDefinition;
import org.alfresco.service.cmr.repository.NodeRef;
/**
* Content change condition evaluator implementation. Required only in Scripted Actions to allow determination if content has changed. <br>
* Usage in {@link org.alfresco.repo.jscript.MetaDataExtractAction#create(boolean)}
*
* @author Sayan Bhattacharya
*/
public class CompareContentConditionEvaluator extends ActionConditionEvaluatorAbstractBase
{
/**
* Evaluator constants
*/
public static final String NAME = "compare-content";
public static final String PARAM_IS_CONTENT_CHANGED = "isContentChanged";
/**
* @see ActionConditionEvaluatorAbstractBase#evaluateImpl(ActionCondition, NodeRef)
*/
@Override
public boolean evaluateImpl(ActionCondition ruleCondition, NodeRef actionedUponNodeRef)
{
return true;
}
/**
* @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(List)
*/
@Override
protected void addParameterDefinitions(List<ParameterDefinition> paramList)
{
// No parameters to add
}
}

View File

@@ -51,12 +51,14 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.action.evaluator.CompareContentConditionEvaluator;
import org.alfresco.repo.content.metadata.AbstractMappingMetadataExtracter;
import org.alfresco.repo.content.metadata.AsynchronousExtractor;
import org.alfresco.repo.content.metadata.MetadataExtracter;
@@ -403,6 +405,7 @@ public class ContentMetadataExtracter extends ActionExecuterAbstractBase
((AbstractMappingMetadataExtracter) extracter).setEnableStringTagging(enableStringTagging);
}
MetadataExtracter.OverwritePolicy overwritePolicy = determineOverwritePolicy(ruleAction);
// Get all the node's properties
Map<QName, Serializable> nodeProperties = nodeService.getProperties(actionedUponNodeRef);
@@ -415,7 +418,7 @@ public class ContentMetadataExtracter extends ActionExecuterAbstractBase
modifiedProperties = extracter.extract(
actionedUponNodeRef,
reader,
/* OverwritePolicy.PRAGMATIC, */
overwritePolicy,
nodeProperties);
}
catch (Throwable e)
@@ -456,6 +459,21 @@ public class ContentMetadataExtracter extends ActionExecuterAbstractBase
stringTaggingSeparators);
}
private MetadataExtracter.OverwritePolicy determineOverwritePolicy(Action ruleAction)
{
return Optional.ofNullable(ruleAction.getActionConditions())
.flatMap(conditions -> conditions.stream()
.filter(e -> CompareContentConditionEvaluator.NAME.equals(e.getActionConditionDefinitionName()))
.findAny()
.map(e -> {
Serializable contentChanged = e.getParameterValue(CompareContentConditionEvaluator.PARAM_IS_CONTENT_CHANGED);
return Boolean.TRUE.equals(contentChanged)
? MetadataExtracter.OverwritePolicy.EAGER
: MetadataExtracter.OverwritePolicy.PRAGMATIC;
}))
.orElse(MetadataExtracter.OverwritePolicy.PRAGMATIC);
}
public static void addExtractedMetadataToNode(NodeRef actionedUponNodeRef, Map<QName, Serializable> nodeProperties,
Map<QName, Serializable> modifiedProperties,
NodeService nodeService, DictionaryService dictionaryService,

View File

@@ -70,6 +70,13 @@ public interface AuditComponent
*/
public void setUserAuditFilter(UserAuditFilter userAuditFilter);
/**
* @param auditRecordReporter
* AuditRecordReporter
* @since 25.3
*/
public void setAuditRecordReporter(AuditRecordReporter auditRecordReporter);
/**
* Get all registered audit applications, whether active or not.
*

View File

@@ -48,7 +48,6 @@ import org.alfresco.repo.audit.model.AuditModelRegistryImpl;
import org.alfresco.repo.domain.audit.AuditDAO;
import org.alfresco.repo.domain.propval.PropertyValueDAO;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
@@ -73,8 +72,8 @@ public class AuditComponentImpl implements AuditComponent
{
private static final String INBOUND_LOGGER = "org.alfresco.repo.audit.inbound";
private static Log logger = LogFactory.getLog(AuditComponentImpl.class);
private static Log loggerInbound = LogFactory.getLog(INBOUND_LOGGER);
private static final Log logger = LogFactory.getLog(AuditComponentImpl.class);
private static final Log loggerInbound = LogFactory.getLog(INBOUND_LOGGER);
private AuditModelRegistryImpl auditModelRegistry;
private PropertyValueDAO propertyValueDAO;
@@ -82,6 +81,7 @@ public class AuditComponentImpl implements AuditComponent
private TransactionService transactionService;
private AuditFilter auditFilter;
private UserAuditFilter userAuditFilter;
private AuditRecordReporter auditRecordReporter;
/**
* Default constructor
@@ -140,6 +140,11 @@ public class AuditComponentImpl implements AuditComponent
this.userAuditFilter = userAuditFilter;
}
public void setAuditRecordReporter(AuditRecordReporter auditRecordReporter)
{
this.auditRecordReporter = auditRecordReporter;
}
/**
* {@inheritDoc}
*
@@ -215,7 +220,7 @@ public class AuditComponentImpl implements AuditComponent
public int deleteAuditEntries(List<Long> auditEntryIds)
{
// Shortcut, if necessary
if (auditEntryIds.size() == 0)
if (auditEntryIds.isEmpty())
{
return 0;
}
@@ -234,7 +239,7 @@ public class AuditComponentImpl implements AuditComponent
{
Long disabledPathsId = application.getDisabledPathsId();
Set<String> disabledPaths = (Set<String>) propertyValueDAO.getPropertyById(disabledPathsId);
return new HashSet<String>(disabledPaths);
return new HashSet<>(disabledPaths);
}
catch (Throwable e)
{
@@ -254,6 +259,16 @@ public class AuditComponentImpl implements AuditComponent
return auditModelRegistry.isAuditEnabled();
}
public boolean isAuditingToDatabaseEnabled()
{
return auditModelRegistry.isAuditingToDatabaseEnabled();
}
public boolean isAuditingToAuditStorageEnabled()
{
return auditModelRegistry.isAuditingToAuditStorageEnabled();
}
/**
* {@inheritDoc}
*
@@ -309,7 +324,7 @@ public class AuditComponentImpl implements AuditComponent
{
PathMapper pathMapper = auditModelRegistry.getAuditPathMapper();
Set<String> mappedPaths = pathMapper.getMappedPathsWithPartialMatch(path);
return loggerInbound.isDebugEnabled() || mappedPaths.size() > 0;
return loggerInbound.isDebugEnabled() || !mappedPaths.isEmpty();
}
/**
@@ -346,7 +361,7 @@ public class AuditComponentImpl implements AuditComponent
// Check if there are any entries that match or supercede the given path
String disablingPath = null;
;
for (String disabledPath : disabledPaths)
{
if (path.startsWith(disabledPath))
@@ -573,7 +588,7 @@ public class AuditComponentImpl implements AuditComponent
}
// Build the key paths using the session root path
Map<String, Serializable> pathedValues = new HashMap<String, Serializable>(values.size() * 2);
Map<String, Serializable> pathedValues = new HashMap<>(values.size() * 2);
for (Map.Entry<String, Serializable> entry : values.entrySet())
{
String pathElement = entry.getKey();
@@ -596,12 +611,7 @@ public class AuditComponentImpl implements AuditComponent
case TXN_NONE:
case TXN_READ_ONLY:
// New transaction
RetryingTransactionCallback<Map<String, Serializable>> callback = new RetryingTransactionCallback<Map<String, Serializable>>() {
public Map<String, Serializable> execute() throws Throwable
{
return recordAuditValuesImpl(mappedValues);
}
};
RetryingTransactionCallback<Map<String, Serializable>> callback = () -> recordAuditValuesImpl(mappedValues);
RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
txnHelper.setForceWritable(true);
return txnHelper.doInTransaction(callback, false, true);
@@ -618,21 +628,16 @@ public class AuditComponentImpl implements AuditComponent
public Map<String, Serializable> recordAuditValuesImpl(Map<String, Serializable> mappedValues)
{
// Group the values by root path
Map<String, Map<String, Serializable>> mappedValuesByRootKey = new HashMap<String, Map<String, Serializable>>();
Map<String, Map<String, Serializable>> mappedValuesByRootKey = new HashMap<>();
for (Map.Entry<String, Serializable> entry : mappedValues.entrySet())
{
String path = entry.getKey();
String rootKey = AuditApplication.getRootKey(path);
Map<String, Serializable> rootKeyMappedValues = mappedValuesByRootKey.get(rootKey);
if (rootKeyMappedValues == null)
{
rootKeyMappedValues = new HashMap<String, Serializable>(7);
mappedValuesByRootKey.put(rootKey, rootKeyMappedValues);
}
Map<String, Serializable> rootKeyMappedValues = mappedValuesByRootKey.computeIfAbsent(rootKey, k -> new HashMap<>(7));
rootKeyMappedValues.put(path, entry.getValue());
}
Map<String, Serializable> allAuditedValues = new HashMap<String, Serializable>(mappedValues.size() * 2 + 1);
Map<String, Serializable> allAuditedValues = new HashMap<>(mappedValues.size() * 2 + 1);
// Now audit for each of the root keys
for (Map.Entry<String, Map<String, Serializable>> entry : mappedValuesByRootKey.entrySet())
{
@@ -694,7 +699,7 @@ public class AuditComponentImpl implements AuditComponent
}
// Check if there is anything to audit
if (values.size() == 0)
if (values.isEmpty())
{
if (logger.isDebugEnabled())
{
@@ -727,12 +732,7 @@ public class AuditComponentImpl implements AuditComponent
Map<String, Serializable> auditData = generateData(generators);
// Now extract values
Map<String, Serializable> extractedData = AuthenticationUtil.runAs(new RunAsWork<Map<String, Serializable>>() {
public Map<String, Serializable> doWork() throws Exception
{
return extractData(application, values);
}
}, AuthenticationUtil.getSystemUserName());
Map<String, Serializable> extractedData = AuthenticationUtil.runAs(() -> extractData(application, values), AuthenticationUtil.getSystemUserName());
// Combine extracted and generated values (extracted data takes precedence)
auditData.putAll(extractedData);
@@ -743,8 +743,8 @@ public class AuditComponentImpl implements AuditComponent
{
String root = value.getKey();
int index = root.lastIndexOf("/");
Map<String, Serializable> argc = new HashMap<String, Serializable>(1);
argc.put(root.substring(index, root.length()).substring(1), value.getValue());
Map<String, Serializable> argc = new HashMap<>(1);
argc.put(root.substring(index).substring(1), value.getValue());
if (!auditFilter.accept(root.substring(0, index), argc))
{
return Collections.emptyMap();
@@ -760,10 +760,15 @@ public class AuditComponentImpl implements AuditComponent
{
// Persist the values (if not just gathering data in a pre call for use in a post call)
boolean justGatherPreCallData = application.isApplicationJustGeneratingPreCallData();
if (!justGatherPreCallData)
if (!justGatherPreCallData && isAuditingToDatabaseEnabled())
{
entryId = auditDAO.createAuditEntry(applicationId, time, username, auditData);
}
if (isAuditingToAuditStorageEnabled())
{
auditRecordReporter.reportAuditRecord(createAuditRecord(auditData, true, username, entryId, application.getApplicationName()));
}
// Done
if (logger.isDebugEnabled())
{
@@ -822,7 +827,7 @@ public class AuditComponentImpl implements AuditComponent
AuditApplication application,
Map<String, Serializable> values)
{
Map<String, Serializable> newData = new HashMap<String, Serializable>(values.size());
Map<String, Serializable> newData = new HashMap<>(values.size());
List<DataExtractorDefinition> extractors = application.getDataExtractors();
for (DataExtractorDefinition extractorDef : extractors)
@@ -900,7 +905,7 @@ public class AuditComponentImpl implements AuditComponent
*/
private Map<String, Serializable> generateData(Map<String, DataGenerator> generators)
{
Map<String, Serializable> newData = new HashMap<String, Serializable>(generators.size() + 5);
Map<String, Serializable> newData = new HashMap<>(generators.size() + 5);
for (Map.Entry<String, DataGenerator> entry : generators.entrySet())
{
String path = entry.getKey();
@@ -925,6 +930,20 @@ public class AuditComponentImpl implements AuditComponent
return newData;
}
/**
* Creates an AuditRecord from the provided audit data.
*/
private AuditRecord createAuditRecord(Map<String, Serializable> auditData, boolean inTransaction, String username, Long entryId, String applicationName)
{
int rootSize = applicationName.length() + 2; // Root is constructed like this -> '/' + auditedApplicationName + '/'.
AuditRecord.Builder builder = AuditRecordUtils.generateAuditRecordBuilder(auditData, rootSize);
builder.setAuditRecordType(applicationName);
builder.setInTransaction(inTransaction);
builder.setUsername(username);
builder.setEntryDBId(entryId);
return builder.build();
}
/**
* {@inheritDoc}
*/

View File

@@ -0,0 +1,130 @@
/*
* #%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 <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.repo.audit;
import java.io.Serializable;
import java.time.ZonedDateTime;
import java.util.Map;
public class AuditRecord
{
private final boolean inTransaction;
private final String auditApplicationId;
private final ZonedDateTime createdAt;
private final String username;
private final Long entryDBId;
private final Map<String, Serializable> auditData;
public AuditRecord(Builder builder)
{
this.auditApplicationId = builder.auditRecordType;
this.inTransaction = builder.inTransaction;
this.auditData = builder.auditRecordData;
this.createdAt = ZonedDateTime.now();
this.username = builder.username;
this.entryDBId = builder.entryDBId;
}
public String getAuditApplicationId()
{
return auditApplicationId;
}
public boolean isInTransaction()
{
return inTransaction;
}
public ZonedDateTime getCreatedAt()
{
return createdAt;
}
public String getUsername()
{
return username;
}
public Long getEntryDBId()
{
return entryDBId;
}
public Map<String, Serializable> getAuditData()
{
return auditData;
}
public static Builder builder()
{
return new Builder();
}
public static class Builder
{
private String auditRecordType;
private boolean inTransaction;
private Map<String, Serializable> auditRecordData;
private String username;
private Long entryDBId;
public Builder setAuditRecordType(String auditRecordType)
{
this.auditRecordType = auditRecordType;
return this;
}
public Builder setInTransaction(boolean inTransaction)
{
this.inTransaction = inTransaction;
return this;
}
public Builder setAuditRecordData(Map<String, Serializable> 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);
}
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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);
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.repo.audit;
public class AuditRecordReporterImpl implements AuditRecordReporter
{
/**
* This method intentionally has an empty implementation.
* <p>
* This class provides a no-op implementation of {@link AuditRecordReporter}.
*/
@Override
public void reportAuditRecord(AuditRecord auditRecord)
{
// No operation performed.
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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.
* <p>
* This method:
* <ul>
* <li>Translates flat {@code key-value} pairs into a nested JSON structure.</li>
* <li>Preloads the builder with the provided arguments.</li>
* <li>Splits keys by {@code /} to build the nested structure.</li>
* <li>Uses the root key as the application ID.</li>
* <li>Assumes each key starts with the same root, constructed as {@code '/' + auditedApplicationName + '/'}, which is removed before splitting.</li>
* </ul>
*
* @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<String, Serializable> data, int keyRootLength)
{
var auditRecordBuilder = AuditRecord.builder();
var rootNode = createRootNode(data, keyRootLength);
auditRecordBuilder.setAuditRecordData(rootNode);
return auditRecordBuilder;
}
@SuppressWarnings("unchecked")
private static HashMap<String, Serializable> createRootNode(Map<String, Serializable> data, int keyRootLength)
{
var rootNode = new HashMap<String, Serializable>();
data.forEach((k, v) -> {
var keys = k.substring(keyRootLength).split("/");
var current = rootNode;
for (int i = 0; i < keys.length - 1; i++)
{
current = (HashMap<String, Serializable>) current.computeIfAbsent(keys[i], newMap -> new HashMap<String, Serializable>());
}
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<String, Serializable>) value, 0);
}
else if (value instanceof NodeRef)
{
return ((NodeRef) value).getId();
}
else
{
return value;
}
}
}

View File

@@ -58,6 +58,21 @@ public interface AuditModelRegistry
*/
public boolean isAuditEnabled();
/**
* Determines whether audit values should be stored in database. <code>True</code> by default if not changed by property.
*
* @return <code>true</code> if audit is enabled.
*/
boolean isAuditingToDatabaseEnabled();
/**
* Determines whether audit values should be stored in audit storage.
*
* @return <code>true</code> if auditing to Audit Storage is enabled.
*
*/
boolean isAuditingToAuditStorageEnabled();
/**
* Get a map of all audit applications key by name
*

View File

@@ -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

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2021 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
@@ -102,6 +102,7 @@ import org.alfresco.service.namespace.QName;
* @author Jesper Steen Møller
* @author Derek Hulley
*/
@SuppressWarnings("PMD.CyclomaticComplexity")
@AlfrescoPublicApi
abstract public class AbstractMappingMetadataExtracter implements MetadataExtracter, MetadataEmbedder, BeanNameAware, ApplicationContextAware
{
@@ -1118,6 +1119,15 @@ abstract public class AbstractMappingMetadataExtracter implements MetadataExtrac
return extract(nodeRef, reader, overwritePolicy, destination, mapping);
}
/**
* {@inheritDoc}
*/
@Override
public Map<QName, Serializable> extract(NodeRef nodeRef, ContentReader reader, OverwritePolicy overwritePolicy, Map<QName, Serializable> destination)
{
return extract(nodeRef, reader, overwritePolicy, destination, mapping);
}
/**
* {@inheritDoc}
*/
@@ -1154,7 +1164,7 @@ abstract public class AbstractMappingMetadataExtracter implements MetadataExtrac
// Check that the content has some meat
if (reader.getSize() > 0 && reader.exists())
{
rawMetadata = extractRaw(nodeRef, reader, getLimits(reader.getMimetype()));
rawMetadata = extractRaw(nodeRef, reader, getLimits(reader.getMimetype()), overwritePolicy);
}
else
{
@@ -2002,7 +2012,7 @@ abstract public class AbstractMappingMetadataExtracter implements MetadataExtrac
}
/**
* Exception wrapper to handle exceeded limits imposed by {@link MetadataExtracterLimits} {@link AbstractMappingMetadataExtracter#extractRaw(NodeRef, ContentReader, MetadataExtracterLimits)}
* Exception wrapper to handle exceeded limits imposed by {@link MetadataExtracterLimits} {@link AbstractMappingMetadataExtracter#extractRaw(NodeRef, ContentReader, MetadataExtracterLimits,OverwritePolicy)}
*/
private class LimitExceededException extends Exception
{
@@ -2032,7 +2042,7 @@ abstract public class AbstractMappingMetadataExtracter implements MetadataExtrac
* All exception conditions can be handled.
*/
private Map<String, Serializable> extractRaw(NodeRef nodeRef,
ContentReader reader, MetadataExtracterLimits limits) throws Throwable
ContentReader reader, MetadataExtracterLimits limits, OverwritePolicy overwritePolicy) throws Throwable
{
if (reader.getSize() > limits.getMaxDocumentSizeMB() * MEGABYTE_SIZE)
{
@@ -2059,6 +2069,12 @@ abstract public class AbstractMappingMetadataExtracter implements MetadataExtrac
}
}
return extractRawInThread(nodeRef, reader, limits, overwritePolicy);
}
protected Map<String, Serializable> extractRawInThread(NodeRef nodeRef, ContentReader reader, MetadataExtracterLimits limits, OverwritePolicy policy)
throws Throwable
{
return extractRawInThread(nodeRef, reader, limits);
}

View File

@@ -93,6 +93,9 @@ public class AsynchronousExtractor extends AbstractMappingMetadataExtracter
private static final String METADATA = "metadata";
private static final Map<String, Serializable> EMPTY_METADATA = Collections.emptyMap();
private static final OverwritePolicy DEFAULT_OVERWRITE_POLICY = OverwritePolicy.PRAGMATIC;
private OverwritePolicy extractOverwritePolicy = DEFAULT_OVERWRITE_POLICY;
private final ObjectMapper jsonObjectMapper = new ObjectMapper();
private NodeService nodeService;
@@ -260,9 +263,9 @@ public class AsynchronousExtractor extends AbstractMappingMetadataExtracter
}
@Override
protected Map<String, Serializable> extractRawInThread(NodeRef nodeRef, ContentReader reader, MetadataExtracterLimits limits)
throws Throwable
protected Map<String, Serializable> extractRawInThread(NodeRef nodeRef, ContentReader reader, MetadataExtracterLimits limits, OverwritePolicy overwritePolicy) throws Throwable
{
this.extractOverwritePolicy = overwritePolicy != null ? overwritePolicy : DEFAULT_OVERWRITE_POLICY;
Map<String, String> options = getExtractOptions(nodeRef, reader, limits);
transformInBackground(nodeRef, reader, MIMETYPE_METADATA_EXTRACT, EXTRACT, options);
return EMPTY_METADATA;
@@ -461,7 +464,7 @@ public class AsynchronousExtractor extends AbstractMappingMetadataExtracter
}
// Remove well know entries from the map that drive how the real metadata is applied.
OverwritePolicy overwritePolicy = removeOverwritePolicy(metadata, "sys:overwritePolicy", OverwritePolicy.PRAGMATIC);
OverwritePolicy overwritePolicy = removeOverwritePolicy(metadata, "sys:overwritePolicy", extractOverwritePolicy);
Boolean enableStringTagging = removeBoolean(metadata, "sys:enableStringTagging", false);
Boolean carryAspectProperties = removeBoolean(metadata, "sys:carryAspectProperties", true);
List<String> stringTaggingSeparators = removeTaggingSeparators(metadata, "sys:stringTaggingSeparators",

View File

@@ -404,6 +404,24 @@ public interface MetadataExtracter extends ContentWorker
return extract(reader, destination);
}
/**
* Identical to {@link #extract(ContentReader, OverwritePolicy ,Map)} but with the addition of the {@code NodeRef} being acted on. By default, the method without the {@code NodeRef} is called.
*
* @param nodeRef
* the node being acted on.
* @param reader
* the source of the content
* @param destination
* the map of properties to populate (essentially a return value)
* @return Returns a map of all properties on the destination map that were added or modified. If the return map is empty, then no properties were modified.
* @throws ContentIOException
* if a detectable error occurs
*/
default Map<QName, Serializable> extract(NodeRef nodeRef, ContentReader reader, OverwritePolicy overwritePolicy, Map<QName, Serializable> destination)
{
return extract(reader, overwritePolicy, destination);
}
/**
* Identical to {@link #extract(ContentReader, OverwritePolicy, Map, Map)} but with the addition of the {@code NodeRef} being acted on. By default, the method without the {@code NodeRef} is called.
*

View File

@@ -2066,7 +2066,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
Node node = getNodeNotNull(nodeId, false);
// Handle sys:referenceable
ReferenceablePropertiesEntity.addReferenceableProperties(node, props);
ReferenceablePropertiesEntity.addReferenceableProperties(node.getId(), node.getNodeRef(), props);
// Handle sys:localized
LocalizedPropertiesEntity.addLocalizedProperties(localeDAO, node, props);
// Handle cm:auditable

View File

@@ -86,10 +86,8 @@ public class ReferenceablePropertiesEntity
/**
* Adds all {@link ContentModel#ASPECT_REFERENCEABLE referencable} properties.
*/
public static void addReferenceableProperties(Node node, Map<QName, Serializable> properties)
public static void addReferenceableProperties(Long nodeId, NodeRef nodeRef, Map<QName, Serializable> properties)
{
Long nodeId = node.getId();
NodeRef nodeRef = node.getNodeRef();
properties.put(ContentModel.PROP_STORE_PROTOCOL, nodeRef.getStoreRef().getProtocol());
properties.put(ContentModel.PROP_STORE_IDENTIFIER, nodeRef.getStoreRef().getIdentifier());
properties.put(ContentModel.PROP_NODE_UUID, nodeRef.getId());

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2023 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
@@ -97,19 +97,26 @@ public abstract class EventConsolidator<REF extends EntityRef, RES extends Resou
* @return the {@link RepoEvent} instance
*/
public RepoEvent<DataAttributes<RES>> getRepoEvent(EventInfo eventInfo)
{
final RepoEvent.Builder<DataAttributes<RES>> builder = RepoEvent.builder();
configureRepoEventBuilder(builder, eventInfo);
return builder.build();
}
protected void configureRepoEventBuilder(RepoEvent.Builder<DataAttributes<RES>> builder, EventInfo eventInfo)
{
EventType eventType = getDerivedEvent();
DataAttributes<RES> eventData = buildEventData(eventInfo, resource, eventType);
return RepoEvent.<DataAttributes<RES>> builder()
.setId(eventInfo.getId())
builder.setId(eventInfo.getId())
.setSource(eventInfo.getSource())
.setTime(eventInfo.getTimestamp())
.setType(eventType.getType())
.setData(eventData)
.setDataschema(EventJSONSchema.getSchemaV1(eventType))
.build();
.setDataschema(EventJSONSchema.getSchemaV1(eventType));
}
/**

View File

@@ -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";

View File

@@ -4,21 +4,21 @@
* %%
* Copyright (C) 2005 - 2016 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
* 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 <http://www.gnu.org/licenses/>.
* #L%
@@ -34,7 +34,7 @@ import org.alfresco.service.cmr.action.ActionService;
/**
* Scripted Action service for describing and executing actions against Nodes.
*
*
* @author davidc
*/
public final class Actions extends BaseScopableProcessorExtension
@@ -44,7 +44,7 @@ public final class Actions extends BaseScopableProcessorExtension
/**
* Set the service registry
*
*
* @param serviceRegistry
* the service registry
*/
@@ -55,7 +55,7 @@ public final class Actions extends BaseScopableProcessorExtension
/**
* Gets the list of registered action names
*
*
* @return the registered action names
*/
public String[] getRegistered()
@@ -73,7 +73,7 @@ public final class Actions extends BaseScopableProcessorExtension
/**
* Create an Action
*
*
* @param actionName
* the action name
* @return the action

View File

@@ -0,0 +1,171 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2020 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 <http://www.gnu.org/licenses/>.
* #L%
*/
/*
* Copyright (C) 2005 - 2025 Alfresco Software Limited
*
* This file is part of Alfresco
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.jscript;
import org.apache.commons.lang3.Strings;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.action.evaluator.CompareContentConditionEvaluator;
import org.alfresco.repo.forms.FormData;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ActionCondition;
import org.alfresco.service.cmr.action.ActionDefinition;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.NodeRef;
/**
* JavaScript wrapper for the "extract-metadata" action.
* <p>
* This class provides a scriptable interface to trigger metadata extraction actions within the Alfresco repository.</br>
* It is similar to {@link Actions} class but is dedicated to metadata extraction functionality.
*
* </br>
*
* @author Sayan Bhattacharya
*/
public final class MetaDataExtractAction extends BaseScopableProcessorExtension
{
private static final Log LOG = LogFactory.getLog(MetaDataExtractAction.class);
private final static String ACTION_NAME = "extract-metadata";
private ContentService contentService;
private ServiceRegistry services;
/**
* Set the service registry
*
* @param serviceRegistry
* the service registry
*/
public void setServiceRegistry(ServiceRegistry serviceRegistry)
{
this.services = serviceRegistry;
}
public void setContentService(ContentService contentService)
{
this.contentService = contentService;
}
/**
* Create a new metadata extraction action instance
*
* @param setActionContext
* if true, sets the action context to "scriptaction".
* @return the newly created action
*/
public ScriptAction create(boolean isContentChanged)
{
ScriptAction scriptAction = null;
ActionService actionService = services.getActionService();
ActionDefinition actionDef = actionService.getActionDefinition(ACTION_NAME);
if (actionDef != null)
{
Action action = actionService.createAction(ACTION_NAME);
ActionCondition actionCondition = actionService.createActionCondition(CompareContentConditionEvaluator.NAME);
actionCondition.setParameterValue(CompareContentConditionEvaluator.PARAM_IS_CONTENT_CHANGED, isContentChanged);
action.addActionCondition(actionCondition);
scriptAction = new ScriptAction(this.services, action, actionDef);
scriptAction.setScope(getScope());
}
return scriptAction;
}
/**
* Check if the content has been updated in the form data compared to the existing content of the node.
*
* @param itemId
* @param formData
* @return true if content has changed, false otherwise
*/
public boolean isContentChanged(String itemId, FormData formData)
{
try
{
NodeRef nodeRef = NodeRef.isNodeRef(itemId) ? new NodeRef(itemId) : parseNodeRef(itemId);
if (nodeRef == null)
{
return false;
}
ContentReader reader = contentService.getReader(nodeRef, ContentModel.PROP_CONTENT);
String contentString = reader.getContentString();
FormData.FieldData fieldData = formData.getFieldData("prop_cm_content");
if (fieldData == null || fieldData.getValue() == null)
{
return false;
}
String propCmContent = String.valueOf(fieldData.getValue());
return !Strings.CS.equals(contentString, propCmContent);
}
catch (Exception e)
{
if (LOG.isDebugEnabled())
{
LOG.debug("Unable to determine if content has changed for node: " + itemId, e);
}
return false;
}
}
private NodeRef parseNodeRef(String itemId)
{
String[] parts = itemId.split("/");
return (parts.length == 3) ? new NodeRef(parts[0], parts[1], parts[2]) : null;
}
}

View File

@@ -28,8 +28,10 @@ package org.alfresco.repo.node.getchildren;
import java.util.List;
import java.util.Set;
import org.alfresco.repo.domain.node.NodeEntity;
import org.alfresco.repo.domain.node.AuditablePropertiesEntity;
import org.alfresco.repo.domain.node.NodePropertyEntity;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.StoreRef;
/**
* Filterable/Sortable Node Entity
@@ -42,12 +44,17 @@ import org.alfresco.repo.domain.node.NodePropertyEntity;
public class FilterSortNodeEntity
{
private Long id; // node id
private String nodeUuid;
private Long typeQNameId;
private NodeEntity node;
private AuditablePropertiesEntity auditablePropertiesEntity;
private NodePropertyEntity prop1;
private NodePropertyEntity prop2;
private NodePropertyEntity prop3;
private String storeProtocol;
private String storeIdentifier;
// Supplemental query-related parameters
private Long parentNodeId;
private Long prop1qnameId;
@@ -80,6 +87,26 @@ public class FilterSortNodeEntity
this.id = id;
}
public String getNodeUuid()
{
return nodeUuid;
}
public void setNodeUuid(String nodeUuid)
{
this.nodeUuid = nodeUuid;
}
public Long getTypeQNameId()
{
return typeQNameId;
}
public void setTypeQNameId(Long typeQNameId)
{
this.typeQNameId = typeQNameId;
}
public String getPattern()
{
return pattern;
@@ -136,6 +163,16 @@ public class FilterSortNodeEntity
this.namePropertyQNameId = namePropertyQNameId;
}
public AuditablePropertiesEntity getAuditablePropertiesEntity()
{
return auditablePropertiesEntity;
}
public void setAuditablePropertiesEntity(AuditablePropertiesEntity auditablePropertiesEntity)
{
this.auditablePropertiesEntity = auditablePropertiesEntity;
}
public NodePropertyEntity getProp1()
{
return prop1;
@@ -166,14 +203,24 @@ public class FilterSortNodeEntity
this.prop3 = prop3;
}
public NodeEntity getNode()
public String getStoreProtocol()
{
return node;
return storeProtocol;
}
public void setNode(NodeEntity childNode)
public void setStoreProtocol(String storeProtocol)
{
this.node = childNode;
this.storeProtocol = storeProtocol;
}
public String getStoreIdentifier()
{
return storeIdentifier;
}
public void setStoreIdentifier(String storeIdentifier)
{
this.storeIdentifier = storeIdentifier;
}
// Supplemental query-related parameters
@@ -257,4 +304,9 @@ public class FilterSortNodeEntity
{
this.isPrimary = isPrimary;
}
public NodeRef createNodeRef()
{
return new NodeRef(new StoreRef(storeProtocol, storeIdentifier), nodeUuid);
}
}

View File

@@ -48,7 +48,6 @@ import org.alfresco.query.CannedQueryParameters;
import org.alfresco.query.CannedQuerySortDetails;
import org.alfresco.query.CannedQuerySortDetails.SortOrder;
import org.alfresco.repo.domain.node.AuditablePropertiesEntity;
import org.alfresco.repo.domain.node.Node;
import org.alfresco.repo.domain.node.NodeDAO;
import org.alfresco.repo.domain.node.NodeEntity;
import org.alfresco.repo.domain.node.NodePropertyEntity;
@@ -775,7 +774,8 @@ public class GetChildrenCannedQuery extends AbstractCannedQueryPermissions<NodeR
if (results.size() >= BATCH_SIZE)
{
// batch
preloadFilterSort();
preloadNodes();
filterSort();
}
results.add(result);
@@ -788,24 +788,27 @@ public class GetChildrenCannedQuery extends AbstractCannedQueryPermissions<NodeR
if (results.size() >= 0)
{
// finish batch
preloadFilterSort();
preloadNodes();
filterSort();
}
}
private void preloadFilterSort()
private void preloadNodes()
{
List<NodeRef> nodeRefs = new ArrayList<>(results.size());
for (FilterSortNodeEntity result : results)
{
nodeRefs.add(result.getNode().getNodeRef());
nodeRefs.add(result.createNodeRef());
}
preload(nodeRefs);
}
private void filterSort()
{
for (FilterSortNodeEntity result : results)
{
Node node = result.getNode();
NodeRef nodeRef = node.getNodeRef();
NodeRef nodeRef = result.createNodeRef();
Map<NodePropertyKey, NodePropertyValue> propertyValues = new HashMap<NodePropertyKey, NodePropertyValue>(3);
@@ -830,7 +833,7 @@ public class GetChildrenCannedQuery extends AbstractCannedQueryPermissions<NodeR
Map<QName, Serializable> propVals = nodePropertyHelper.convertToPublicProperties(propertyValues);
// Add referenceable / spoofed properties (including spoofed name if null)
ReferenceablePropertiesEntity.addReferenceableProperties(node, propVals);
ReferenceablePropertiesEntity.addReferenceableProperties(result.getId(), nodeRef, propVals);
// special cases
@@ -852,7 +855,7 @@ public class GetChildrenCannedQuery extends AbstractCannedQueryPermissions<NodeR
}
// Auditable props (eg. cm:creator, cm:created, cm:modifier, cm:modified, ...)
AuditablePropertiesEntity auditableProps = node.getAuditableProperties();
AuditablePropertiesEntity auditableProps = result.getAuditablePropertiesEntity();
if (auditableProps != null)
{
for (Map.Entry<QName, Serializable> entry : auditableProps.getAuditableProperties().entrySet())
@@ -862,7 +865,7 @@ public class GetChildrenCannedQuery extends AbstractCannedQueryPermissions<NodeR
}
// Node type
Long nodeTypeQNameId = node.getTypeQNameId();
Long nodeTypeQNameId = result.getTypeQNameId();
if (nodeTypeQNameId != null)
{
Pair<Long, QName> pair = qnameDAO.getQName(nodeTypeQNameId);

View File

@@ -237,6 +237,12 @@
<bean id="no-condition" class="org.alfresco.repo.action.evaluator.NoConditionEvaluator" parent="action-condition-evaluator">
</bean>
<bean id="compare-content" class="org.alfresco.repo.action.evaluator.CompareContentConditionEvaluator" parent="action-condition-evaluator">
<property name="publicCondition">
<value>false</value>
</property>
</bean>
<bean id="compare-property-value" class="org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator" parent="action-condition-evaluator">
<property name="nodeService">
<ref bean="nodeService" />

View File

@@ -37,8 +37,9 @@
<property name="properties" ref="global-properties" />
</bean>
</property>
<property name="auditRecordReporter" ref="auditRecordReporter"/>
</bean>
<!-- User Audit Filter -->
<bean id="userAuditFilter" class="org.alfresco.repo.audit.UserAuditFilter">
@@ -108,5 +109,8 @@
<!-- Reference in the audit registry managed bean -->
<alias name="Audit" alias="auditModel.modelRegistry"/>
</beans>
<!-- Audit Record Reported -->
<bean id="auditRecordReporter" class="org.alfresco.repo.audit.AuditRecordReporterImpl"/>
</beans>

View File

@@ -133,7 +133,15 @@
<resultMap id="result_FilterSortNode" type="FilterSortNode">
<id property="id" column="id" jdbcType="BIGINT" javaType="java.lang.Long"/>
<result property="nodeUuid" column="uuid" jdbcType="VARCHAR" javaType="java.lang.String"/>
<result property="typeQNameId" column="type_qname_id" jdbcType="BIGINT" javaType="java.lang.Long"/>
<result property="auditablePropertiesEntity.auditCreator" column="audit_creator" jdbcType="VARCHAR" javaType="java.lang.String"/>
<result property="auditablePropertiesEntity.auditCreated" column="audit_created" jdbcType="VARCHAR" javaType="java.lang.String"/>
<result property="auditablePropertiesEntity.auditModifier" column="audit_modifier" jdbcType="VARCHAR" javaType="java.lang.String"/>
<result property="auditablePropertiesEntity.auditModified" column="audit_modified" jdbcType="VARCHAR" javaType="java.lang.String"/>
<result property="auditablePropertiesEntity.auditAccessed" column="audit_accessed" jdbcType="VARCHAR" javaType="java.lang.String"/>
<result property="prop1.nodeId" column="prop1_node_id" jdbcType="BIGINT" javaType="java.lang.Long"/>
<result property="prop1.key.qnameId" column="prop1_qname_id" jdbcType="BIGINT" javaType="java.lang.Long"/>
<result property="prop1.key.localeId" column="prop1_locale_id" jdbcType="BIGINT" javaType="java.lang.Long"/>
@@ -169,9 +177,9 @@
<result property="prop3.value.floatValue" column="prop3_float_value" jdbcType="FLOAT" javaType="java.lang.Float"/>
<result property="prop3.value.doubleValue" column="prop3_double_value" jdbcType="FLOAT" javaType="java.lang.Double"/>
<result property="prop3.value.stringValue" column="prop3_string_value" jdbcType="VARCHAR" javaType="java.lang.String"/>
<association property="node" resultMap="alfresco.node.result_Node"/>
<result property="storeProtocol" column="protocol" jdbcType="VARCHAR" javaType="java.lang.String"/>
<result property="storeIdentifier" column="identifier" jdbcType="VARCHAR" javaType="java.lang.String"/>
</resultMap>
<resultMap id="result_ArchivedNodes" type="ArchivedNodes">
@@ -972,8 +980,8 @@
</select>
<!-- GetChildren - with explicit prop filtering and/or sorting -->
<select id="select_GetChildrenCannedQueryWithProps" parameterType="FilterSortNode" resultMap="result_FilterSortNode">
select
<select id="select_GetChildrenCannedQueryWithProps" parameterType="FilterSortNode" resultMap="result_FilterSortNode" flushCache="true">
select distinct
childNode.id as id,
childNode.version as version,
childStore.id as store_id,
@@ -989,7 +997,7 @@
childNode.audit_created as audit_created,
childNode.audit_modifier as audit_modifier,
childNode.audit_modified as audit_modified,
childNode.audit_accessed as audit_accessed
childNode.audit_accessed as audit_accessed
<if test="prop1qnameId != null">
, prop1.node_id as prop1_node_id,
prop1.qname_id as prop1_qname_id,
@@ -1067,9 +1075,6 @@
#{item}
</foreach>
</if>
<if test="prop1qnameId == null and auditableProps == false">
<include refid="alfresco.node.select_ChildAssoc_OrderBy"/>
</if>
</select>
<!-- GetChildren - with no explicit sorting (or prop filtering) - note: still filtered by child type (and optionally primary or secondary) -->

View File

@@ -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

View File

@@ -104,6 +104,17 @@
</property>
</bean>
<bean id="metadataExtractServiceScript" parent="baseJavaScriptExtension"
class="org.alfresco.repo.jscript.MetaDataExtractAction">
<property name="extensionName">
<value>metadataExtractAction</value>
</property>
<property name="contentService" ref="ContentService" />
<property name="serviceRegistry">
<ref bean="ServiceRegistry"/>
</property>
</bean>
<bean id="imapScript" parent="baseJavaScriptExtension" class="org.alfresco.repo.jscript.Imap">
<property name="extensionName">
<value>imap</value>

View File

@@ -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,

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2017 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
@@ -66,6 +66,7 @@ import org.alfresco.util.testing.category.NonBuildTests;
org.alfresco.repo.importer.FileImporterTest.class,
org.alfresco.repo.importer.ImporterComponentTest.class,
org.alfresco.repo.jscript.PeopleTest.class,
org.alfresco.repo.jscript.MetaDataExtractActionTest.class,
org.alfresco.repo.jscript.RhinoScriptTest.class,
// needs a clean DB to run

View File

@@ -28,6 +28,7 @@ package org.alfresco.repo.action.executer;
import static org.awaitility.Awaitility.await;
import java.io.Serializable;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@@ -46,6 +47,8 @@ import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.content.metadata.AbstractMappingMetadataExtracter;
import org.alfresco.repo.content.metadata.MetadataExtracterRegistry;
import org.alfresco.repo.content.transform.AbstractContentTransformerTest;
import org.alfresco.repo.jscript.MetaDataExtractAction;
import org.alfresco.repo.jscript.ScriptAction;
import org.alfresco.repo.security.authentication.AuthenticationComponent;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.service.cmr.repository.ContentReader;
@@ -74,6 +77,10 @@ public class ContentMetadataExtracterTest extends BaseSpringTest
protected static final String QUICK_DESCRIPTION = "Pangram, fox, dog, Gym class featuring a brown fox and lazy dog";
protected static final String QUICK_CREATOR = "Nevin Nollop";
protected static final String QUICK_UPDATED_TITLE = "The hot dog is eaten by the city fox";
protected static final String QUICK_UPDATED_DESCRIPTION = "Pangram, fox, dog, Gym class featuring only brown fox";
protected static final String QUICK_UPDATED_CREATOR = "Friday";
private NodeService nodeService;
private ContentService contentService;
private MetadataExtracterRegistry registry;
@@ -84,6 +91,8 @@ public class ContentMetadataExtracterTest extends BaseSpringTest
private ContentMetadataExtracter executer;
private MetaDataExtractAction extractAction;
private final static String ID = GUID.generate();
@Before
@@ -116,6 +125,9 @@ public class ContentMetadataExtracterTest extends BaseSpringTest
// Get the executer instance
this.executer = (ContentMetadataExtracter) this.applicationContext.getBean("extract-metadata");
// get the js script action
this.extractAction = (MetaDataExtractAction) this.applicationContext.getBean("metadataExtractServiceScript");
}
/**
@@ -351,4 +363,45 @@ public class ContentMetadataExtracterTest extends BaseSpringTest
}
});
}
@Test
public void testUsingScriptAction_WhenContentChanged() throws Exception
{
// update the content
ContentWriter cw = this.contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true);
cw.setMimetype(MimetypeMap.MIMETYPE_PDF);
cw.putContent(AbstractContentTransformerTest.loadNamedQuickTestFile("quickupdated.pdf"));
// Make the nodeRef visible to other transactions as it will need to be in async requests
TestTransaction.flagForCommit();
TestTransaction.end();
// Execute the action
transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>() {
public Void execute() throws Throwable
{
ScriptAction action = extractAction.create(true);
action.execute(nodeRef, false, false);
return null;
}
});
// Need to wait for the async extract
await().pollInSameThread()
.atMost(Duration.ofSeconds(100))
.until(() -> nodeService.getProperty(nodeRef, ContentModel.PROP_DESCRIPTION), Objects::nonNull);
// Check that the properties have been preserved, but that description has been set
transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>() {
public Void execute() throws Throwable
{
assertEquals(QUICK_UPDATED_TITLE, nodeService.getProperty(nodeRef, ContentModel.PROP_TITLE));
assertEquals(QUICK_UPDATED_CREATOR, nodeService.getProperty(nodeRef, ContentModel.PROP_AUTHOR));
assertEquals(QUICK_UPDATED_DESCRIPTION, nodeService.getProperty(nodeRef, ContentModel.PROP_DESCRIPTION));
return null;
}
});
}
}

View File

@@ -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");

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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<String, Serializable>();
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<String, ?>) 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<String, Object>) 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<String, Serializable>();
var expectedValue = new HashMap<String, Serializable>();
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<String, ?>) auditData.get("CREATED");
assertNotNull(created);
assertEquals(1, created.size());
var result = (HashMap<String, Object>) created.get("result");
assertNotNull(result);
assertEquals(1, result.size());
var resultValue = (HashMap<String, Object>) 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"));
}
}

View File

@@ -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));

View File

@@ -0,0 +1,128 @@
/*
* #%L
* Alfresco Repository
* %%
* 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
* 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 <http://www.gnu.org/licenses/>.
* #L%
*/
/*
* Copyright (C) 2005 Jesper Steen Møller
*
* This file is part of Alfresco
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.jscript;
import static org.junit.Assert.*;
import org.junit.Test;
import org.mockito.Mockito;
import org.alfresco.repo.forms.FormData;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ActionCondition;
import org.alfresco.service.cmr.action.ActionDefinition;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentService;
public class MetaDataExtractActionTest
{
@Test
public void testIsContentChangedReturnsTrue()
{
MetaDataExtractAction action = new MetaDataExtractAction();
ContentService contentService = Mockito.mock(ContentService.class);
ContentReader reader = Mockito.mock(ContentReader.class);
FormData formData = Mockito.mock(FormData.class);
FormData.FieldData fieldData = Mockito.mock(FormData.FieldData.class);
String nodeRefStr = "workspace://SpacesStore/abc/def";
Mockito.when(contentService.getReader(Mockito.any(), Mockito.any())).thenReturn(reader);
Mockito.when(reader.getContentString()).thenReturn("oldContent");
Mockito.when(formData.getFieldData("prop_cm_content")).thenReturn(fieldData);
Mockito.when(fieldData.getValue()).thenReturn("newContent");
action.setContentService(contentService);
boolean result = action.isContentChanged(nodeRefStr, formData);
assertTrue(result);
}
@Test
public void testIsContentChangedReturnsFalse()
{
MetaDataExtractAction action = new MetaDataExtractAction();
ContentService contentService = Mockito.mock(ContentService.class);
ContentReader reader = Mockito.mock(ContentReader.class);
FormData formData = Mockito.mock(FormData.class);
FormData.FieldData fieldData = Mockito.mock(FormData.FieldData.class);
String nodeRefStr = "workspace://SpacesStore/abc/def";
Mockito.when(contentService.getReader(Mockito.any(), Mockito.any())).thenReturn(reader);
Mockito.when(reader.getContentString()).thenReturn("sameContent");
Mockito.when(formData.getFieldData("prop_cm_content")).thenReturn(fieldData);
Mockito.when(fieldData.getValue()).thenReturn("sameContent");
action.setContentService(contentService);
boolean result = action.isContentChanged(nodeRefStr, formData);
assertFalse(result);
}
@Test
public void testCreateWhenContentChangedReturnsScriptAction()
{
MetaDataExtractAction action = new MetaDataExtractAction();
ServiceRegistry serviceRegistry = Mockito.mock(ServiceRegistry.class);
ActionService actionService = Mockito.mock(ActionService.class);
ActionDefinition actionDefinition = Mockito.mock(ActionDefinition.class);
Action alfrescoAction = Mockito.mock(Action.class);
ActionCondition actionCondition = Mockito.mock(ActionCondition.class);
Mockito.when(serviceRegistry.getActionService()).thenReturn(actionService);
Mockito.when(actionService.getActionDefinition(Mockito.anyString())).thenReturn(actionDefinition);
Mockito.when(actionService.createAction(Mockito.anyString())).thenReturn(alfrescoAction);
Mockito.when(actionService.createActionCondition(Mockito.anyString())).thenReturn(actionCondition);
action.setServiceRegistry(serviceRegistry);
ScriptAction result = action.create(true);
assertNotNull("ScriptAction should not be null when content has changed", result);
}
}

Binary file not shown.