Compare commits

...

92 Commits

Author SHA1 Message Date
alfresco-build
d933cedbd7 [maven-release-plugin][skip ci] prepare release 23.7.0.7 2025-11-05 22:37:03 +00:00
Tiago Salvado
5e54f94c6a [MNT-25393] Obtain period properties dynamically when creating a retention step (#3625) (#3639) 2025-11-05 21:14:29 +00:00
alfresco-build
8484c3e022 [maven-release-plugin][skip ci] prepare for next development iteration 2025-11-05 10:17:16 +00:00
alfresco-build
eca25addff [maven-release-plugin][skip ci] prepare release 23.7.0.6 2025-11-05 10:17:14 +00:00
Gerard Olenski
0ea417ed37 ACS-10528 Bump jakarta mail to 2.0.2 (#3635) (#3637) 2025-11-05 09:57:43 +01:00
alfresco-build
fb811cdb2d [maven-release-plugin][skip ci] prepare for next development iteration 2025-11-04 12:04:33 +00:00
alfresco-build
9c0ac89015 [maven-release-plugin][skip ci] prepare release 23.7.0.5 2025-11-04 12:04:31 +00:00
Damian Ujma
1028b5c44b ACS-10456 Backport camel and netty version bump to 23.N (#3634) 2025-11-04 11:42:53 +01:00
alfresco-build
39d209a6a1 [maven-release-plugin][skip ci] prepare for next development iteration 2025-10-31 11:42:38 +00:00
alfresco-build
d9adbd60e1 [maven-release-plugin][skip ci] prepare release 23.7.0.4 2025-10-31 11:42:36 +00:00
Somnath-Deshmukh
cb07b26653 MNT-25422 Backporting to 23.N (#3628)
Bulleted list, numbered list, and underline are not working properly when adding comment in the file. Backporting to 23.N
2025-10-31 15:50:32 +05:30
SanjoyHyland2025
327129f021 Merge pull request #3618 from Alfresco/fix/MNT-25186_23.N
MNT-25186: fixed the tags and category filters for elasticsearch
2025-10-24 16:46:06 +05:30
alfresco-build
afbaf11ff9 [maven-release-plugin][skip ci] prepare for next development iteration 2025-10-22 09:56:07 +00:00
alfresco-build
e779057f23 [maven-release-plugin][skip ci] prepare release 23.7.0.3 2025-10-22 09:56:05 +00:00
Debjit Chattopadhyay
49ee9f8f29 Revert "MNT-24776 adding if-else conditionals to avoid null values"
Revert "MNT-24776 adding if-else conditionals to avoid null values"
2025-10-22 14:33:52 +05:30
Debjit Chattopadhyay
213886cedd Revert "MNT-24776 adding if-else conditionals to avoid null values"
This reverts commit 353d50a35c.
2025-10-22 13:13:42 +05:30
Sanjoy Das
1be9a43bcd MNT-25186: fixed the tags and category filters for elasticsearch 2025-10-22 12:17:37 +05:30
alfresco-build
eceb7c0eb3 [maven-release-plugin][skip ci] prepare for next development iteration 2025-10-21 11:39:39 +00:00
alfresco-build
8cd46d4585 [maven-release-plugin][skip ci] prepare release 23.7.0.2 2025-10-21 11:39:37 +00:00
Debjit Chattopadhyay
11d71e1941 (MNT-24776) Category Picker Error when a User does not have Read Permissions to a Category
(MNT-24776) Category Picker Error when a User does not have Read Permissions to a Category
2025-10-21 15:30:44 +05:30
Debjit Chattopadhyay
353d50a35c MNT-24776 adding if-else conditionals to avoid null values
(cherry picked from commit be02be5a8b)
2025-10-21 12:12:06 +05:30
alfresco-build
2daafc711d [maven-release-plugin][skip ci] prepare for next development iteration 2025-10-19 04:45:23 +00:00
alfresco-build
dfa94fbe21 [maven-release-plugin][skip ci] prepare release 23.7.0.1 2025-10-19 04:45:22 +00:00
Somnath-Deshmukh
4a93aec66b MNT-25359 Validating and Sanitizing the comment before posting (#3615)
MNT-25359 Validating and Sanitizing the comment before posting to prevent any XSS attack
2025-10-17 21:00:11 +05:30
Jakub Kochman
3f0bbc9844 Updating release/23.N branch to 23.7.0 after 23.6.0 ACS release [skip ci] 2025-10-10 11:19:17 +02:00
alfresco-build
cb9ad42101 [maven-release-plugin][skip ci] prepare for next development iteration 2025-10-09 16:41:21 +00:00
alfresco-build
ca385b3bbc [maven-release-plugin][skip ci] prepare release 23.6.0.33 2025-10-09 16:41:20 +00:00
Kacper Magdziarz
19c1582f1e [ACS-10423] Bump IE/SS to 2.0.17 (#3612) 2025-10-09 17:38:42 +02:00
alfresco-build
06a918b082 [maven-release-plugin][skip ci] prepare for next development iteration 2025-10-09 13:44:28 +00:00
alfresco-build
f543de9959 [maven-release-plugin][skip ci] prepare release 23.6.0.32 2025-10-09 13:44:26 +00:00
Debjit Chattopadhyay
8124279e6a Merge pull request #3610 from Alfresco/fix/revert_MNT_24776
Revert "MNT-24776 adding if-else conditionals to avoid null values"
2025-10-09 18:27:59 +05:30
Debjit Chattopadhyay
4281fd5b2d Revert "MNT-24776 adding if-else conditionals to avoid null values"
This reverts commit 393b064918.
2025-10-09 18:14:50 +05:30
alfresco-build
d10d88306b [maven-release-plugin][skip ci] prepare for next development iteration 2025-10-09 10:12:06 +00:00
alfresco-build
1d7a37cd8c [maven-release-plugin][skip ci] prepare release 23.6.0.31 2025-10-09 10:12:04 +00:00
Debjit Chattopadhyay
4bcb795452 Merge pull request #3609 from Alfresco/fix/MNT-24776_backport_to_23.N
MNT-24776 backport to release/23.N
2025-10-09 14:34:25 +05:30
Debjit Chattopadhyay
393b064918 MNT-24776 adding if-else conditionals to avoid null values
(cherry picked from commit be02be5a8b)
2025-10-09 13:29:30 +05:30
alfresco-build
f741f2ca45 [maven-release-plugin][skip ci] prepare for next development iteration 2025-10-08 15:59:31 +00:00
alfresco-build
ef676f11e4 [maven-release-plugin][skip ci] prepare release 23.6.0.30 2025-10-08 15:59:29 +00:00
cezary-witkowski
478c81fee3 [ACS-10454] Bump AOS to fix "Edit in Microsoft Office" error (#3606) 2025-10-08 16:57:01 +02:00
alfresco-build
cf9cc8042d [maven-release-plugin][skip ci] prepare for next development iteration 2025-10-06 13:25:18 +00:00
alfresco-build
8d790ed1cb [maven-release-plugin][skip ci] prepare release 23.6.0.29 2025-10-06 13:25:16 +00:00
cezary-witkowski
87c7bd2877 [ACS-10454] Bump AOS to fix "Edit in Microsoft Office" error (#3602) 2025-10-06 14:16:10 +02:00
alfresco-build
9125f889b0 [maven-release-plugin][skip ci] prepare for next development iteration 2025-10-03 11:40:58 +00:00
alfresco-build
2fb74d2691 [maven-release-plugin][skip ci] prepare release 23.6.0.28 2025-10-03 11:40:56 +00:00
Gerard Olenski
d671162dae ACS-10427 Bump ATS 4.2.2 (#3601) 2025-10-03 12:54:03 +02:00
alfresco-build
bfaa629da7 [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-26 17:19:48 +00:00
alfresco-build
719d73a558 [maven-release-plugin][skip ci] prepare release 23.6.0.27 2025-09-26 17:19:46 +00:00
Piotr Żurek
a2aa867f3f ACS-9665 Fix formatting 2025-09-26 18:34:04 +02:00
Piotr Żurek
8d745c536a Cherry pick ACS-9665 add event generation extensions (#3593) 280a873cb6 Piotr Żurek <Piotr.Zurek@hyland.com> 26 Sep 2025 at 10:25 2025-09-26 18:25:10 +02:00
alfresco-build
b0f4c21ae3 [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-26 12:31:24 +00:00
alfresco-build
72494e34fa [maven-release-plugin][skip ci] prepare release 23.6.0.26 2025-09-26 12:31:22 +00:00
Tiago Salvado
792b7024ea [ACS-9940] Bump spring security version to 6.4.11 (#3592) (#3594) 2025-09-26 12:44:25 +01:00
alfresco-build
40a1371f0d [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-25 20:52:06 +00:00
alfresco-build
c22c47e63f [maven-release-plugin][skip ci] prepare release 23.6.0.25 2025-09-25 20:52:03 +00:00
Tiago Salvado
232299d42d [ACS-10155] Bump spring version to 6.2.11 (#3589) (#3591) 2025-09-25 21:04:30 +01:00
alfresco-build
aca7969849 [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-25 10:22:26 +00:00
alfresco-build
4ab2bbd3d6 [maven-release-plugin][skip ci] prepare release 23.6.0.24 2025-09-25 10:22:24 +00:00
Debjit Chattopadhyay
f68f02372d Merge pull request #3590 from Alfresco/fix/MNT-24776_revert
Revert MNT-24776 as this fix is affecting other scenarios.
2025-09-25 15:05:51 +05:30
Debjit Chattopadhyay
9b0eedc8c1 Revert MNT-24776 as this fix is affecting other scenarios. 2025-09-25 13:19:23 +05:30
alfresco-build
f164dedcee [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-23 12:55:06 +00:00
alfresco-build
9cdaa0a265 [maven-release-plugin][skip ci] prepare release 23.6.0.23 2025-09-23 12:55:04 +00:00
cezary-witkowski
ef034e596b [ACS-10041] Repository - CPU spikes and OOM errors with SQL Server 2019 (#3588) 2025-09-23 14:10:29 +02:00
alfresco-build
1251081a69 [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-23 11:20:49 +00:00
alfresco-build
2d16eb6f42 [maven-release-plugin][skip ci] prepare release 23.6.0.22 2025-09-23 11:20:47 +00:00
Gerard Olenski
e577134875 ACS-10195 Bump Tika and ATS (#3587) 2025-09-23 12:37:56 +02:00
alfresco-build
510eadd565 [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-22 12:43:25 +00:00
alfresco-build
187646895c [maven-release-plugin][skip ci] prepare release 23.6.0.21 2025-09-22 12:43:22 +00:00
Tiago Salvado
f9515e336f [ACS-10166] Include qname and namespace in NodeIdsWhichReferenceContentUrl query (#3579) (#3582) 2025-09-22 12:57:36 +01:00
alfresco-build
828dd20576 [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-22 09:21:07 +00:00
alfresco-build
3372e20c35 [maven-release-plugin][skip ci] prepare release 23.6.0.20 2025-09-22 09:21:05 +00:00
Gerard Olenski
64b5cace27 ACS-10159 Bump ATS (#3581) 2025-09-22 10:12:37 +02:00
alfresco-build
83acf26cf4 [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-18 11:13:01 +00:00
alfresco-build
b3be0f2b7f [maven-release-plugin][skip ci] prepare release 23.6.0.19 2025-09-18 11:12:59 +00:00
Belal Ansari
7a6ebb9a05 Backporting MNT-25216 Error on fixedAclUpdaterJobDetail execution when using Oracle to ACS 23 (#3575) 2025-09-18 15:57:15 +05:30
alfresco-build
fa0f239618 [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-17 01:05:50 +00:00
alfresco-build
43799408a8 [maven-release-plugin][skip ci] prepare release 23.6.0.18 2025-09-17 01:05:48 +00:00
Tiago Salvado
e7305006f0 [ACS-9929] Bump cxf version to 4.1.2 (#3572) 2025-09-17 01:21:54 +01:00
alfresco-build
40c30411af [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-12 11:02:12 +00:00
alfresco-build
91f8b43237 [maven-release-plugin][skip ci] prepare release 23.6.0.17 2025-09-12 11:02:09 +00:00
Eva Vasques
6fccf828e1 ACS-10042 - AGS Concurrent IPR Group Creation (#3566) (#3570) 2025-09-12 11:10:43 +01:00
alfresco-build
3fac3373c9 [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-04 08:24:19 +00:00
alfresco-build
ee857ce1de [maven-release-plugin][skip ci] prepare release 23.6.0.16 2025-09-04 08:24:17 +00:00
Debjit Chattopadhyay
483d7fab21 Merge pull request #3563 from Alfresco/fix/MNT-24776-backport-23.N
backport MNT-24776 to 23.N
2025-09-04 13:01:59 +05:30
Debjit Chattopadhyay
590209b299 Fix for "MNT-24776 : Category Picker Error when a User does not have Read Permissions to a Category"
(cherry picked from commit 626640ddc7)
2025-09-03 15:14:48 +05:30
alfresco-build
376514df67 [maven-release-plugin][skip ci] prepare for next development iteration 2025-08-29 13:30:01 +00:00
alfresco-build
7144a2dd94 [maven-release-plugin][skip ci] prepare release 23.6.0.15 2025-08-29 13:29:59 +00:00
Debjit Chattopadhyay
b4da3d8c20 MNT-24308 - fix search filterquery creation for displaying favourites in Share for both Solr and Elasticsearch
MNT-24308 : On click of 'My Favorites' link under 'Documents' in the left panel of 'Document Library' page, all the links appear grayed out
2025-08-29 18:15:59 +05:30
Debjit Chattopadhyay
62de9ff0c0 MNT-24308 : On click of 'My Favorites' link under 'Documents' in the left panel of 'Document Library' page, all the links appear grayed out 2025-08-29 16:25:23 +05:30
alfresco-build
a11acce720 [maven-release-plugin][skip ci] prepare for next development iteration 2025-08-28 13:37:40 +00:00
alfresco-build
1128011e15 [maven-release-plugin][skip ci] prepare release 23.6.0.14 2025-08-28 13:37:38 +00:00
Tiago Salvado
d0cb45de0d [MNT-25242] Add property to control if scope is cleaned (#3520) (#3550) 2025-08-28 11:18:05 +01:00
alfresco-build
2b48195896 [maven-release-plugin][skip ci] prepare for next development iteration 2025-08-28 10:12:53 +00:00
51 changed files with 7782 additions and 6803 deletions

View File

@@ -107,7 +107,7 @@ jobs:
bash ./scripts/ci/init.sh
bash ./scripts/ci/build.sh
- name: "Run SAST Scan"
uses: veracode/Veracode-pipeline-scan-action@v1.0.16
uses: veracode/Veracode-pipeline-scan-action@v1.0.20
with:
vid: ${{ secrets.VERACODE_API_ID }}
vkey: ${{ secrets.VERACODE_API_KEY }}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,3 +1,3 @@
SOLR6_TAG=2.0.13
SOLR6_TAG=2.0.17
POSTGRES_TAG=15.4
ACTIVEMQ_TAG=5.18.3-jre17-rockylinux8

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

@@ -160,6 +160,7 @@
<property name="nodesModelFactory" ref="nodesModelFactory" />
<property name="nodeService" ref="NodeService"/>
<property name="recordsManagementServiceRegistry" ref="RecordsManagementServiceRegistry"/>
<property name="dispositionService" ref="DispositionService"/>
</bean>
<bean class="org.alfresco.rm.rest.api.recordfolders.RecordFolderEntityResource">

View File

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

View File

@@ -27,7 +27,6 @@
package org.alfresco.module.org_alfresco_module_rm.query;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -36,6 +35,11 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
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;
@@ -47,9 +51,6 @@ import org.alfresco.service.cmr.repository.InvalidNodeRefException;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.Pair;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mybatis.spring.SqlSessionTemplate;
/**
* Records management query DAO implementation
@@ -89,7 +90,8 @@ public class RecordsManagementQueryDAOImpl implements RecordsManagementQueryDAO,
protected TenantService tenantService;
/**
* @param sqlSessionTemplate SQL session template
* @param sqlSessionTemplate
* SQL session template
*/
public final void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate)
{
@@ -97,7 +99,8 @@ public class RecordsManagementQueryDAOImpl implements RecordsManagementQueryDAO,
}
/**
* @param qnameDAO qname DAO
* @param qnameDAO
* qname DAO
*/
public final void setQnameDAO(QNameDAO qnameDAO)
{
@@ -173,8 +176,9 @@ public class RecordsManagementQueryDAOImpl implements RecordsManagementQueryDAO,
/**
* Get a set of node reference which reference the provided content URL
*
* @param String contentUrl content URL
* @return Set<NodeRef> set of nodes that reference the provided content URL
* @param String
* contentUrl content URL
* @return Set<NodeRef> set of nodes that reference the provided content URL
*/
@Override
public Set<NodeRef> getNodeRefsWhichReferenceContentUrl(String contentUrl)
@@ -188,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())
{
@@ -224,7 +234,7 @@ public class RecordsManagementQueryDAOImpl implements RecordsManagementQueryDAO,
if (logger.isDebugEnabled())
{
logMessage.append(nodeRefToAdd)
.append(" (from version)");
.append(" (from version)");
}
}
@@ -232,7 +242,7 @@ public class RecordsManagementQueryDAOImpl implements RecordsManagementQueryDAO,
else
{
nodeRefToAdd = nodeDAO.getNodeIdStatus(nodeId)
.getNodeRef();
.getNodeRef();
if (logger.isDebugEnabled())
{
logMessage.append(nodeRefToAdd);
@@ -266,9 +276,9 @@ public class RecordsManagementQueryDAOImpl implements RecordsManagementQueryDAO,
{
Map<String, Object> params = new HashMap<>(2);
params.put("processed", qnameDAO.getQName(ASPECT_DISPOSITION_PROCESSED)
.getFirst());
.getFirst());
params.put("folderQnameId", qnameDAO.getQName(TYPE_RECORD_FOLDER)
.getFirst());
.getFirst());
params.put("start", start);
params.put("end", end);
@@ -280,7 +290,7 @@ public class RecordsManagementQueryDAOImpl implements RecordsManagementQueryDAO,
for (NodeRefEntity nodeRefEntity : entities)
{
results.add(
new NodeRef(nodeRefEntity.getProtocol(), nodeRefEntity.getIdentifier(), nodeRefEntity.getUuid()));
new NodeRef(nodeRefEntity.getProtocol(), nodeRefEntity.getIdentifier(), nodeRefEntity.getUuid()));
}
return results;
@@ -289,7 +299,8 @@ public class RecordsManagementQueryDAOImpl implements RecordsManagementQueryDAO,
/**
* @see org.alfresco.module.org_alfresco_module_rm.query.RecordsManagementQueryDAO#getPropertyStringValueEntity(String stringValue)
*/
public PropertyStringValueEntity getPropertyStringValueEntity(String stringValue){
public PropertyStringValueEntity getPropertyStringValueEntity(String stringValue)
{
PropertyStringValueEntity propertyStringValueEntity = new PropertyStringValueEntity();
propertyStringValueEntity.setValue(stringValue);

View File

@@ -36,6 +36,7 @@ import java.util.Set;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.extensions.webscripts.ui.common.StringUtils;
import org.alfresco.model.ContentModel;
@@ -649,8 +650,8 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl
}
catch (DuplicateChildNodeNameException ex)
{
// the group was concurrently created
group = authorityService.getName(AuthorityType.GROUP, groupShortName);
// Rethrow as ConcurrencyFailureException so that is can be retried and linked to the group created by the concurrent transaction
throw new ConcurrencyFailureException("IPR group creation failed due to concurrent duplicate group name creation: " + groupShortName);
}
return group;

View File

@@ -26,10 +26,25 @@
*/
package org.alfresco.rm.rest.api.retentionschedule;
import static org.alfresco.module.org_alfresco_module_rm.util.RMParameterCheck.checkNotBlank;
import static org.alfresco.util.ParameterCheck.mandatory;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.alfresco.module.org_alfresco_module_rm.RecordsManagementServiceRegistry;
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionActionDefinition;
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionSchedule;
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionScheduleImpl;
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService;
import org.alfresco.module.org_alfresco_module_rm.disposition.property.DispositionProperty;
import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
import org.alfresco.rest.framework.WebApiDescription;
import org.alfresco.rest.framework.core.exceptions.ConstraintViolatedException;
@@ -50,18 +65,6 @@ import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static org.alfresco.module.org_alfresco_module_rm.util.RMParameterCheck.checkNotBlank;
import static org.alfresco.util.ParameterCheck.mandatory;
/**
* Retention schedule action relation is used to perform the retention schedule step operations.
*/
@@ -73,6 +76,7 @@ public class RetentionScheduleActionRelation implements RelationshipResourceActi
protected NodeService nodeService;
private RecordsManagementServiceRegistry service;
private ApiNodesModelFactory nodesModelFactory;
private DispositionService dispositionService;
public void setApiUtils(FilePlanComponentsApiUtils apiUtils)
{
@@ -94,27 +98,41 @@ public class RetentionScheduleActionRelation implements RelationshipResourceActi
this.service = service;
}
public void setDispositionService(DispositionService dispositionService)
{
this.dispositionService = dispositionService;
}
@Override
@WebApiDescription(title="Create a retention schedule step for the particular retention schedule using the 'retentionScheduleId'")
@WebApiDescription(title = "Create a retention schedule step for the particular retention schedule using the 'retentionScheduleId'")
public List<RetentionScheduleActionDefinition> create(String retentionScheduleId, List<RetentionScheduleActionDefinition> nodeInfos, Parameters parameters)
{
checkNotBlank("retentionScheduleId", retentionScheduleId);
mandatory("entity", nodeInfos);
mandatory("parameters", parameters);
NodeRef retentionScheduleNodeRef = apiUtils.lookupAndValidateNodeType(retentionScheduleId, RecordsManagementModel.TYPE_DISPOSITION_SCHEDULE);
RetentionScheduleActionDefinition retentionScheduleActionDefinition = nodeInfos.get(0);
// validation for the order of the step
retentionScheduleStepValidation(retentionScheduleNodeRef, nodeInfos.get(0));
retentionScheduleStepValidation(retentionScheduleNodeRef, retentionScheduleActionDefinition);
DispositionSchedule dispositionSchedule = new DispositionScheduleImpl(service, nodeService, retentionScheduleNodeRef);
boolean isRecordLevel = dispositionSchedule.isRecordLevelDisposition();
// request property validation
retentionScheduleRequestValidation(nodeInfos.get(0));
retentionScheduleRequestValidation(retentionScheduleActionDefinition, isRecordLevel);
// create the parameters for the action definition
Map<QName, Serializable> actionDefinitionParams = nodesModelFactory.createRetentionActionDefinitionParams(nodeInfos.get(0));
Map<QName, Serializable> actionDefinitionParams = nodesModelFactory.createRetentionActionDefinitionParams(retentionScheduleActionDefinition);
// create the child association from the schedule to the action definition
NodeRef actionNodeRef = this.nodeService.createNode(retentionScheduleNodeRef,
RecordsManagementModel.ASSOC_DISPOSITION_ACTION_DEFINITIONS,
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI,
QName.createValidLocalName(nodeInfos.get(0).getName())),
QName.createValidLocalName(retentionScheduleActionDefinition.getName())),
RecordsManagementModel.TYPE_DISPOSITION_ACTION_DEFINITION, actionDefinitionParams).getChildRef();
DispositionSchedule dispositionSchedule = new DispositionScheduleImpl(service, nodeService, retentionScheduleNodeRef);
DispositionActionDefinition dispositionActionDefinition = dispositionSchedule.getDispositionActionDefinition(actionNodeRef.getId());
List<RetentionScheduleActionDefinition> responseActions = new ArrayList<>();
if (dispositionActionDefinition != null)
@@ -141,8 +159,11 @@ public class RetentionScheduleActionRelation implements RelationshipResourceActi
/**
* this method is used to validate the order of the retention schedule step
* @param retentionScheduleNodeRef nodeRef
* @param retentionScheduleActionDefinition retention schedule action definition
*
* @param retentionScheduleNodeRef
* nodeRef
* @param retentionScheduleActionDefinition
* retention schedule action definition
*/
private void retentionScheduleStepValidation(NodeRef retentionScheduleNodeRef, RetentionScheduleActionDefinition retentionScheduleActionDefinition)
{
@@ -188,17 +209,19 @@ public class RetentionScheduleActionRelation implements RelationshipResourceActi
/**
* this method is used to validate the request of the retention schedule
* @param retentionScheduleActionDefinition retention schedule action definition
*
* @param retentionScheduleActionDefinition
* retention schedule action definition
*/
private void retentionScheduleRequestValidation(RetentionScheduleActionDefinition retentionScheduleActionDefinition)
private void retentionScheduleRequestValidation(RetentionScheduleActionDefinition retentionScheduleActionDefinition, boolean isRecordLevel)
{
// step name validation
if (invalidStepNameCheck(retentionScheduleActionDefinition.getName()))
{
throw new InvalidArgumentException("name value is invalid : " +retentionScheduleActionDefinition.getName());
throw new InvalidArgumentException("name value is invalid : " + retentionScheduleActionDefinition.getName());
}
validatePeriodAndPeriodProperty(retentionScheduleActionDefinition);
validatePeriodAndPeriodProperty(retentionScheduleActionDefinition, isRecordLevel);
// event name validation
if (invalidEventNameCheck(retentionScheduleActionDefinition.getEvents()))
@@ -217,17 +240,17 @@ public class RetentionScheduleActionRelation implements RelationshipResourceActi
}
}
private void validatePeriodAndPeriodProperty(RetentionScheduleActionDefinition retentionScheduleActionDefinition)
private void validatePeriodAndPeriodProperty(RetentionScheduleActionDefinition retentionScheduleActionDefinition, boolean isRecordLevel)
{
// period value validation
if (invalidPeriodCheck(retentionScheduleActionDefinition.getPeriod()))
{
throw new InvalidArgumentException("period value is invalid : " +retentionScheduleActionDefinition.getPeriod());
throw new InvalidArgumentException("period value is invalid : " + retentionScheduleActionDefinition.getPeriod());
}
// periodProperty validation
List<String> validPeriodProperties = Arrays.asList("cm:created", "rma:cutOffDate", "rma:dispositionAsOf");
if (validPeriodProperties.stream().noneMatch(retentionScheduleActionDefinition.getPeriodProperty()::equals))
Collection<DispositionProperty> validPeriodProperties = dispositionService.getDispositionProperties(isRecordLevel, retentionScheduleActionDefinition.getName());
if (validPeriodProperties.stream().map(dp -> dp.getQName().toPrefixString()).noneMatch(retentionScheduleActionDefinition.getPeriodProperty()::equals))
{
throw new InvalidArgumentException("periodProperty value is invalid: " + retentionScheduleActionDefinition.getPeriodProperty());
}
@@ -243,7 +266,7 @@ public class RetentionScheduleActionRelation implements RelationshipResourceActi
{
return retentionScheduleActionDefinition.getLocation() != null
&& !retentionScheduleActionDefinition.getName().equals(RetentionSteps.TRANSFER.stepName)
&& !retentionScheduleActionDefinition.getLocation().isEmpty();
&& !retentionScheduleActionDefinition.getLocation().isEmpty();
}
private boolean checkStepAlreadyExists(Set<String> completedActions, String stepName)

View File

@@ -29,14 +29,23 @@ package org.alfresco.module.org_alfresco_module_rm.test.legacy.service;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import org.junit.Assert;
import org.springframework.dao.ConcurrencyFailureException;
import org.alfresco.model.ContentModel;
import org.alfresco.module.org_alfresco_module_rm.test.util.BaseRMTestCase;
import org.alfresco.query.PagingRequest;
import org.alfresco.query.PagingResults;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.site.SiteModel;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.security.AccessPermission;
import org.alfresco.service.cmr.security.AccessStatus;
import org.alfresco.service.cmr.security.AuthorityType;
import org.alfresco.service.cmr.site.SiteService;
import org.alfresco.service.cmr.site.SiteVisibility;
import org.alfresco.util.GUID;
@@ -73,8 +82,7 @@ public class ExtendedSecurityServiceImplTest extends BaseRMTestCase
private String createTestUser()
{
return doTestInTransaction(new Test<String>()
{
return doTestInTransaction(new Test<String>() {
public String run()
{
String userName = GUID.generate();
@@ -90,8 +98,7 @@ public class ExtendedSecurityServiceImplTest extends BaseRMTestCase
final String elephant = createTestUser();
final String snake = createTestUser();
doTestInTransaction(new Test<Void>()
{
doTestInTransaction(new Test<Void>() {
public Void run()
{
assertFalse(extendedSecurityService.hasExtendedSecurity(filePlan));
@@ -118,7 +125,7 @@ public class ExtendedSecurityServiceImplTest extends BaseRMTestCase
// test remove
extendedSecurityService.remove(recordToo);
assertFalse(extendedSecurityService.hasExtendedSecurity(recordToo));
assertTrue(extendedSecurityService.getReaders(recordToo).isEmpty());
assertTrue(extendedSecurityService.getWriters(recordToo).isEmpty());
@@ -133,8 +140,7 @@ public class ExtendedSecurityServiceImplTest extends BaseRMTestCase
final String monkey = createTestUser();
final String elephant = createTestUser();
doTestInTransaction(new Test<Void>()
{
doTestInTransaction(new Test<Void>() {
Set<String> extendedReaders = new HashSet<>(2);
public Void run() throws Exception
@@ -184,112 +190,337 @@ public class ExtendedSecurityServiceImplTest extends BaseRMTestCase
public void testDifferentUsersDifferentPermissions()
{
final String userNone = createTestUser();
final String userRead = createTestUser();
final String userWrite = createTestUser();
final String siteShortName = GUID.generate();
final String userNone = createTestUser();
final String userRead = createTestUser();
final String userWrite = createTestUser();
final String siteShortName = GUID.generate();
doTestInTransaction(new Test<Void>()
{
doTestInTransaction(new Test<Void>() {
public Void run() throws Exception
{
siteService.createSite(null, siteShortName, "test", "test", SiteVisibility.PRIVATE);
return null;
siteService.createSite(null, siteShortName, "test", "test", SiteVisibility.PRIVATE);
return null;
}
});
final NodeRef documentLibrary = doTestInTransaction(new Test<NodeRef>()
{
final NodeRef documentLibrary = doTestInTransaction(new Test<NodeRef>() {
public NodeRef run() throws Exception
{
siteService.setMembership(siteShortName, userRead, SiteModel.SITE_CONSUMER);
siteService.setMembership(siteShortName, userWrite, SiteModel.SITE_COLLABORATOR);
return siteService.createContainer(siteShortName, SiteService.DOCUMENT_LIBRARY, null, null);
siteService.setMembership(siteShortName, userRead, SiteModel.SITE_CONSUMER);
siteService.setMembership(siteShortName, userWrite, SiteModel.SITE_COLLABORATOR);
return siteService.createContainer(siteShortName, SiteService.DOCUMENT_LIBRARY, null, null);
}
});
final NodeRef record = doTestInTransaction(new Test<NodeRef>()
{
final NodeRef record = doTestInTransaction(new Test<NodeRef>() {
public NodeRef run() throws Exception
{
NodeRef record = fileFolderService.create(documentLibrary, GUID.generate(), ContentModel.TYPE_CONTENT).getNodeRef();
recordService.createRecord(filePlan, record);
return record;
NodeRef record = fileFolderService.create(documentLibrary, GUID.generate(), ContentModel.TYPE_CONTENT)
.getNodeRef();
recordService.createRecord(filePlan, record);
return record;
}
});
doTestInTransaction(new Test<Void>()
{
doTestInTransaction(new Test<Void>() {
public Void run() throws Exception
{
AuthenticationUtil.runAs(new RunAsWork<Void>()
{
public Void doWork() throws Exception
{
// check permissions
assertEquals(AccessStatus.DENIED, permissionService.hasPermission(record, READ_RECORDS));
assertEquals(AccessStatus.DENIED, permissionService.hasPermission(record, FILING));
return null;
}
}, userNone);
AuthenticationUtil.runAs(new RunAsWork<Void>() {
public Void doWork() throws Exception
{
// check permissions
assertEquals(AccessStatus.DENIED, permissionService.hasPermission(record, READ_RECORDS));
assertEquals(AccessStatus.DENIED, permissionService.hasPermission(record, FILING));
return null;
}
}, userNone);
AuthenticationUtil.runAs(new RunAsWork<Void>()
{
public Void doWork() throws Exception
{
// check permissions
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(record, READ_RECORDS));
assertEquals(AccessStatus.DENIED, permissionService.hasPermission(record, FILING));
return null;
}
}, userRead);
AuthenticationUtil.runAs(new RunAsWork<Void>() {
public Void doWork() throws Exception
{
// check permissions
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(record, READ_RECORDS));
assertEquals(AccessStatus.DENIED, permissionService.hasPermission(record, FILING));
return null;
}
}, userRead);
AuthenticationUtil.runAs(new RunAsWork<Void>()
{
public Void doWork() throws Exception
{
// check permissions
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(record, READ_RECORDS));
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(record, FILING));
return null;
}
}, userWrite);
AuthenticationUtil.runAs(new RunAsWork<Void>() {
public Void doWork() throws Exception
{
// check permissions
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(record, READ_RECORDS));
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(record, FILING));
return null;
}
}, userWrite);
AuthenticationUtil.runAs(new RunAsWork<Void>()
{
public Void doWork() throws Exception
{
// check permissions
assertEquals(AccessStatus.DENIED, permissionService.hasPermission(record, READ_RECORDS));
assertEquals(AccessStatus.DENIED, permissionService.hasPermission(record, FILING));
return null;
}
}, userNone);
AuthenticationUtil.runAs(new RunAsWork<Void>() {
public Void doWork() throws Exception
{
// check permissions
assertEquals(AccessStatus.DENIED, permissionService.hasPermission(record, READ_RECORDS));
assertEquals(AccessStatus.DENIED, permissionService.hasPermission(record, FILING));
return null;
}
}, userNone);
AuthenticationUtil.runAs(new RunAsWork<Void>()
{
public Void doWork() throws Exception
{
// check permissions
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(record, READ_RECORDS));
assertEquals(AccessStatus.DENIED, permissionService.hasPermission(record, FILING));
return null;
}
}, userRead);
AuthenticationUtil.runAs(new RunAsWork<Void>() {
public Void doWork() throws Exception
{
// check permissions
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(record, READ_RECORDS));
assertEquals(AccessStatus.DENIED, permissionService.hasPermission(record, FILING));
return null;
}
}, userRead);
AuthenticationUtil.runAs(new RunAsWork<Void>()
{
public Void doWork() throws Exception
{
// check permissions
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(record, READ_RECORDS));
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(record, FILING));
return null;
}
}, userWrite);
AuthenticationUtil.runAs(new RunAsWork<Void>() {
public Void doWork() throws Exception
{
// check permissions
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(record, READ_RECORDS));
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(record, FILING));
return null;
}
}, userWrite);
return null;
return null;
}
});
}
public void testConcurrentSetWithRetry()
{
Set<String> extendedReaders = new HashSet<>(2);
Set<String> extendedWriters = new HashSet<>(2);
Set<NodeRef> documents = setupConcurrentTestCase(10, extendedReaders, extendedWriters);
// For each record created previously, spawn a thread to set extended security so we cause concurrency
// failure trying to create IPR groups with the same name
fireParallelExecutionOfSetExtendedSecurity(documents, extendedReaders, extendedWriters, true);
// Look for duplicated IPR groups and verify all documents have the same groups assigned
verifyCreatedGroups(documents, false);
AuthenticationUtil.clearCurrentSecurityContext();
}
public void testConcurrentSetWithoutRetry()
{
Set<String> extendedReaders = new HashSet<>(2);
Set<String> extendedWriters = new HashSet<>(2);
Set<NodeRef> documents = setupConcurrentTestCase(10, extendedReaders, extendedWriters);
// For each record created previously, spawn a thread to set extended security so we cause concurrency
// failure trying to create IPR groups with the same name.
// Since there is no retry, we expect to get a ConcurrencyFailureException
Assert.assertThrows(ConcurrencyFailureException.class, () -> {
fireParallelExecutionOfSetExtendedSecurity(documents, extendedReaders, extendedWriters, false);
});
// Look for duplicated IPR groups and verify all documents have the same groups assigned
// Since there was a ConcurrencyFailureException some threads failed to set extended security so some
// documents may not have IPR groups created.
verifyCreatedGroups(documents, true);
AuthenticationUtil.clearCurrentSecurityContext();
}
private Set<NodeRef> setupConcurrentTestCase(int concurrentThreads, Set<String> extendedReaders, Set<String> extendedWriters)
{
final String usera = createTestUser();
final String userb = createTestUser();
final String owner = createTestUser();
extendedReaders.add(usera);
extendedReaders.add(userb);
extendedWriters.add(usera);
extendedWriters.add(userb);
AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser();
// Create a site
NodeRef documentLib = createSite(new HashSet<>(), new HashSet<>());
// Create records in the site document library
return createRecords(concurrentThreads, documentLib, owner);
}
private NodeRef createSite(Set<String> readers, Set<String> writers)
{
return retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<NodeRef>() {
@Override
public NodeRef execute() throws Throwable
{
final String siteShortName = GUID.generate();
siteService.createSite(null, siteShortName, "test", "test", SiteVisibility.PRIVATE);
readers.forEach(reader -> siteService.setMembership(siteShortName, reader, SiteModel.SITE_CONSUMER));
writers.forEach(writer -> siteService.setMembership(siteShortName, writer, SiteModel.SITE_COLLABORATOR));
return siteService.createContainer(siteShortName, SiteService.DOCUMENT_LIBRARY, null, null);
}
}, false, true);
}
private Set<NodeRef> createRecords(int numRecords, NodeRef parent, String owner)
{
return retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Set<NodeRef>>() {
@Override
public Set<NodeRef> execute() throws Throwable
{
int createdRecords = 0;
Set<NodeRef> documents = new HashSet<>();
while (createdRecords < numRecords)
{
final NodeRef doc = fileFolderService.create(parent, GUID.generate(), ContentModel.TYPE_CONTENT).getNodeRef();
ownableService.setOwner(doc, owner);
recordService.createRecord(filePlan, doc, rmFolder, true);
recordService.file(doc);
recordService.complete(doc);
documents.add(doc);
createdRecords++;
}
return documents;
}
}, false, true);
}
private void setExtendedSecurity(NodeRef doc, Set<String> readers, Set<String> writers, boolean useRetry)
{
if (!useRetry)
{
setExtendedSecurity(doc, readers, writers);
return;
}
retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Void>() {
@Override
public Void execute() throws Throwable
{
setExtendedSecurity(doc, readers, writers);
return null;
}
}, false, true);
}
private void setExtendedSecurity(NodeRef doc, Set<String> readers, Set<String> writers)
{
AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser();
extendedSecurityService.set(doc, readers, writers);
}
private void fireParallelExecutionOfSetExtendedSecurity(Set<NodeRef> documents, Set<String> extendedReaders, Set<String> extendedWriters, boolean useRetry)
{
CompletableFuture<?>[] futures = documents.stream()
.map(doc -> CompletableFuture.runAsync(() -> setExtendedSecurity(doc, extendedReaders, extendedWriters, useRetry)))
.toArray(CompletableFuture[]::new);
try
{
CompletableFuture.allOf(futures).join();
}
catch (Exception e)
{
Throwable cause = e.getCause();
if (cause instanceof ConcurrencyFailureException)
{
throw (ConcurrencyFailureException) cause;
}
throw new RuntimeException("Error during parallel execution", e);
}
}
private void verifyCreatedGroups(Set<NodeRef> documents, boolean onlyDuplicatesValidation)
{
retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Void>() {
@Override
public Void execute() throws Throwable
{
Set<String> expectedAuthorities = null;
Set<Set<String>> errors = new HashSet<>();
for (NodeRef doc : documents)
{
Set<AccessPermission> permissions = permissionService.getAllSetPermissions(doc);
Set<String> authorities = getDocumentAuthorities(permissions);
Set<String> authoritiesById = getAuthorityIds(authorities);
verifyIPRGroups(authorities, onlyDuplicatesValidation);
if (onlyDuplicatesValidation)
{
// Some documents may not have IPR groups created if there was a ConcurrencyFailureException
continue;
}
// All documents should have the same exact set of groups assigned
if (expectedAuthorities == null)
{
expectedAuthorities = authoritiesById;
}
if (!expectedAuthorities.equals(authoritiesById))
{
errors.add(authoritiesById);
}
}
assertTrue("Unexpected authorities linked to document", errors.isEmpty());
return null;
}
}, false, true);
}
private Set<String> getDocumentAuthorities(Set<AccessPermission> permissions)
{
Set<String> authorities = new HashSet<>();
for (AccessPermission accessPermission : permissions)
{
String authority = accessPermission.getAuthority();
String authName = authorityService.getName(AuthorityType.GROUP, authority);
authorities.add(authName);
}
return authorities;
}
private Set<String> getAuthorityIds(Set<String> authorities)
{
Set<String> authorityIds = new HashSet<>();
for (String authority : authorities)
{
String authId = authorityService.getAuthorityNodeRef(authority) != null
? authorityService.getAuthorityNodeRef(authority).getId()
: null;
authorityIds.add(authId);
}
return authorityIds;
}
private void verifyIPRGroups(Set<String> authorities, boolean onlyDuplicatesValidation)
{
boolean hasGroupIPR = false;
for (String authorityName : authorities)
{
String shortName = authorityService.getShortName(authorityName);
if (authorityName.startsWith("GROUP_IPR"))
{
hasGroupIPR = true;
PagingResults<String> results = authorityService.getAuthorities(AuthorityType.GROUP, null, shortName, false,
false, new PagingRequest(0, 10));
assertEquals("No duplicated IPR group expected", 1, results.getPage().size());
}
}
if (!onlyDuplicatesValidation)
{
assertTrue("No IPR Groups created", hasGroupIPR);
}
}
}

View File

@@ -4,7 +4,7 @@
# Version label
version.major=23
version.minor=6
version.minor=7
version.revision=0
version.label=

View File

@@ -27,22 +27,32 @@
package org.alfresco.module.org_alfresco_module_rm.test.util;
import static org.alfresco.module.org_alfresco_module_rm.test.util.AlfMock.generateQName;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.alfresco.module.org_alfresco_module_rm.test.util.AlfMock.generateQName;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.ExpectedException;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.context.ApplicationContext;
import org.alfresco.model.ContentModel;
import org.alfresco.module.org_alfresco_module_rm.action.RecordsManagementActionService;
import org.alfresco.module.org_alfresco_module_rm.audit.RecordsManagementAuditService;
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService;
import org.alfresco.module.org_alfresco_module_rm.event.RecordsManagementEventService;
import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanService;
import org.alfresco.module.org_alfresco_module_rm.freeze.FreezeService;
import org.alfresco.module.org_alfresco_module_rm.hold.HoldService;
@@ -85,20 +95,11 @@ import org.alfresco.service.namespace.QNamePattern;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.alfresco.util.GUID;
import org.alfresco.util.collections.CollectionUtils;
import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.ExpectedException;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.context.ApplicationContext;
/**
* Base unit test.
* <p>
* Contains core and records management service mocks ready for injection. Helper methods
* provide an easy way to build RM or Alfresco constructs for use in tests.
* Contains core and records management service mocks ready for injection. Helper methods provide an easy way to build RM or Alfresco constructs for use in tests.
*
* @author Roy Wetherall
* @since 2.2
@@ -112,47 +113,85 @@ public class BaseUnitTest implements RecordsManagementModel, ContentModel
protected NodeRef record;
/** core service mocks */
@Mock(name="nodeService") protected NodeService mockedNodeService;
@Mock(name="dictionaryService") protected DictionaryService mockedDictionaryService;
@Mock(name="namespaceService") protected NamespaceService mockedNamespaceService;
@Mock(name="identifierService") protected IdentifierService mockedIdentifierService;
@Mock(name="permissionService") protected PermissionService mockedPermissionService;
@Mock(name="ownableService") protected OwnableService mockedOwnableService;
@Mock(name="searchService") protected SearchService mockedSearchService;
@Mock(name="retryingTransactionHelper") protected RetryingTransactionHelper mockedRetryingTransactionHelper;
@Mock(name="authorityService") protected AuthorityService mockedAuthorityService;
@Mock(name="policyComponent") protected PolicyComponent mockedPolicyComponent;
@Mock(name="copyService") protected CopyService mockedCopyService;
@Mock(name="fileFolderService") protected FileFolderService mockedFileFolderService;
@Mock(name="modelSecurityService") protected ModelSecurityService mockedModelSecurityService;
@Mock(name="ruleService") protected RuleService mockedRuleService;
@Mock(name="versionService") protected VersionService mockedVersionService;
@Mock(name = "nodeService")
protected NodeService mockedNodeService;
@Mock(name = "dictionaryService")
protected DictionaryService mockedDictionaryService;
@Mock(name = "namespaceService")
protected NamespaceService mockedNamespaceService;
@Mock(name = "identifierService")
protected IdentifierService mockedIdentifierService;
@Mock(name = "permissionService")
protected PermissionService mockedPermissionService;
@Mock(name = "ownableService")
protected OwnableService mockedOwnableService;
@Mock(name = "searchService")
protected SearchService mockedSearchService;
@Mock(name = "retryingTransactionHelper")
protected RetryingTransactionHelper mockedRetryingTransactionHelper;
@Mock(name = "authorityService")
protected AuthorityService mockedAuthorityService;
@Mock(name = "policyComponent")
protected PolicyComponent mockedPolicyComponent;
@Mock(name = "copyService")
protected CopyService mockedCopyService;
@Mock(name = "fileFolderService")
protected FileFolderService mockedFileFolderService;
@Mock(name = "modelSecurityService")
protected ModelSecurityService mockedModelSecurityService;
@Mock(name = "ruleService")
protected RuleService mockedRuleService;
@Mock(name = "versionService")
protected VersionService mockedVersionService;
/** rm service mocks */
@Mock(name="filePlanService") protected FilePlanService mockedFilePlanService;
@Mock(name="recordFolderService") protected RecordFolderService mockedRecordFolderService;
@Mock(name="recordService") protected RecordService mockedRecordService;
@Mock(name="holdService") protected HoldService mockedHoldService;
@Mock(name="recordsManagementActionService") protected RecordsManagementActionService mockedRecordsManagementActionService;
@Mock(name="reportService") protected ReportService mockedReportService;
@Mock(name="filePlanRoleService") protected FilePlanRoleService mockedFilePlanRoleService;
@Mock(name="recordsManagementAuditService") protected RecordsManagementAuditService mockedRecordsManagementAuditService;
@Mock(name="policyBehaviourFilter") protected BehaviourFilter mockedBehaviourFilter;
@Mock(name="authenticationUtil") protected AuthenticationUtil mockedAuthenticationUtil;
@Mock(name="extendedPermissionService") protected ExtendedPermissionService mockedExtendedPermissionService;
@Mock(name="extendedSecurityService") protected ExtendedSecurityService mockedExtendedSecurityService;
@Mock(name="recordableVersionConfigService") protected RecordableVersionConfigService mockedRecordableVersionConfigService;
@Mock(name="cmObjectType") protected CmObjectType mockedCmObjectType;
@Mock(name="recordableVersionService") protected RecordableVersionService mockedRecordableVersionService;
@Mock(name="transactionalResourceHelper") protected TransactionalResourceHelper mockedTransactionalResourceHelper;
@Mock(name="alfrescoTransactionSupport") protected AlfrescoTransactionSupport mockedAlfrescoTransactionSupport;
@Mock(name="freezeService") protected FreezeService mockedFreezeService;
@Mock(name="dispositionService") protected DispositionService mockedDispositionService;
@Mock(name = "filePlanService")
protected FilePlanService mockedFilePlanService;
@Mock(name = "recordFolderService")
protected RecordFolderService mockedRecordFolderService;
@Mock(name = "recordService")
protected RecordService mockedRecordService;
@Mock(name = "holdService")
protected HoldService mockedHoldService;
@Mock(name = "recordsManagementActionService")
protected RecordsManagementActionService mockedRecordsManagementActionService;
@Mock(name = "reportService")
protected ReportService mockedReportService;
@Mock(name = "filePlanRoleService")
protected FilePlanRoleService mockedFilePlanRoleService;
@Mock(name = "recordsManagementAuditService")
protected RecordsManagementAuditService mockedRecordsManagementAuditService;
@Mock(name = "policyBehaviourFilter")
protected BehaviourFilter mockedBehaviourFilter;
@Mock(name = "authenticationUtil")
protected AuthenticationUtil mockedAuthenticationUtil;
@Mock(name = "extendedPermissionService")
protected ExtendedPermissionService mockedExtendedPermissionService;
@Mock(name = "extendedSecurityService")
protected ExtendedSecurityService mockedExtendedSecurityService;
@Mock(name = "recordableVersionConfigService")
protected RecordableVersionConfigService mockedRecordableVersionConfigService;
@Mock(name = "cmObjectType")
protected CmObjectType mockedCmObjectType;
@Mock(name = "recordableVersionService")
protected RecordableVersionService mockedRecordableVersionService;
@Mock(name = "transactionalResourceHelper")
protected TransactionalResourceHelper mockedTransactionalResourceHelper;
@Mock(name = "alfrescoTransactionSupport")
protected AlfrescoTransactionSupport mockedAlfrescoTransactionSupport;
@Mock(name = "freezeService")
protected FreezeService mockedFreezeService;
@Mock(name = "dispositionService")
protected DispositionService mockedDispositionService;
@Mock(name = "recordsManagementEventService")
protected RecordsManagementEventService mockedRecordsManagementEventService;
/** application context mock */
@Mock(name="applicationContext") protected ApplicationContext mockedApplicationContext;
@Mock(name = "applicationContext")
protected ApplicationContext mockedApplicationContext;
@Mock protected NodeTypeUtility mockedNodeTypeUtility;
@Mock
protected NodeTypeUtility mockedNodeTypeUtility;
/** expected exception rule */
@Rule
@@ -171,17 +210,16 @@ public class BaseUnitTest implements RecordsManagementModel, ContentModel
lenient().doReturn(mockedNodeService).when(mockedApplicationContext).getBean("dbNodeService");
// setup retrying transaction helper
Answer<Object> doInTransactionAnswer = new Answer<Object>()
{
Answer<Object> doInTransactionAnswer = new Answer<Object>() {
@SuppressWarnings("rawtypes")
@Override
public Object answer(InvocationOnMock invocation) throws Throwable
{
RetryingTransactionCallback callback = (RetryingTransactionCallback)invocation.getArguments()[0];
RetryingTransactionCallback callback = (RetryingTransactionCallback) invocation.getArguments()[0];
return callback.execute();
}
};
lenient().doAnswer(doInTransactionAnswer).when(mockedRetryingTransactionHelper).<Object>doInTransaction(any(RetryingTransactionCallback.class));
lenient().doAnswer(doInTransactionAnswer).when(mockedRetryingTransactionHelper).<Object> doInTransaction(any(RetryingTransactionCallback.class));
// setup mocked authentication util
MockAuthenticationUtilHelper.setup(mockedAuthenticationUtil);
@@ -215,8 +253,9 @@ public class BaseUnitTest implements RecordsManagementModel, ContentModel
/**
* Helper method to generate hold reference
*
* @param name hold name
* @return {@link NodeRef} node reference that will behave like a hold
* @param name
* hold name
* @return {@link NodeRef} node reference that will behave like a hold
*/
protected NodeRef generateHoldNodeRef(String name)
{
@@ -229,7 +268,7 @@ public class BaseUnitTest implements RecordsManagementModel, ContentModel
/**
* Helper method to generate record folder reference
*
* @return {@link NodeRef} node reference that will behave like a record folder
* @return {@link NodeRef} node reference that will behave like a record folder
*/
protected NodeRef generateRecordFolder()
{
@@ -242,7 +281,7 @@ public class BaseUnitTest implements RecordsManagementModel, ContentModel
/**
* Helper method to generate a record node reference.
*
* @return {@link NodeRef} node reference that will behave like a record or type cm:content
* @return {@link NodeRef} node reference that will behave like a record or type cm:content
*/
protected NodeRef generateRecord()
{
@@ -256,7 +295,8 @@ public class BaseUnitTest implements RecordsManagementModel, ContentModel
/**
* Helper method to setup a node reference as a file plan component.
*
* @param nodeRef {@link NodeRef} node reference that will now behave like a file plan component
* @param nodeRef
* {@link NodeRef} node reference that will now behave like a file plan component
*/
protected void setupAsFilePlanComponent(NodeRef nodeRef)
{
@@ -269,7 +309,7 @@ public class BaseUnitTest implements RecordsManagementModel, ContentModel
/**
* Helper method to generate a node reference.
*
* @return {@link NodeRef} node reference that behaves like a node that exists in the spaces store
* @return {@link NodeRef} node reference that behaves like a node that exists in the spaces store
*/
protected NodeRef generateNodeRef()
{
@@ -279,9 +319,9 @@ public class BaseUnitTest implements RecordsManagementModel, ContentModel
/**
* Helper method to generate a node reference of a particular type.
*
* @param type content type qualified name
* @return {@link NodeRef} node reference that behaves like a node that exists in the spaces store with
* the content type provided
* @param type
* content type qualified name
* @return {@link NodeRef} node reference that behaves like a node that exists in the spaces store with the content type provided
*/
protected NodeRef generateNodeRef(QName type)
{
@@ -291,8 +331,9 @@ public class BaseUnitTest implements RecordsManagementModel, ContentModel
/**
* Helper method to generate a cm:content node reference with a given name.
*
* @param name content name
* @return NodeRef node reference
* @param name
* content name
* @return NodeRef node reference
*/
protected NodeRef generateCmContent(String name)
{
@@ -304,10 +345,11 @@ public class BaseUnitTest implements RecordsManagementModel, ContentModel
/**
* Helper method to generate a node reference of a particular type with a given existence characteristic.
*
* @param type content type qualified name
* @param exists indicates whether this node should behave like a node that exists or not
* @return {@link NodeRef} node reference that behaves like a node that exists (or not) in the spaces store with
* the content type provided
* @param type
* content type qualified name
* @param exists
* indicates whether this node should behave like a node that exists or not
* @return {@link NodeRef} node reference that behaves like a node that exists (or not) in the spaces store with the content type provided
*/
protected NodeRef generateNodeRef(QName type, boolean exists)
{
@@ -324,9 +366,11 @@ public class BaseUnitTest implements RecordsManagementModel, ContentModel
/**
* Helper method to generate a mocked child association reference.
*
* @param parent parent node (optional)
* @param child child node (optional)
* @return {@link ChildAssociationRef} mocked to return the parent and child nodes
* @param parent
* parent node (optional)
* @param child
* child node (optional)
* @return {@link ChildAssociationRef} mocked to return the parent and child nodes
*/
protected ChildAssociationRef generateChildAssociationRef(NodeRef parent, NodeRef child)
{
@@ -357,17 +401,17 @@ public class BaseUnitTest implements RecordsManagementModel, ContentModel
{
makePrimaryParentOf(child, parent, ContentModel.ASSOC_CONTAINS, generateQName());
}
protected void makePrimaryParentOf(NodeRef child, NodeRef parent, QName assocType, QName assocName)
{
makePrimaryParentOf(child, parent, assocType, assocName, mockedNodeService);
makePrimaryParentOf(child, parent, assocType, assocName, mockedNodeService);
}
protected void makePrimaryParentOf(NodeRef child, NodeRef parent, QName assocType, QName assocName, NodeService mockedNodeService)
{
doReturn(new ChildAssociationRef(assocType, parent, assocName, child))
.when(mockedNodeService)
.getPrimaryParent(child);
.when(mockedNodeService)
.getPrimaryParent(child);
}
/**
@@ -378,7 +422,7 @@ public class BaseUnitTest implements RecordsManagementModel, ContentModel
* @param parent
* @param children
*/
protected void makeChildrenOf(NodeRef parent, NodeRef ... children)
protected void makeChildrenOf(NodeRef parent, NodeRef... children)
{
List<ChildAssociationRef> assocs = new ArrayList<>(children.length);
for (NodeRef child : children)
@@ -390,7 +434,7 @@ public class BaseUnitTest implements RecordsManagementModel, ContentModel
}
@SuppressWarnings("unchecked")
protected <T> List<T> buildList(T ... values)
protected <T> List<T> buildList(T... values)
{
List<T> result = new ArrayList<>(values.length);
for (T value : values)

View File

@@ -0,0 +1,405 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* 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.rm.rest.api.retentionschedule;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionActionDefinition;
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionActionDefinitionImpl;
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService;
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionServiceImpl;
import org.alfresco.module.org_alfresco_module_rm.disposition.property.DispositionProperty;
import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
import org.alfresco.module.org_alfresco_module_rm.test.util.BaseUnitTest;
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.rm.rest.api.impl.ApiNodesModelFactory;
import org.alfresco.rm.rest.api.impl.FilePlanComponentsApiUtils;
import org.alfresco.rm.rest.api.model.RetentionScheduleActionDefinition;
import org.alfresco.rm.rest.api.model.RetentionSteps;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.QName;
/**
* Unit tests for RetentionScheduleActionRelation
*/
public class RetentionScheduleActionRelationUnitTest extends BaseUnitTest
{
@Mock
private FilePlanComponentsApiUtils apiUtils;
@Mock
private ApiNodesModelFactory nodesModelFactory;
@Mock
private Parameters parameters;
private RetentionScheduleActionRelation retentionScheduleActionRelation;
private NodeRef rsRecordLevelNodeRef = new NodeRef("workspace://SpacesStore/recordLevel");
private NodeRef rsRecordFolderLevelNodeRef = new NodeRef("workspace://SpacesStore/recordFolderLevel");
@Before
public void setUp()
{
// Disposition Service
DispositionService dispositionService = new DispositionServiceImpl();
// Disposition Properties
DispositionProperty publicationDate = createDispositionProperty("dod:publicationDate", false, true, Set.of());
DispositionProperty cutoffDate = createDispositionProperty("rma:cutOffDate", true, true, Set.of("cutoff"));
DispositionProperty dispositionAsOf = createDispositionProperty("rma:dispositionAsOf", true, true, Set.of());
DispositionProperty dateFiled = createDispositionProperty("rma:dateFiled", false, true, Set.of());
DispositionProperty created = createDispositionProperty("cm:created", true, true, Set.of());
// Register Disposition Properties
dispositionService.registerDispositionProperty(publicationDate);
dispositionService.registerDispositionProperty(cutoffDate);
dispositionService.registerDispositionProperty(dispositionAsOf);
dispositionService.registerDispositionProperty(dateFiled);
dispositionService.registerDispositionProperty(created);
// Retention Schedule Action Relation
retentionScheduleActionRelation = new RetentionScheduleActionRelation();
retentionScheduleActionRelation.setApiUtils(apiUtils);
retentionScheduleActionRelation.setNodeService(mockedNodeService);
retentionScheduleActionRelation.setNodesModelFactory(nodesModelFactory);
retentionScheduleActionRelation.setDispositionService(dispositionService);
}
/**
* Create "cutoff" retention step for a retention schedule with "record" level disposition supplying only VALID disposition properties.
*
* <p>
* Valid: dod:publicationDate, rma:dispositionAsOf, rma:dateField and cm:created
* </p>
*/
@Test
public void testCreate_RetentionScheduleRecordLevel_Cutoff_Valid() throws Exception
{
// Retention schedule with "record" level disposition
String retentionScheduleId = useRetentionScheduleWithRecordLevel(true, false);
// Retention step action
String actionName = RetentionSteps.CUTOFF.stepName;
// Cutoff - dod:publicationDate
executeValidStep(retentionScheduleId, actionName, "dod:publicationDate");
// Cutoff - rma:dispositionAsOf
executeValidStep(retentionScheduleId, actionName, "rma:dispositionAsOf");
// Cutoff - rma:dateFiled
executeValidStep(retentionScheduleId, actionName, "rma:dateFiled");
// Cutoff - cm:created
executeValidStep(retentionScheduleId, actionName, "cm:created");
verify(mockedNodeService, times(4)).createNode(any(), any(), any(), any(), any());
}
/**
* Create "cutoff" retention step for a retention schedule with "record" level disposition supplying only INVALID disposition properties.
*
* <p>
* Invalid: rma:cutOffDate
* </p>
*/
@Test
public void testCreate_RetentionScheduleRecordLevel_Cutoff_Invalid() throws Exception
{
// Retention schedule with "record" level disposition
String retentionScheduleId = useRetentionScheduleWithRecordLevel(true, false);
// Retention step action
String actionName = RetentionSteps.CUTOFF.stepName;
// Cutoff - rma:cutOffDate
executeInvalidStep(retentionScheduleId, actionName, "rma:cutOffDate");
verify(mockedNodeService, never()).createNode(any(), any(), any(), any(), any());
}
/**
* Create "cutoff" retention step for a retention schedule with "record folder" level disposition supplying only VALID disposition properties.
*
* <p>
* Valid: rma:dispositionAsOf and cm:created
* </p>
*/
@Test
public void testCreate_RetentionScheduleRecordFolderLevel_Cutoff_Valid() throws Exception
{
// Retention schedule with "record folder" level disposition
String retentionScheduleId = useRetentionScheduleWithRecordLevel(false, false);
// Retention step action
String actionName = RetentionSteps.CUTOFF.stepName;
// Cutoff - rma:dispositionAsOf
executeValidStep(retentionScheduleId, actionName, "rma:dispositionAsOf");
// Cutoff - cm:created
executeValidStep(retentionScheduleId, actionName, "cm:created");
verify(mockedNodeService, times(2)).createNode(any(), any(), any(), any(), any());
}
/**
* Create "cutoff" retention step for a retention schedule with "record folder" level disposition supplying only INVALID disposition properties.
*
* <p>
* Invalid: dod:publicationDate, rma:cutOffDate and rma:dateFiled
* </p>
*/
@Test
public void testCreate_RetentionScheduleRecordFolderLevel_Cutoff_Invalid() throws Exception
{
// Retention schedule with "record folder" level disposition
String retentionScheduleId = useRetentionScheduleWithRecordLevel(false, false);
// Retention step action
String actionName = RetentionSteps.CUTOFF.stepName;
// Cutoff - dod:publicationDate
executeInvalidStep(retentionScheduleId, actionName, "dod:publicationDate");
// Cutoff - rma:cutOffDate
executeInvalidStep(retentionScheduleId, actionName, "rma:cutOffDate");
// Cutoff - rma:dateFiled
executeInvalidStep(retentionScheduleId, actionName, "rma:dateFiled");
verify(mockedNodeService, never()).createNode(any(), any(), any(), any(), any());
}
/**
* Create "transfer" retention step for a retention schedule with "record" level disposition supplying only VALID disposition properties.
*
* <p>
* Valid: dod:publicationDate, rma:cutOffDate, rma:dispositionAsOf, rma:dateField and cm:created
* </p>
*/
@Test
public void testCreate_RetentionScheduleRecordLevel_Transfer_Valid() throws Exception
{
// Retention schedule with "record" level disposition
String retentionScheduleId = useRetentionScheduleWithRecordLevel(true, true);
// Retention step action
String actionName = RetentionSteps.TRANSFER.stepName;
// Transfer - dod:publicationDate
executeValidStep(retentionScheduleId, actionName, "dod:publicationDate");
// Transfer - rma:cutOffDate
executeValidStep(retentionScheduleId, actionName, "rma:cutOffDate");
// Transfer - rma:dispositionAsOf
executeValidStep(retentionScheduleId, actionName, "rma:dispositionAsOf");
// Transfer - rma:dateFiled
executeValidStep(retentionScheduleId, actionName, "rma:dateFiled");
// Transfer - cm:created
executeValidStep(retentionScheduleId, actionName, "cm:created");
verify(mockedNodeService, times(5)).createNode(any(), any(), any(), any(), any());
}
/**
* Create "transfer" retention step for a retention schedule with "record" level disposition supplying only INVALID disposition properties.
*
* <p>
* Invalid: any other property that is not dod:publicationDate, rma:cutOffDate, rma:dispositionAsOf, rma:dateField and cm:created.
* </p>
*/
@Test
public void testCreate_RetentionScheduleRecordLevel_Transfer_Invalid() throws Exception
{
// Retention schedule with "record" level disposition
String retentionScheduleId = useRetentionScheduleWithRecordLevel(true, true);
// Retention step action
String actionName = RetentionSteps.TRANSFER.stepName;
// Transfer - bad:property
executeInvalidStep(retentionScheduleId, actionName, "bad:property");
verify(mockedNodeService, never()).createNode(any(), any(), any(), any(), any());
}
/**
* Create "transfer" retention step for a retention schedule with "record folder" level disposition supplying only VALID disposition properties.
*
* <p>
* Valid: rma:cutOffDate, rma:dispositionAsOf and cm:created
* </p>
*/
@Test
public void testCreate_RetentionScheduleRecordFolderLevel_Transfer_Valid() throws Exception
{
// Retention schedule with "record" level disposition
String retentionScheduleId = useRetentionScheduleWithRecordLevel(false, true);
// Retention step action
String actionName = RetentionSteps.TRANSFER.stepName;
// Transfer - rma:cutOffDate
executeValidStep(retentionScheduleId, actionName, "rma:cutOffDate");
// Transfer - rma:dispositionAsOf
executeValidStep(retentionScheduleId, actionName, "rma:dispositionAsOf");
// Transfer - cm:created
executeValidStep(retentionScheduleId, actionName, "cm:created");
verify(mockedNodeService, times(3)).createNode(any(), any(), any(), any(), any());
}
/**
* Create "transfer" retention step for a retention schedule with "record folder" level disposition supplying only INVALID disposition properties.
*
* <p>
* Invalid: dod:publicationDate and rma:dateFiled
* </p>
*/
@Test
public void testCreate_RetentionScheduleRecordFolderLevel_Transfer_Invalid() throws Exception
{
// Retention schedule with "record" level disposition
String retentionScheduleId = useRetentionScheduleWithRecordLevel(false, true);
// Retention step action
String actionName = RetentionSteps.TRANSFER.stepName;
// Transfer - dod:publicationDate
executeInvalidStep(retentionScheduleId, actionName, "dod:publicationDate");
// Transfer - rma:dateFiled
executeInvalidStep(retentionScheduleId, actionName, "rma:dateFiled");
verify(mockedNodeService, never()).createNode(any(), any(), any(), any(), any());
}
private void executeValidStep(String retentionScheduleId, String actionName, String periodProperty)
{
RetentionScheduleActionDefinition actionDef = createAction(actionName, periodProperty);
retentionScheduleActionRelation.create(retentionScheduleId, Arrays.asList(actionDef), parameters);
}
private void executeInvalidStep(String retentionScheduleId, String actionName, String periodProperty)
{
RetentionScheduleActionDefinition actionDef = createAction(actionName, periodProperty);
try
{
retentionScheduleActionRelation.create(retentionScheduleId, Arrays.asList(actionDef), parameters);
}
catch (InvalidArgumentException e)
{
assertTrue(e.getMessage().contains("periodProperty value is invalid: " + periodProperty));
}
}
private String useRetentionScheduleWithRecordLevel(Boolean withRecordLevel, boolean hasCompletedActions)
{
NodeRef retentionScheduleNodeRef = withRecordLevel ? rsRecordLevelNodeRef : rsRecordFolderLevelNodeRef;
String retentionScheduleId = retentionScheduleNodeRef.getId();
ChildAssociationRef retentionScheduleAssocRef = mock(ChildAssociationRef.class);
NodeRef cutOffActionNodeRef = mock(NodeRef.class);
DispositionActionDefinition cutoffAction = new DispositionActionDefinitionImpl(
mockedRecordsManagementEventService, mockedRecordsManagementActionService, mockedNodeService,
cutOffActionNodeRef, 0);
List<DispositionActionDefinition> completedActions = hasCompletedActions ? Arrays.asList(cutoffAction)
: Collections.emptyList();
when(retentionScheduleAssocRef.getChildRef()).thenReturn(new NodeRef("workspace://SpacesStore/123"));
when(apiUtils.lookupAndValidateNodeType(eq(retentionScheduleId), any(QName.class))).thenReturn(retentionScheduleNodeRef);
when(mockedNodeService.getProperty(retentionScheduleNodeRef, RecordsManagementModel.PROP_RECORD_LEVEL_DISPOSITION)).thenReturn(withRecordLevel);
when(nodesModelFactory.getRetentionActions(retentionScheduleNodeRef)).thenReturn(completedActions);
when(mockedNodeService.createNode(any(), any(), any(), any(), any())).thenReturn(retentionScheduleAssocRef);
return retentionScheduleId;
}
private RetentionScheduleActionDefinition createAction(String name, String periodProperty)
{
RetentionScheduleActionDefinition actionDef = mock(RetentionScheduleActionDefinition.class);
when(actionDef.getName()).thenReturn(name);
when(actionDef.getPeriodProperty()).thenReturn(periodProperty);
when(actionDef.getPeriodAmount()).thenReturn(2);
when(actionDef.getPeriod()).thenReturn("day");
when(actionDef.getEvents()).thenReturn(Collections.singletonList("versioned"));
when(actionDef.isCombineRetentionStepConditions()).thenReturn(false);
return actionDef;
}
private DispositionProperty createDispositionProperty(String name, Boolean appliesToFolderLevel,
Boolean appliesToRecordLevel, Set<String> excludedActions) {
when(mockedNamespaceService.getNamespaceURI(any())).thenReturn(name.split(":")[0]);
DispositionProperty dp = new DispositionProperty();
dp.setNamespaceService(mockedNamespaceService);
dp.setName(name);
dp.setAppliesToRecordLevel(appliesToRecordLevel);
dp.setAppliesToFolderLevel(appliesToFolderLevel);
dp.setExcludedDispositionActions(excludedActions);
return dp;
}
}

View File

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

View File

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

View File

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

View File

@@ -91,6 +91,15 @@ function doclist_getAllNodes(parsedArgs, filterParams, query, totalItemCount)
};
}
function sanitizeJunkFavouriteKeys(favourites){
for (var key in favourites) {
if (!key || key.trim() === "") {
delete favourites[key];
}
}
return favourites;
}
/**
* Main entry point: Create collection of documents and folders in the given space
*
@@ -123,6 +132,47 @@ function doclist_main()
if (logger.isLoggingEnabled())
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)
{
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()
};
}
var totalItemCount = filterParams.limitResults ? parseInt(filterParams.limitResults, 10) : -1;
// For all sites documentLibrary query we pull in all available results and post filter

View File

@@ -182,11 +182,14 @@ var Filters =
case "favourites":
for (var favourite in favourites)
{
if (filterQuery)
if (favourite && favourite.trim() !== "")
{
filterQuery += " OR ";
if (filterQuery)
{
filterQuery += " OR ";
}
filterQuery += "ID:\"" + favourite + "\"";
}
filterQuery += "ID:\"" + favourite + "\"";
}
if (filterQuery.length !== 0)
@@ -201,7 +204,13 @@ var Filters =
else
{
// empty favourites query
filterQuery = "+ID:\"\"";
logger.warn("No favourites found for user: " + person.properties.userName);
return {
query: null,
limitResults: 0,
sort: [],
language: "lucene"
};
}
filterParams.query = filterQuery;
@@ -224,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
@@ -240,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":
@@ -262,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>23.6.0.13</version>
<version>23.7.0.7</version>
</parent>
<dependencies>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,3 +1,3 @@
SOLR6_TAG=2.0.13
SOLR6_TAG=2.0.17
POSTGRES_TAG=15.4
ACTIVEMQ_TAG=5.18.3-jre17-rockylinux8

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

40
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>23.6.0.13</version>
<version>23.7.0.7</version>
<packaging>pom</packaging>
<name>Alfresco Community Repo Parent</name>
@@ -24,7 +24,7 @@
<properties>
<acs.version.major>23</acs.version.major>
<acs.version.minor>6</acs.version.minor>
<acs.version.minor>7</acs.version.minor>
<acs.version.revision>0</acs.version.revision>
<acs.version.label />
<amp.min.version>${acs.version.major}.0.0</amp.min.version>
@@ -51,17 +51,17 @@
<dependency.alfresco-server-root.version>7.0.1</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.1.5</dependency.alfresco-transform-core.version>
<dependency.alfresco-transform-service.version>4.1.5</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.0</dependency.alfresco-greenmail.version>
<dependency.acs-event-model.version>0.0.33</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.0</dependency.cxf.version>
<dependency.cxf.version>4.1.2</dependency.cxf.version>
<dependency.opencmis.version>1.0.0-jakarta-1</dependency.opencmis.version>
<dependency.webscripts.version>10.2</dependency.webscripts.version>
<dependency.bouncycastle.version>1.78.1</dependency.bouncycastle.version>
@@ -81,12 +81,12 @@
<dependency.slf4j.version>2.0.16</dependency.slf4j.version>
<dependency.log4j.version>2.23.1</dependency.log4j.version>
<dependency.groovy.version>3.0.22</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.1</dependency.poi.version>
<dependency.jboss.logging.version>3.5.0.Final</dependency.jboss.logging.version>
<dependency.camel.version>4.10.2</dependency.camel.version> <!-- when bumping this version, please keep track/sync with included netty.io dependencies -->
<dependency.netty.version>4.1.118.Final</dependency.netty.version> <!-- must be in sync with camels transitive dependencies, e.g.: netty-common -->
<dependency.camel.version>4.15.0</dependency.camel.version> <!-- when bumping this version, please keep track/sync with included netty.io dependencies -->
<dependency.netty.version>4.1.127.Final</dependency.netty.version> <!-- must be in sync with camels transitive dependencies, e.g.: netty-common -->
<dependency.activemq.version>5.18.3</dependency.activemq.version>
<dependency.apache-compress.version>1.27.1</dependency.apache-compress.version>
<dependency.awaitility.version>4.2.2</dependency.awaitility.version>
@@ -104,7 +104,7 @@
<dependency.jakarta-annotation-api.version>3.0.0</dependency.jakarta-annotation-api.version>
<dependency.jakarta-transaction-api.version>2.0.1</dependency.jakarta-transaction-api.version>
<dependency.jakarta-jws-api.version>3.0.0</dependency.jakarta-jws-api.version>
<dependency.jakarta-ee-mail.version>2.0.1</dependency.jakarta-ee-mail.version>
<dependency.jakarta-ee-mail.version>2.0.2</dependency.jakarta-ee-mail.version>
<dependency.jakarta-ee-activation.version>2.0.1</dependency.jakarta-ee-activation.version>
<dependency.jakarta-ee-jms.version>3.1.0</dependency.jakarta-ee-jms.version>
<dependency.java-ee-activation.version>1.2.0</dependency.java-ee-activation.version>
@@ -113,7 +113,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>23.4.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>23.6.0.13</tag>
<tag>23.7.0.7</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>
@@ -1132,16 +1138,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>23.6.0.13</version>
<version>23.7.0.7</version>
</parent>
<dependencies>

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2016 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
@@ -31,6 +31,14 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.json.simple.JSONObject;
import org.owasp.html.HtmlPolicyBuilder;
import org.owasp.html.PolicyFactory;
import org.springframework.extensions.webscripts.Cache;
import org.springframework.extensions.webscripts.Status;
import org.springframework.extensions.webscripts.WebScriptRequest;
import org.alfresco.model.ContentModel;
import org.alfresco.model.ForumModel;
import org.alfresco.repo.content.MimetypeMap;
@@ -44,10 +52,6 @@ import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.json.simple.JSONObject;
import org.springframework.extensions.webscripts.Cache;
import org.springframework.extensions.webscripts.Status;
import org.springframework.extensions.webscripts.WebScriptRequest;
/**
* This class is the controller for the comments.post web script.
@@ -58,7 +62,7 @@ import org.springframework.extensions.webscripts.WebScriptRequest;
public class CommentsPost extends AbstractCommentsWebScript
{
/**
* Overrides AbstractCommentsWebScript to add comment
* Overrides AbstractCommentsWebScript to add comment
*/
@Override
protected Map<String, Object> executeImpl(NodeRef nodeRef, WebScriptRequest req, Status status, Cache cache)
@@ -66,6 +70,29 @@ public class CommentsPost extends AbstractCommentsWebScript
// get json object from request
JSONObject json = parseJSON(req);
// Validating and Sanitizing comment content to prevent XSS
String commentContent = getOrNull(json, "content");
if (StringUtils.isBlank(commentContent))
{
throw new IllegalArgumentException("Comment content must not be empty");
}
else
{
// Allowed HTML elements and attributes in comment content e.g. Text formatting ,Lists and Structure & Styling
String[] allowedElements = {"b", "i", "u", "strong", "em", "ul", "ol", "li", "p", "br", "span", "div"};
PolicyFactory policy = new HtmlPolicyBuilder()
.allowElements(allowedElements)
.allowAttributes("style")
.onElements("span", "div", "p", "ul")
.allowStyling()
.allowStandardUrlProtocols()
.toFactory();
String safeContent = policy.sanitize(commentContent);
json.replace("content", safeContent);
}
/* MNT-10231, MNT-9771 fix */
this.behaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
@@ -99,32 +126,32 @@ public class CommentsPost extends AbstractCommentsWebScript
{
// fetch the parent to add the node to
NodeRef commentsFolder = getOrCreateCommentsFolder(nodeRef);
// get a unique name
String name = getUniqueChildName("comment");
// create the comment
NodeRef commentNodeRef = nodeService.createNode(commentsFolder,
ContentModel.ASSOC_CONTAINS,
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, QName.createValidLocalName(name)),
NodeRef commentNodeRef = nodeService.createNode(commentsFolder,
ContentModel.ASSOC_CONTAINS,
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, QName.createValidLocalName(name)),
ForumModel.TYPE_POST).getChildRef();
// fetch the title required to create a comment
String title = getOrNull(json, JSON_KEY_TITLE);
HashMap<QName, Serializable> props = new HashMap<QName, Serializable>(1, 1.0f);
props.put(ContentModel.PROP_TITLE, title != null ? title : "");
nodeService.addProperties(commentNodeRef, props);
ContentWriter writer = contentService.getWriter(commentNodeRef, ContentModel.PROP_CONTENT, true);
// fetch the content of a comment
String contentString = getOrNull(json, JSON_KEY_CONTENT);
writer.setMimetype(MimetypeMap.MIMETYPE_HTML);
writer.putContent(contentString);
return commentNodeRef;
}
/**
* generates an comment item value
*
@@ -134,34 +161,34 @@ public class CommentsPost extends AbstractCommentsWebScript
private Map<String, Object> generateItemValue(NodeRef commentNodeRef)
{
Map<String, Object> result = new HashMap<String, Object>(4, 1.0f);
String creator = (String)this.nodeService.getProperty(commentNodeRef, ContentModel.PROP_CREATOR);
String creator = (String) this.nodeService.getProperty(commentNodeRef, ContentModel.PROP_CREATOR);
Serializable created = this.nodeService.getProperty(commentNodeRef, ContentModel.PROP_CREATED);
Serializable modified = this.nodeService.getProperty(commentNodeRef, ContentModel.PROP_MODIFIED);
boolean isUpdated = false;
if (created instanceof Date && modified instanceof Date)
{
isUpdated = ((Date)modified).getTime() - ((Date)created).getTime() > 5000;
isUpdated = ((Date) modified).getTime() - ((Date) created).getTime() > 5000;
}
// TODO refactor v0 Comments API to use CommentService (see ACE-5437)
Serializable owner = this.nodeService.getProperty(commentNodeRef, ContentModel.PROP_OWNER);
String currentUser = this.serviceRegistry.getAuthenticationService().getCurrentUserName();
boolean isSiteManager = this.permissionService.hasPermission(commentNodeRef, SiteModel.SITE_MANAGER) == (AccessStatus.ALLOWED);
boolean isCoordinator = this.permissionService.hasPermission(commentNodeRef, PermissionService.COORDINATOR) == (AccessStatus.ALLOWED);
boolean canEditComment = isSiteManager || isCoordinator || currentUser.equals(creator) || currentUser.equals(owner);
result.put("node", commentNodeRef);
result.put("author", this.personService.getPerson(creator));
result.put("isUpdated", isUpdated);
result.put("canEditComment", canEditComment);
return result;
}
/**
* generates the response model for adding a comment
*
@@ -194,7 +221,7 @@ public class CommentsPost extends AbstractCommentsWebScript
}
return commentsFolder;
}
/**
* returns the nodeRef of the existing one
*
@@ -207,7 +234,7 @@ public class CommentsPost extends AbstractCommentsWebScript
{
List<ChildAssociationRef> assocs = nodeService.getChildAssocs(nodeRef, ForumModel.ASSOC_DISCUSSION, RegexQNamePattern.MATCH_ALL);
ChildAssociationRef firstAssoc = assocs.get(0);
return nodeService.getChildByName(firstAssoc.getChildRef(), ContentModel.ASSOC_CONTAINS, COMMENTS_TOPIC_NAME);
}
else
@@ -220,7 +247,7 @@ public class CommentsPost extends AbstractCommentsWebScript
{
return prefix + "-" + System.currentTimeMillis();
}
/**
* creates the comments folder if it does not exists
*
@@ -229,35 +256,34 @@ public class CommentsPost extends AbstractCommentsWebScript
*/
private NodeRef createCommentsFolder(final NodeRef nodeRef)
{
NodeRef commentsFolder = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<NodeRef>()
{
NodeRef commentsFolder = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<NodeRef>() {
public NodeRef doWork() throws Exception
{
NodeRef commentsFolder = null;
AuthenticationUtil.pushAuthentication();
// ALF-5240: turn off auditing round the discussion node creation to prevent
// the source document from being modified by the first user leaving a comment
behaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
try
{
{
// MNT-12082: set System user for creating forumFolder and commentsFolder nodes
AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getSystemUserName());
nodeService.addAspect(nodeRef, QName.createQName(NamespaceService.FORUMS_MODEL_1_0_URI, "discussable"), null);
nodeService.addAspect(nodeRef, QName.createQName(NamespaceService.FORUMS_MODEL_1_0_URI, "commentsRollup"), null);
List<ChildAssociationRef> assocs = nodeService.getChildAssocs(nodeRef, QName.createQName(NamespaceService.FORUMS_MODEL_1_0_URI, "discussion"), RegexQNamePattern.MATCH_ALL);
if (assocs.size() != 0)
{
NodeRef forumFolder = assocs.get(0).getChildRef();
Map<QName, Serializable> props = new HashMap<QName, Serializable>(1, 1.0f);
props.put(ContentModel.PROP_NAME, COMMENTS_TOPIC_NAME);
commentsFolder = nodeService.createNode(
forumFolder,
ContentModel.ASSOC_CONTAINS,
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, COMMENTS_TOPIC_NAME),
ContentModel.ASSOC_CONTAINS,
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, COMMENTS_TOPIC_NAME),
QName.createQName(NamespaceService.FORUMS_MODEL_1_0_URI, "topic"),
props).getChildRef();
}
@@ -267,12 +293,12 @@ public class CommentsPost extends AbstractCommentsWebScript
AuthenticationUtil.popAuthentication();
behaviourFilter.enableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
}
return commentsFolder;
}
}, AuthenticationUtil.getSystemUserName());
}, AuthenticationUtil.getSystemUserName());
return commentsFolder;
}

View File

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

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 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
@@ -23,105 +23,102 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.repo.domain.node;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.alfresco.model.ContentModel;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
import org.alfresco.service.namespace.QName;
/**
* Class holding properties associated with the <b>sys:referenceable</b> aspect.
* This aspect is common enough to warrant direct inclusion on the <b>Node</b> entity.
*
* @author Derek Hulley
* @since 3.4
*/
public class ReferenceablePropertiesEntity
{
private static final Set<QName> REFERENCEABLE_PROP_QNAMES;
static
{
REFERENCEABLE_PROP_QNAMES = new HashSet<QName>(8);
REFERENCEABLE_PROP_QNAMES.add(ContentModel.PROP_STORE_PROTOCOL);
REFERENCEABLE_PROP_QNAMES.add(ContentModel.PROP_STORE_IDENTIFIER);
REFERENCEABLE_PROP_QNAMES.add(ContentModel.PROP_NODE_UUID);
REFERENCEABLE_PROP_QNAMES.add(ContentModel.PROP_NODE_DBID);
}
/**
* @return Returns <tt>true</tt> if the property belongs to the <b>sys:referenceable</b> aspect
*/
public static boolean isReferenceableProperty(QName qname)
{
return REFERENCEABLE_PROP_QNAMES.contains(qname);
}
/**
* Remove all {@link ContentModel#ASPECT_REFERENCEABLE referencable} properties
*/
public static void removeReferenceableProperties(Node node, Map<QName, Serializable> properties)
{
properties.keySet().removeAll(REFERENCEABLE_PROP_QNAMES);
String name = DefaultTypeConverter.INSTANCE.convert(String.class, properties.get(ContentModel.PROP_NAME));
if (name != null && name.equals(node.getUuid()))
{
// The cm:name matches the UUID, so drop it
properties.remove(ContentModel.PROP_NAME);
}
}
/**
* Remove all {@link ContentModel#ASPECT_REFERENCEABLE referencable} properties
*/
public static void removeReferenceableProperties(Set<QName> propertyQNames)
{
propertyQNames.removeAll(REFERENCEABLE_PROP_QNAMES);
}
/**
* Adds all {@link ContentModel#ASPECT_REFERENCEABLE referencable} properties.
*/
public static void addReferenceableProperties(Node node, 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());
properties.put(ContentModel.PROP_NODE_DBID, nodeId);
// add the ID as the name, if required
String name = DefaultTypeConverter.INSTANCE.convert(String.class, properties.get(ContentModel.PROP_NAME));
if (name == null)
{
properties.put(ContentModel.PROP_NAME, nodeRef.getId());
}
}
public static Serializable getReferenceableProperty(Node node, QName qname)
{
NodeRef nodeRef = node.getNodeRef();
if (qname.equals(ContentModel.PROP_STORE_PROTOCOL))
{
return nodeRef.getStoreRef().getProtocol();
}
else if (qname.equals(ContentModel.PROP_STORE_IDENTIFIER))
{
return nodeRef.getStoreRef().getIdentifier();
}
else if (qname.equals(ContentModel.PROP_NODE_UUID))
{
return nodeRef.getId();
}
else if (qname.equals(ContentModel.PROP_NODE_DBID))
{
return node.getId();
}
throw new IllegalArgumentException("Not sys:referenceable property: " + qname);
}
}
package org.alfresco.repo.domain.node;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.alfresco.model.ContentModel;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
import org.alfresco.service.namespace.QName;
/**
* Class holding properties associated with the <b>sys:referenceable</b> aspect. This aspect is common enough to warrant direct inclusion on the <b>Node</b> entity.
*
* @author Derek Hulley
* @since 3.4
*/
public class ReferenceablePropertiesEntity
{
private static final Set<QName> REFERENCEABLE_PROP_QNAMES;
static
{
REFERENCEABLE_PROP_QNAMES = new HashSet<QName>(8);
REFERENCEABLE_PROP_QNAMES.add(ContentModel.PROP_STORE_PROTOCOL);
REFERENCEABLE_PROP_QNAMES.add(ContentModel.PROP_STORE_IDENTIFIER);
REFERENCEABLE_PROP_QNAMES.add(ContentModel.PROP_NODE_UUID);
REFERENCEABLE_PROP_QNAMES.add(ContentModel.PROP_NODE_DBID);
}
/**
* @return Returns <tt>true</tt> if the property belongs to the <b>sys:referenceable</b> aspect
*/
public static boolean isReferenceableProperty(QName qname)
{
return REFERENCEABLE_PROP_QNAMES.contains(qname);
}
/**
* Remove all {@link ContentModel#ASPECT_REFERENCEABLE referencable} properties
*/
public static void removeReferenceableProperties(Node node, Map<QName, Serializable> properties)
{
properties.keySet().removeAll(REFERENCEABLE_PROP_QNAMES);
String name = DefaultTypeConverter.INSTANCE.convert(String.class, properties.get(ContentModel.PROP_NAME));
if (name != null && name.equals(node.getUuid()))
{
// The cm:name matches the UUID, so drop it
properties.remove(ContentModel.PROP_NAME);
}
}
/**
* Remove all {@link ContentModel#ASPECT_REFERENCEABLE referencable} properties
*/
public static void removeReferenceableProperties(Set<QName> propertyQNames)
{
propertyQNames.removeAll(REFERENCEABLE_PROP_QNAMES);
}
/**
* Adds all {@link ContentModel#ASPECT_REFERENCEABLE referencable} properties.
*/
public static void addReferenceableProperties(Long nodeId, NodeRef nodeRef, Map<QName, Serializable> properties)
{
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());
properties.put(ContentModel.PROP_NODE_DBID, nodeId);
// add the ID as the name, if required
String name = DefaultTypeConverter.INSTANCE.convert(String.class, properties.get(ContentModel.PROP_NAME));
if (name == null)
{
properties.put(ContentModel.PROP_NAME, nodeRef.getId());
}
}
public static Serializable getReferenceableProperty(Node node, QName qname)
{
NodeRef nodeRef = node.getNodeRef();
if (qname.equals(ContentModel.PROP_STORE_PROTOCOL))
{
return nodeRef.getStoreRef().getProtocol();
}
else if (qname.equals(ContentModel.PROP_STORE_IDENTIFIER))
{
return nodeRef.getStoreRef().getIdentifier();
}
else if (qname.equals(ContentModel.PROP_NODE_UUID))
{
return nodeRef.getId();
}
else if (qname.equals(ContentModel.PROP_NODE_DBID))
{
return node.getId();
}
throw new IllegalArgumentException("Not sys:referenceable property: " + qname);
}
}

View File

@@ -116,7 +116,7 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl
private static final String SELECT_NODE_MAX_ID = "alfresco.node.select_NodeMaxId";
private static final String SELECT_NODE_INTERVAL_BY_TYPE = "alfresco.node.select_MinMaxNodeIdForNodeType";
private static final String SELECT_NODES_WITH_ASPECT_IDS = "alfresco.node.select_NodesWithAspectIds";
private static final String SELECT_NODES_WITH_ASPECT_IDS_LIMITED = "alfresco.node.select_NodesWithAspectIds_Limited";
private static final String SELECT_NODES_WITH_ASPECT_IDS_LIMITED = "alfresco.node.select.select_NodesWithAspectIds_Limited";
private static final String INSERT_NODE_ASSOC = "alfresco.node.insert.insert_NodeAssoc";
private static final String UPDATE_NODE_ASSOC = "alfresco.node.update_NodeAssoc";
private static final String DELETE_NODE_ASSOC = "alfresco.node.delete_NodeAssoc";

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
@@ -39,8 +39,10 @@ import org.alfresco.service.namespace.QName;
/**
* Encapsulates events occurred in a single transaction.
*
* @param <REF> entity (e.g. node, child association, peer association) reference type
* @param <RES> entity resource type
* @param <REF>
* entity (e.g. node, child association, peer association) reference type
* @param <RES>
* entity resource type
*/
public abstract class EventConsolidator<REF extends EntityRef, RES extends Resource>
{
@@ -90,23 +92,31 @@ public abstract class EventConsolidator<REF extends EntityRef, RES extends Resou
/**
* Builds and returns the {@link RepoEvent} instance.
*
* @param eventInfo the object holding the event information
* @param eventInfo
* the object holding the event information
* @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())
.setSource(eventInfo.getSource())
.setTime(eventInfo.getTimestamp())
.setType(eventType.getType())
.setData(eventData)
.setDataschema(EventJSONSchema.getSchemaV1(eventType))
.build();
builder.setId(eventInfo.getId())
.setSource(eventInfo.getSource())
.setTime(eventInfo.getTimestamp())
.setType(eventType.getType())
.setData(eventData)
.setDataschema(EventJSONSchema.getSchemaV1(eventType));
}
/**
@@ -114,9 +124,9 @@ public abstract class EventConsolidator<REF extends EntityRef, RES extends Resou
*/
protected DataAttributes<RES> buildEventData(EventInfo eventInfo, RES resource, EventType eventType)
{
return EventData.<RES>builder()
.setEventGroupId(eventInfo.getTxnId())
.setResource(resource)
.build();
return EventData.<RES> builder()
.setEventGroupId(eventInfo.getTxnId())
.setResource(resource)
.build();
}
}

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 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
@@ -125,6 +125,9 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess
/** Number of (bytecode) instructions that will trigger the observer */
private int observerInstructionCount = 100;
/** Flag to enable or disable scope cleaning at the end of each script execution */
private boolean cleanScope = true;
/** Custom context factory */
public static AlfrescoContextFactory contextFactory;
@@ -209,6 +212,15 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess
}
/**
* @param cleanScope
* true to enable scope cleaning at the end of each script execution - set to false to disable this feature.
*/
public void setCleanScope(boolean cleanScope)
{
this.cleanScope = cleanScope;
}
/**
* @see org.alfresco.service.cmr.repository.ScriptProcessor#reset()
*/
public void reset()
@@ -614,7 +626,7 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess
}
finally
{
if (!secure)
if (!secure && cleanScope)
{
unsetScope(model, scope);
}

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 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
@@ -23,239 +23,290 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
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.NodePropertyEntity;
/**
* Filterable/Sortable Node Entity
*
* Can be optionally filtered/sorted by (up to) three properties - note: sort properties are applied in order
*
* @author jan
* @since 4.0
*/
public class FilterSortNodeEntity
{
private Long id; // node id
private NodeEntity node;
private NodePropertyEntity prop1;
private NodePropertyEntity prop2;
private NodePropertyEntity prop3;
// Supplemental query-related parameters
private Long parentNodeId;
private Long prop1qnameId;
private Long prop2qnameId;
private Long prop3qnameId;
private List<Long> childNodeTypeQNameIds;
private Set<Long> assocTypeQNameIds;
private String pattern;
private Long namePropertyQNameId;
private boolean auditableProps;
private boolean nodeType;
private Boolean isPrimary;
/**
* Default constructor
*/
public FilterSortNodeEntity()
{
auditableProps = false;
}
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
public String getPattern()
{
return pattern;
}
protected String escape(String s, char escapeChar)
{
StringBuilder sb = new StringBuilder();
int idx = -1;
int offset = 0;
do
{
idx = s.indexOf(escapeChar, offset);
if(idx != -1)
{
sb.append(s.substring(offset, idx));
sb.append("\\");
sb.append(escapeChar);
offset = idx + 1;
}
}
while(idx != -1);
sb.append(s.substring(offset));
return sb.toString();
}
public void setPattern(String pattern)
{
if(pattern != null)
{
// escape the '%' character with '\' (standard SQL escape character)
pattern = escape(pattern, '%');
// replace the wildcard character '*' with the one used in database queries i.e. '%'
this.pattern = pattern.replace('*', '%');
}
}
public void setAssocTypeQNameIds(Set<Long> assocTypeQNameIds)
{
this.assocTypeQNameIds = assocTypeQNameIds;
}
public Set<Long> getAssocTypeQNameIds()
{
return assocTypeQNameIds;
}
public Long getNamePropertyQNameId()
{
return namePropertyQNameId;
}
public void setNamePropertyQNameId(Long namePropertyQNameId)
{
this.namePropertyQNameId = namePropertyQNameId;
}
public NodePropertyEntity getProp1()
{
return prop1;
}
public void setProp1(NodePropertyEntity prop1)
{
this.prop1 = prop1;
}
public NodePropertyEntity getProp2()
{
return prop2;
}
public void setProp2(NodePropertyEntity prop2)
{
this.prop2 = prop2;
}
public NodePropertyEntity getProp3()
{
return prop3;
}
public void setProp3(NodePropertyEntity prop3)
{
this.prop3 = prop3;
}
public NodeEntity getNode()
{
return node;
}
public void setNode(NodeEntity childNode)
{
this.node = childNode;
}
// Supplemental query-related parameters
public Long getParentNodeId()
{
return parentNodeId;
}
public void setParentNodeId(Long parentNodeId)
{
this.parentNodeId = parentNodeId;
}
public Long getProp1qnameId()
{
return prop1qnameId;
}
public void setProp1qnameId(Long prop1qnameId)
{
this.prop1qnameId = prop1qnameId;
}
public Long getProp2qnameId()
{
return prop2qnameId;
}
public void setProp2qnameId(Long prop2qnameId)
{
this.prop2qnameId = prop2qnameId;
}
public Long getProp3qnameId()
{
return prop3qnameId;
}
public void setProp3qnameId(Long prop3qnameId)
{
this.prop3qnameId = prop3qnameId;
}
public List<Long> getChildNodeTypeQNameIds()
{
return childNodeTypeQNameIds;
}
public void setChildNodeTypeQNameIds(List<Long> childNodeTypeQNameIds)
{
this.childNodeTypeQNameIds = childNodeTypeQNameIds;
}
public boolean isAuditableProps()
{
return auditableProps;
}
public void setAuditableProps(boolean auditableProps)
{
this.auditableProps = auditableProps;
}
public boolean isNodeType()
{
return nodeType;
}
public void setNodeType(boolean nodeType)
{
this.nodeType = nodeType;
}
public Boolean isPrimary()
{
return isPrimary;
}
public void setIsPrimary(Boolean isPrimary)
{
this.isPrimary = isPrimary;
}
}
package org.alfresco.repo.node.getchildren;
import java.util.List;
import java.util.Set;
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
*
* Can be optionally filtered/sorted by (up to) three properties - note: sort properties are applied in order
*
* @author jan
* @since 4.0
*/
public class FilterSortNodeEntity
{
private Long id; // node id
private String nodeUuid;
private Long typeQNameId;
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;
private Long prop2qnameId;
private Long prop3qnameId;
private List<Long> childNodeTypeQNameIds;
private Set<Long> assocTypeQNameIds;
private String pattern;
private Long namePropertyQNameId;
private boolean auditableProps;
private boolean nodeType;
private Boolean isPrimary;
/**
* Default constructor
*/
public FilterSortNodeEntity()
{
auditableProps = false;
}
public Long getId()
{
return id;
}
public void setId(Long id)
{
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;
}
protected String escape(String s, char escapeChar)
{
StringBuilder sb = new StringBuilder();
int idx = -1;
int offset = 0;
do
{
idx = s.indexOf(escapeChar, offset);
if (idx != -1)
{
sb.append(s.substring(offset, idx));
sb.append("\\");
sb.append(escapeChar);
offset = idx + 1;
}
} while (idx != -1);
sb.append(s.substring(offset));
return sb.toString();
}
public void setPattern(String pattern)
{
if (pattern != null)
{
// escape the '%' character with '\' (standard SQL escape character)
pattern = escape(pattern, '%');
// replace the wildcard character '*' with the one used in database queries i.e. '%'
this.pattern = pattern.replace('*', '%');
}
}
public void setAssocTypeQNameIds(Set<Long> assocTypeQNameIds)
{
this.assocTypeQNameIds = assocTypeQNameIds;
}
public Set<Long> getAssocTypeQNameIds()
{
return assocTypeQNameIds;
}
public Long getNamePropertyQNameId()
{
return namePropertyQNameId;
}
public void setNamePropertyQNameId(Long namePropertyQNameId)
{
this.namePropertyQNameId = namePropertyQNameId;
}
public AuditablePropertiesEntity getAuditablePropertiesEntity()
{
return auditablePropertiesEntity;
}
public void setAuditablePropertiesEntity(AuditablePropertiesEntity auditablePropertiesEntity)
{
this.auditablePropertiesEntity = auditablePropertiesEntity;
}
public NodePropertyEntity getProp1()
{
return prop1;
}
public void setProp1(NodePropertyEntity prop1)
{
this.prop1 = prop1;
}
public NodePropertyEntity getProp2()
{
return prop2;
}
public void setProp2(NodePropertyEntity prop2)
{
this.prop2 = prop2;
}
public NodePropertyEntity getProp3()
{
return prop3;
}
public void setProp3(NodePropertyEntity prop3)
{
this.prop3 = prop3;
}
public String getStoreProtocol()
{
return storeProtocol;
}
public void setStoreProtocol(String storeProtocol)
{
this.storeProtocol = storeProtocol;
}
public String getStoreIdentifier()
{
return storeIdentifier;
}
public void setStoreIdentifier(String storeIdentifier)
{
this.storeIdentifier = storeIdentifier;
}
// Supplemental query-related parameters
public Long getParentNodeId()
{
return parentNodeId;
}
public void setParentNodeId(Long parentNodeId)
{
this.parentNodeId = parentNodeId;
}
public Long getProp1qnameId()
{
return prop1qnameId;
}
public void setProp1qnameId(Long prop1qnameId)
{
this.prop1qnameId = prop1qnameId;
}
public Long getProp2qnameId()
{
return prop2qnameId;
}
public void setProp2qnameId(Long prop2qnameId)
{
this.prop2qnameId = prop2qnameId;
}
public Long getProp3qnameId()
{
return prop3qnameId;
}
public void setProp3qnameId(Long prop3qnameId)
{
this.prop3qnameId = prop3qnameId;
}
public List<Long> getChildNodeTypeQNameIds()
{
return childNodeTypeQNameIds;
}
public void setChildNodeTypeQNameIds(List<Long> childNodeTypeQNameIds)
{
this.childNodeTypeQNameIds = childNodeTypeQNameIds;
}
public boolean isAuditableProps()
{
return auditableProps;
}
public void setAuditableProps(boolean auditableProps)
{
this.auditableProps = auditableProps;
}
public boolean isNodeType()
{
return nodeType;
}
public void setNodeType(boolean nodeType)
{
this.nodeType = nodeType;
}
public Boolean isPrimary()
{
return isPrimary;
}
public void setIsPrimary(Boolean isPrimary)
{
this.isPrimary = isPrimary;
}
public NodeRef createNodeRef()
{
return new NodeRef(new StoreRef(storeProtocol, storeIdentifier), nodeUuid);
}
}

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,8 +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>
@@ -782,25 +791,6 @@
<if test="ordered == true">order by node.id ASC</if>
</select>
<select id="select_NodesWithAspectIds_Limited" parameterType="Ids" resultMap="result_NodeRef" >
select
node.id as id,
store.protocol as protocol,
store.identifier as identifier,
node.uuid as uuid
from
alf_node_aspects na
join alf_node node on (na.node_id = node.id)
left join alf_store store on (store.id = node.store_id)
where
<![CDATA[na.node_id >= #{idOne}]]>
<if test="idTwo != null"><![CDATA[and na.node_id < #{idTwo}]]></if>
and na.qname_id in
<foreach item="item" index="i" collection="ids" open="(" separator="," close=")">#{item}</foreach>
<if test="ordered == true">order by node.id ASC</if>
<if test="maxResults != null"><![CDATA[limit #{maxResults}]]></if>
</select>
<!-- Common results for result_NodeAssoc -->
<sql id="select_NodeAssoc_Results">
select
@@ -991,8 +981,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,
@@ -1008,7 +998,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,
@@ -1086,9 +1076,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) -->
@@ -1566,4 +1553,4 @@
</foreach>
</delete>
</mapper>
</mapper>

View File

@@ -30,4 +30,23 @@
<![CDATA[and commit_time_ms <= #{maxCommitTime}]]>
</select>
</mapper>
<select id="select_NodesWithAspectIds_Limited" parameterType="Ids" resultMap="alfresco.node.result_NodeRef" >
select
node.id as id,
store.protocol as protocol,
store.identifier as identifier,
node.uuid as uuid
from
alf_node_aspects na
join alf_node node on (na.node_id = node.id)
left join alf_store store on (store.id = node.store_id)
where
<![CDATA[na.node_id >= #{idOne}]]>
<if test="idTwo != null"><![CDATA[and na.node_id < #{idTwo}]]></if>
and na.qname_id in
<foreach item="item" index="i" collection="ids" open="(" separator="," close=")">#{item}</foreach>
<if test="ordered == true">order by node.id ASC</if>
<if test="maxResults != null"><![CDATA[limit #{maxResults}]]></if>
</select>
</mapper>

View File

@@ -30,4 +30,23 @@
<![CDATA[and commit_time_ms <= #{maxCommitTime}]]>
</select>
</mapper>
<select id="select_NodesWithAspectIds_Limited" parameterType="Ids" resultMap="alfresco.node.result_NodeRef" >
select
node.id as id,
store.protocol as protocol,
store.identifier as identifier,
node.uuid as uuid
from
alf_node_aspects na
join alf_node node on (na.node_id = node.id)
left join alf_store store on (store.id = node.store_id)
where
<![CDATA[na.node_id >= #{idOne}]]>
<if test="idTwo != null"><![CDATA[and na.node_id < #{idTwo}]]></if>
and na.qname_id in
<foreach item="item" index="i" collection="ids" open="(" separator="," close=")">#{item}</foreach>
<if test="ordered == true">order by node.id ASC</if>
<if test="maxResults != null"><![CDATA[limit #{maxResults}]]></if>
</select>
</mapper>

View File

@@ -3,7 +3,7 @@
repository.name=Main Repository
# Schema number
version.schema=19500
version.schema=19600
# Directory configuration
@@ -1394,6 +1394,9 @@ scripts.execution.maxMemoryUsedInBytes=-1
# Number of instructions that will trigger the observer
scripts.execution.observerInstructionCount=5000
# Flag to control if the scope is cleaned at the end of script execution
scripts.execution.clean.scope=true
# Default value being used in POST/size-details endpoint to partition a huge folder into smaller chunks
# so that we can compute more efficiently and consolidate all sizes into a single unit.
default.async.folder.items=1000

View File

@@ -60,6 +60,9 @@
<property name="observerInstructionCount">
<value>${scripts.execution.observerInstructionCount}</value>
</property>
<property name="cleanScope">
<value>${scripts.execution.clean.scope}</value>
</property>
</bean>
<!-- base config implementation that script extension beans extend from - for auto registration