mirror of
				https://github.com/Alfresco/alfresco-community-repo.git
				synced 2025-10-29 15:21:53 +00:00 
			
		
		
		
	Compare commits
	
		
			90 Commits
		
	
	
		
			20.11
			...
			dev-swapni
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | ba7be5999b | ||
|  | 8503b2c96c | ||
|  | f43806b9f4 | ||
|  | 1eebb8ec12 | ||
|  | 008b340851 | ||
|  | 6d3e249149 | ||
|  | bf276c60d6 | ||
|  | 54be23513a | ||
|  | fa0fdff8d4 | ||
|  | c37b26e678 | ||
|  | b5e13e253a | ||
|  | 5b5164420f | ||
|  | 17c09efb93 | ||
|  | f96304bd28 | ||
|  | 499f679c8c | ||
|  | 80c6a0127d | ||
|  | 74e44acb1c | ||
|  | f4d66debea | ||
|  | af838043c9 | ||
|  | 1245647a9f | ||
|  | fe35f312bb | ||
|  | a5f16f1b11 | ||
|  | 38a9f6d3e1 | ||
|  | dc512e5ab0 | ||
|  | 5c8db99231 | ||
|  | e39a97ed8d | ||
|  | ad0ad081c6 | ||
|  | 8b9513ca8f | ||
|  | 5b3582c051 | ||
|  | ccba1768bd | ||
|  | fde8ff084b | ||
|  | 93a7790d44 | ||
|  | f03790e278 | ||
|  | 2c979ea71e | ||
|  | 227c74a8bd | ||
|  | 24aa64c165 | ||
|  | 183873166c | ||
|  | 6e4bb33fb1 | ||
|  | ae37c9273f | ||
|  | 783efca1d2 | ||
|  | 1c9419d635 | ||
|  | 03f80ace94 | ||
|  | e2a62cf315 | ||
|  | 1cd7253f76 | ||
|  | 17b04d7321 | ||
|  | fe5faa3263 | ||
|  | 85e2d84add | ||
|  | 7e7d86aca8 | ||
|  | 6eab9d2cfb | ||
|  | 5588cc45a2 | ||
|  | b32da2af76 | ||
|  | ee912bf126 | ||
|  | 38c638b0ce | ||
|  | db2982ea62 | ||
|  | 71454395cb | ||
|  | 9059d5a87b | ||
|  | 71f8784a7e | ||
|  | 088d8f9448 | ||
|  | 0d43100018 | ||
|  | 93800b6906 | ||
|  | 82f8b938a6 | ||
|  | 8727ee18e8 | ||
|  | a688d475dc | ||
|  | 83b1c323de | ||
|  | e063e1d816 | ||
|  | 65c62c7d03 | ||
|  | 7a6949a059 | ||
|  | 1a0e45b6c8 | ||
|  | 1d1dead902 | ||
|  | 6a69b3fd86 | ||
|  | f391cfa38c | ||
|  | fbef1156a8 | ||
|  | 5d93c2efd4 | ||
|  | c94807cd8c | ||
|  | 63b0ff8cf4 | ||
|  | 15c99b0c10 | ||
|  | 0f24f453c8 | ||
|  | 03ce7e37d4 | ||
|  | 1a5740eec1 | ||
|  | f55602842d | ||
|  | 7819e29bcc | ||
|  | 91031ca72a | ||
|  | 32314480a1 | ||
|  | f49b7a393f | ||
|  | d32ba12405 | ||
|  | 7430c80a41 | ||
|  | af2c11063b | ||
|  | 0d74540b2b | ||
|  | d68ceb1a4b | ||
|  | 379f65db0d | 
| @@ -7,7 +7,7 @@ | ||||
|    <parent> | ||||
|       <groupId>org.alfresco</groupId> | ||||
|       <artifactId>alfresco-community-repo-amps</artifactId> | ||||
|       <version>20.11</version> | ||||
|       <version>20.28-SNAPSHOT</version> | ||||
|    </parent> | ||||
|  | ||||
|    <modules> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|    <parent> | ||||
|       <groupId>org.alfresco</groupId> | ||||
|       <artifactId>alfresco-governance-services-community-parent</artifactId> | ||||
|       <version>20.11</version> | ||||
|       <version>20.28-SNAPSHOT</version> | ||||
|    </parent> | ||||
|  | ||||
|    <modules> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|    <parent> | ||||
|       <groupId>org.alfresco</groupId> | ||||
|       <artifactId>alfresco-governance-services-automation-community-repo</artifactId> | ||||
|       <version>20.11</version> | ||||
|       <version>20.28-SNAPSHOT</version> | ||||
|    </parent> | ||||
|  | ||||
|    <build> | ||||
|   | ||||
| @@ -0,0 +1,118 @@ | ||||
| /* | ||||
|  * #%L | ||||
|  * Alfresco Records Management Module | ||||
|  * %% | ||||
|  * Copyright (C) 2005 - 2022 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.rest.rm.community.rules; | ||||
|  | ||||
| import org.alfresco.rest.rm.community.base.BaseRMRestTest; | ||||
| import org.alfresco.rest.rm.community.model.record.Record; | ||||
| import org.alfresco.rest.rm.community.model.recordcategory.RecordCategory; | ||||
| import org.alfresco.rest.rm.community.model.recordcategory.RecordCategoryChild; | ||||
| import org.alfresco.rest.rm.community.model.rules.ActionsOnRule; | ||||
| import org.alfresco.rest.rm.community.model.rules.RuleDefinition; | ||||
| import org.alfresco.rest.rm.community.requests.gscore.api.RecordCategoryAPI; | ||||
| import org.alfresco.rest.rm.community.requests.gscore.api.RecordFolderAPI; | ||||
| import org.alfresco.rest.rm.community.requests.gscore.api.RecordsAPI; | ||||
| import org.alfresco.rest.rm.community.smoke.CreateCategoriesTests; | ||||
| import org.alfresco.rest.v0.RulesAPI; | ||||
| import org.alfresco.test.AlfrescoTest; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.testng.annotations.Test; | ||||
|  | ||||
| import java.util.Collections; | ||||
|  | ||||
| import static org.alfresco.rest.core.v0.BaseAPI.NODE_PREFIX; | ||||
| import static org.alfresco.rest.rm.community.base.TestData.ELECTRONIC_RECORD_NAME; | ||||
| import static org.alfresco.rest.rm.community.base.TestData.NONELECTRONIC_RECORD_NAME; | ||||
| import static org.alfresco.rest.rm.community.util.CommonTestUtils.generateTestPrefix; | ||||
| import static org.alfresco.rest.rm.community.utils.CoreUtil.createBodyForMoveCopy; | ||||
| import static org.alfresco.rest.rm.community.utils.CoreUtil.toContentModel; | ||||
| import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.*; | ||||
| import static org.alfresco.utility.data.RandomData.getRandomName; | ||||
| import static org.alfresco.utility.report.log.Step.STEP; | ||||
| import static org.springframework.http.HttpStatus.*; | ||||
|  | ||||
| public class CopyToRuleOnFoldersTest extends BaseRMRestTest { | ||||
|  | ||||
|     private RecordCategory category; | ||||
|     private RecordCategoryChild folder1,folder2; | ||||
|     private final static String title = "Run in background"; | ||||
|     private final String TEST_PREFIX = generateTestPrefix(CopyToRuleOnFoldersTest.class); | ||||
|     private final String RM_ADMIN = TEST_PREFIX + "rm_admin"; | ||||
|     private final String electronicRecord = TEST_PREFIX + "record_electronic_for_copyTo"; | ||||
|     private final String nonElectronicRecord = TEST_PREFIX + "record_non_electronic_for_copyTo"; | ||||
|  | ||||
|     @Autowired | ||||
|     private RulesAPI rulesAPI; | ||||
|  | ||||
|     @Test | ||||
|     @AlfrescoTest(jira = "RM-2994") | ||||
|     public void copyToRuleOnFoldersTest() | ||||
|     { | ||||
|  | ||||
|         RuleDefinition ruleDefinition = RuleDefinition.createNewRule().title("name").description("description1") | ||||
|             .runInBackground(true).title(title) | ||||
|             .actions(Collections.singletonList(ActionsOnRule.COPY_TO.getActionValue())); | ||||
|  | ||||
|  | ||||
|         STEP("Create the RM site if doesn't exist"); | ||||
|         createRMSiteIfNotExists(); | ||||
|  | ||||
|         STEP("Create record categories and record folders"); | ||||
|         category= createRootCategory(getRandomName("recordCategory")); | ||||
|         String folder1 = createCategoryFolderInFilePlan().getId(); | ||||
|         String folder2 = createCategoryFolderInFilePlan().getId(); | ||||
|  | ||||
|         // create a rule on folder | ||||
|         rulesAPI.createRule(getAdminUser().getUsername(), getAdminUser().getPassword(), NODE_PREFIX + folder1, ruleDefinition); | ||||
|  | ||||
|         // create electronic record in record folder | ||||
|         String electronicRecordId = createElectronicRecord(folder1, ELECTRONIC_RECORD_NAME).getId(); | ||||
|         assertStatusCode(CREATED); | ||||
|  | ||||
|         // create non-electronic record in record folder | ||||
|         String nonElectronicRecord = createElectronicRecord(folder1, NONELECTRONIC_RECORD_NAME).getId(); | ||||
|         assertStatusCode(CREATED); | ||||
|  | ||||
|         // Move the electronic and non-electronic records from "Category with records"> "Folder with rule" | ||||
|         // to "Copy Category with records" > "Folder with rule" | ||||
|        getRestAPIFactory().getNodeAPI(toContentModel(folder1)).copy(createBodyForMoveCopy(category.getId())); | ||||
|         getRestAPIFactory().getNodeAPI(toContentModel( electronicRecord)).move(createBodyForMoveCopy(folder2)); | ||||
|         getRestAPIFactory().getNodeAPI(toContentModel( nonElectronicRecord)).move(createBodyForMoveCopy(folder2)); | ||||
|  | ||||
|         RecordsAPI recordsAPI = getRestAPIFactory().getRecordsAPI(); | ||||
|         // Delete the record category | ||||
|         RecordCategoryAPI recordCategoryAPI = getRestAPIFactory().getRecordCategoryAPI(); | ||||
|         String recordCategoryId = category.getId(); | ||||
|         recordCategoryAPI.deleteRecordCategory(recordCategoryId); | ||||
|         recordsAPI.deleteRecord(electronicRecord); | ||||
|         recordsAPI.deleteRecord(nonElectronicRecord); | ||||
|         assertStatusCode(NO_CONTENT); | ||||
|  | ||||
|  | ||||
|     } | ||||
|  | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,229 @@ | ||||
| /* | ||||
|  * #%L | ||||
|  * Alfresco Records Management Module | ||||
|  * %% | ||||
|  * Copyright (C) 2005 - 2022 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.rest.rm.community.rules; | ||||
|  | ||||
| import org.alfresco.dataprep.CMISUtil; | ||||
| import org.alfresco.rest.rm.community.base.BaseRMRestTest; | ||||
| import org.alfresco.rest.rm.community.model.recordcategory.RecordCategory; | ||||
| import org.alfresco.rest.rm.community.model.recordcategory.RecordCategoryChild; | ||||
| import org.alfresco.rest.rm.community.model.rules.ActionsOnRule; | ||||
| import org.alfresco.rest.rm.community.model.rules.RuleDefinition; | ||||
| import org.alfresco.rest.rm.community.model.unfiledcontainer.UnfiledContainerChildEntry; | ||||
| import org.alfresco.rest.rm.community.model.user.UserRoles; | ||||
|  | ||||
| import org.alfresco.rest.rm.community.requests.gscore.api.UnfiledContainerAPI; | ||||
| import org.alfresco.rest.rm.community.smoke.FileAsRecordTests; | ||||
| import org.alfresco.rest.v0.RulesAPI; | ||||
| import org.alfresco.rest.v0.service.RoleService; | ||||
| import org.alfresco.test.AlfrescoTest; | ||||
| import org.alfresco.utility.model.FileModel; | ||||
| import org.alfresco.utility.model.FolderModel; | ||||
| import org.alfresco.utility.model.UserModel; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.testng.annotations.AfterClass; | ||||
| import org.testng.annotations.BeforeClass; | ||||
| import org.testng.annotations.Test; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.stream.Collectors; | ||||
| import static org.alfresco.rest.core.v0.BaseAPI.NODE_PREFIX; | ||||
| import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.FILE_PLAN_ALIAS; | ||||
| import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.UNFILED_RECORDS_CONTAINER_ALIAS; | ||||
| import static org.alfresco.rest.rm.community.model.user.UserPermissions.PERMISSION_FILING; | ||||
| import static org.alfresco.rest.rm.community.util.CommonTestUtils.generateTestPrefix; | ||||
| import static org.alfresco.utility.data.RandomData.getRandomName; | ||||
| import static org.alfresco.utility.report.log.Step.STEP; | ||||
| import static org.springframework.http.HttpStatus.CREATED; | ||||
|  | ||||
| @AlfrescoTest (jira = "APPS-36") | ||||
| public class FileAsRecordRuleTests extends BaseRMRestTest | ||||
| { | ||||
|     private UserModel nonRMUser, rmManager; | ||||
|     private RecordCategory category_manager, category_admin; | ||||
|     private RecordCategoryChild folder_admin, folder_manager ; | ||||
|     private static final String CATEGORY_MANAGER = "catManager" + generateTestPrefix(FileAsRecordTests.class); | ||||
|     private static final String CATEGORY_ADMIN = "catAdmin" + generateTestPrefix(FileAsRecordTests.class); | ||||
|     private static final String FOLDER_MANAGER = "recordFolder" + generateTestPrefix(FileAsRecordTests.class); | ||||
|     private static final String FOLDER_ADMIN = "recordFolder" + generateTestPrefix(FileAsRecordTests.class); | ||||
|     private FolderModel testFolder; | ||||
|     private FileModel document,inPlaceRecord; | ||||
|  | ||||
|  | ||||
|     @Autowired | ||||
|     private RoleService roleService; | ||||
|     @Autowired | ||||
|     private RulesAPI rulesAPI; | ||||
|  | ||||
|     /** | ||||
|      * Create preconditions: | ||||
|      * 1. RM site is created | ||||
|      * 2. Two users: user without RM role and a user with RM manager role | ||||
|      * 3. Two Record categories with one folder each | ||||
|      * 4. User with RM MANAGER role has Filling permission over one category | ||||
|      * 5. A collaboration folder with rule set to declare and file as record to a record folder | ||||
|      **/ | ||||
|     @BeforeClass(alwaysRun = true) | ||||
|     public void preconditionForDeclareFileAsRecordRuleTests() | ||||
|     { | ||||
|         STEP("Create the RM site if doesn't exist"); | ||||
|         createRMSiteIfNotExists(); | ||||
|  | ||||
|         STEP("Create a user"); | ||||
|         nonRMUser = dataUser.createRandomTestUser("testUser"); | ||||
|  | ||||
|         STEP("Create a collaboration site"); | ||||
|         testSite = dataSite.usingUser(nonRMUser).createPublicRandomSite(); | ||||
|  | ||||
|         STEP("Create two categories with two folders"); | ||||
|         category_manager = createRootCategory(CATEGORY_MANAGER); | ||||
|         category_admin = createRootCategory(CATEGORY_ADMIN); | ||||
|         folder_admin = createFolder(category_admin.getId(),FOLDER_ADMIN); | ||||
|         folder_manager = createFolder(category_manager.getId(),FOLDER_MANAGER); | ||||
|  | ||||
|         STEP("Create an rm user and give filling permission over CATEGORY_MANAGER record category"); | ||||
|         RecordCategory recordCategory = new RecordCategory().builder() | ||||
|             .id(category_manager.getId()).build(); | ||||
|  | ||||
|         rmManager = roleService.createCollaboratorWithRMRoleAndPermission(testSite, recordCategory, | ||||
|             UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING); | ||||
|  | ||||
|         STEP("Create a collaboration folder with a rule set to declare and file as record to a record folder"); | ||||
|         RecordCategoryChild folderWithRule = createFolder(recordCategory.getId(), getRandomName("recordFolder")); | ||||
|         RuleDefinition ruleDefinition = RuleDefinition.createNewRule().title("name").description("description") | ||||
|             .applyToChildren(true) | ||||
|             .actions(Collections.singletonList(ActionsOnRule.DECLARE_AS_RECORD.getActionValue())); | ||||
|         rulesAPI.createRule(getAdminUser().getUsername(), getAdminUser().getPassword(), NODE_PREFIX + folderWithRule.getId(), ruleDefinition); | ||||
|  | ||||
|         assertStatusCode(CREATED); | ||||
|     } | ||||
|     /** | ||||
|      * Given I am a user that can create a rule on a folder in a collaboration site | ||||
|      * When I am creating the rule | ||||
|      * Then I have the option of adding a "Declare and File as Record" action to the rule | ||||
|      * <p> | ||||
|      * Given I am creating a rule | ||||
|      * When I add the "Declare and File as Record" action to the rule | ||||
|      * Then I am able to select the record folder I want the declared record to be filed to | ||||
|      * <p> | ||||
|      * Given I am configuring a "Declare and File as Record" action within a rule | ||||
|      * And I have at least one records management role (eg RM User) | ||||
|      * When I am selecting the record folder location to file the declared record to | ||||
|      * Then I see the record folders in the file plan that I have file access to as the creator of the record | ||||
|      **/ | ||||
|     @Test | ||||
|     public void declareAsRecordRuleAsRMUserWithFilingPermissions() { | ||||
|         STEP("Create a collaboration folder"); | ||||
|         testFolder = dataContent.usingSite(testSite) | ||||
|             .usingUser(rmManager) | ||||
|             .createFolder(); | ||||
|  | ||||
|         STEP("Create a rule with Declare as Record action and check that user can select a record folder."); | ||||
|         RecordCategory recordCategory = new RecordCategory().builder() | ||||
|             .id(category_manager.getId()).build(); | ||||
|         RecordCategoryChild folderWithRule = createFolder(recordCategory.getId(), getRandomName("recordFolder")); | ||||
|         RuleDefinition ruleDefinition = RuleDefinition.createNewRule().title("name").description("description") | ||||
|             .applyToChildren(true) | ||||
|             .actions(Collections.singletonList(ActionsOnRule.DECLARE_AS_RECORD.getActionValue())); | ||||
|         rulesAPI.createRule(getAdminUser().getUsername(), getAdminUser().getPassword(), NODE_PREFIX + folderWithRule.getId(), ruleDefinition); | ||||
|  | ||||
|         assertStatusCode(CREATED); | ||||
|     } | ||||
|     /** | ||||
|      * Given I am configuring a "Declare and File as Record" action within a rule | ||||
|      * And I don't have a records management role | ||||
|      * When I am selecting the record folder location to file the declared record to | ||||
|      * Then I can see only the file plan | ||||
|      */ | ||||
|     @Test | ||||
|     public void declareAsRecordRuleAsNonRMUser() | ||||
|     { | ||||
|         STEP("Create a collaboration folder"); | ||||
|         testFolder = dataContent.usingSite(testSite) | ||||
|             .usingUser(nonRMUser) | ||||
|             .createFolder(); | ||||
|  | ||||
|         STEP("Create a rule with Declare as Record action and check that user can select a record folder."); | ||||
|         RecordCategory recordCategory = new RecordCategory().builder() | ||||
|             .id(category_manager.getId()).build(); | ||||
|  | ||||
|         RuleDefinition ruleDefinition = RuleDefinition.createNewRule().title("name").description("description") | ||||
|             .applyToChildren(true) | ||||
|             .actions(Collections.singletonList(ActionsOnRule.DECLARE_AS_RECORD.getActionValue())); | ||||
|         rulesAPI.createRule(nonRMUser.getUsername(), nonRMUser.getPassword(), NODE_PREFIX + testFolder.getNodeRef(), ruleDefinition); | ||||
|  | ||||
|         assertStatusCode(CREATED); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Given I have not selected a record folder location | ||||
|      * When the rule is triggered | ||||
|      * Then the file is declared as record to the UnFiled Records folder | ||||
|      */ | ||||
|     @Test | ||||
|     public void triggerDeclareToUnfiledRuleAsNonRMUser() | ||||
|     { | ||||
|         STEP("Create a collaboration folder with a rule set to declare and file as record without a record folder location"); | ||||
|  | ||||
|         RecordCategory recordCategory = new RecordCategory().builder() | ||||
|             .id(category_manager.getId()).build(); | ||||
|  | ||||
|         RecordCategoryChild folderWithRule = createFolder(recordCategory.getId(), getRandomName("recordFolder")); | ||||
|         RuleDefinition ruleDefinition = RuleDefinition.createNewRule().title("name").description("description") | ||||
|             .applyToChildren(true) | ||||
|             .actions(Collections.singletonList(ActionsOnRule.DECLARE_AS_RECORD.getActionValue())); | ||||
|         rulesAPI.createRule(getAdminUser().getUsername(), getAdminUser().getPassword(), NODE_PREFIX + folderWithRule.getId(), ruleDefinition); | ||||
|  | ||||
|         assertStatusCode(CREATED); | ||||
|  | ||||
|         STEP("Create as nonRMUser a new file into the previous folder in order to trigger the rule"); | ||||
|         inPlaceRecord = dataContent.usingUser(nonRMUser).usingResource(testFolder).createContent(CMISUtil.DocumentType.TEXT_PLAIN); | ||||
|  | ||||
| //      Verify that declared record is in Unfilled Records Folder | ||||
|         UnfiledContainerAPI unfiledContainersAPI = getRestAPIFactory().getUnfiledContainersAPI(); | ||||
|         List<UnfiledContainerChildEntry> matchingRecords = unfiledContainersAPI.getUnfiledContainerChildren(UNFILED_RECORDS_CONTAINER_ALIAS) | ||||
|             .getEntries() | ||||
|             .stream() | ||||
|             .filter(e -> e.getEntry().getId().equals(inPlaceRecord.getNodeRefWithoutVersion())) | ||||
|             .collect(Collectors.toList()); | ||||
|     } | ||||
|  | ||||
|     @AfterClass(alwaysRun = true) | ||||
|     public void cleanupDeclareAsRecordRuleTests() | ||||
|     { | ||||
|         STEP("Delete the collaboration site"); | ||||
|         dataSite.usingUser(nonRMUser).deleteSite(testSite); | ||||
|  | ||||
|         STEP("Delete Users"); | ||||
|         dataUser.deleteUser(nonRMUser); | ||||
|         dataUser.deleteUser(rmManager); | ||||
|  | ||||
|         STEP("Delete categories"); | ||||
|         getRestAPIFactory().getFilePlansAPI().getRootRecordCategories(FILE_PLAN_ALIAS).getEntries().forEach(recordCategoryEntry -> | ||||
|             deleteRecordCategory(recordCategoryEntry.getEntry().getId())); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,221 @@ | ||||
| /* | ||||
|  * #%L | ||||
|  * Alfresco Records Management Module | ||||
|  * %% | ||||
|  * Copyright (C) 2005 - 2022 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.rest.rm.community.rules; | ||||
|  | ||||
| import org.alfresco.dataprep.CMISUtil; | ||||
| import org.alfresco.rest.rm.community.base.BaseRMRestTest; | ||||
| import org.alfresco.rest.rm.community.model.recordcategory.RecordCategory; | ||||
| import org.alfresco.rest.rm.community.model.recordcategory.RecordCategoryChild; | ||||
| import org.alfresco.rest.rm.community.model.rules.ActionsOnRule; | ||||
| import org.alfresco.rest.rm.community.model.rules.RuleDefinition; | ||||
| import org.alfresco.rest.rm.community.model.unfiledcontainer.UnfiledContainerChildEntry; | ||||
| import org.alfresco.rest.rm.community.model.user.UserRoles; | ||||
| import org.alfresco.rest.rm.community.requests.gscore.api.UnfiledContainerAPI; | ||||
| import org.alfresco.rest.rm.community.smoke.FileAsRecordTests; | ||||
| import org.alfresco.rest.v0.RulesAPI; | ||||
| import org.alfresco.rest.v0.service.RoleService; | ||||
| import org.alfresco.utility.model.FileModel; | ||||
| import org.alfresco.utility.model.FileType; | ||||
| import org.alfresco.utility.model.FolderModel; | ||||
| import org.alfresco.utility.model.UserModel; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.testng.annotations.AfterClass; | ||||
| import org.testng.annotations.BeforeClass; | ||||
| import org.testng.annotations.Test; | ||||
|  | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| import static org.alfresco.rest.core.v0.BaseAPI.NODE_PREFIX; | ||||
| import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.FILE_PLAN_ALIAS; | ||||
| import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.UNFILED_RECORDS_CONTAINER_ALIAS; | ||||
| import static org.alfresco.rest.rm.community.model.user.UserPermissions.PERMISSION_FILING; | ||||
| import static org.alfresco.rest.rm.community.util.CommonTestUtils.generateTestPrefix; | ||||
| import static org.alfresco.utility.data.RandomData.getRandomName; | ||||
| import static org.alfresco.utility.report.log.Step.STEP; | ||||
| import static org.springframework.http.HttpStatus.CREATED; | ||||
|  | ||||
| public class FileVersionAsRecordRuleTest  extends BaseRMRestTest { | ||||
|  | ||||
|     private UserModel nonRMuser, rmManager; | ||||
|     private RecordCategory category_manager, category_admin; | ||||
|     private RecordCategoryChild folder_admin, folder_manager ; | ||||
|     private static final String CATEGORY_MANAGER = "catManager" + generateTestPrefix(FileAsRecordTests.class); | ||||
|     private static final String CATEGORY_ADMIN = "catAdmin" + generateTestPrefix(FileAsRecordTests.class); | ||||
|     private static final String FOLDER_MANAGER = "recordFolder" + generateTestPrefix(FileAsRecordTests.class); | ||||
|     private static final String FOLDER_ADMIN = "recordFolder" + generateTestPrefix(FileAsRecordTests.class); | ||||
|     private FolderModel testFolder; | ||||
|     private FileModel document,inPlaceRecord; | ||||
|  | ||||
|  | ||||
|     @Autowired | ||||
|     private RoleService roleService; | ||||
|     @Autowired | ||||
|     private RulesAPI rulesAPI; | ||||
|  | ||||
|     @BeforeClass(alwaysRun = true) | ||||
|     public void createTestPrecondition() | ||||
|     { | ||||
|  | ||||
|  | ||||
|         STEP("Create the RM site if doesn't exist"); | ||||
|         createRMSiteIfNotExists(); | ||||
|  | ||||
|         STEP("Create a user"); | ||||
|         nonRMuser = dataUser.createRandomTestUser("testUser"); | ||||
|  | ||||
|         STEP("Create a collaboration site"); | ||||
|         testSite = dataSite.usingUser(nonRMuser).createPublicRandomSite(); | ||||
|  | ||||
|         STEP("Create a document with the user without RM role"); | ||||
|         document = dataContent.usingSite(testSite) | ||||
|             .usingUser(nonRMuser) | ||||
|             .createContent(CMISUtil.DocumentType.TEXT_PLAIN); | ||||
|  | ||||
|         STEP("Create two categories with two folders"); | ||||
|         category_manager = createRootCategory(CATEGORY_MANAGER); | ||||
|         category_admin = createRootCategory(CATEGORY_ADMIN); | ||||
|         folder_admin = createFolder(category_admin.getId(),FOLDER_ADMIN); | ||||
|         folder_manager = createFolder(category_manager.getId(),FOLDER_MANAGER); | ||||
|  | ||||
|  | ||||
|         STEP("Create an rm user and give filling permission over CATEGORY_MANAGER record category"); | ||||
|         RecordCategory recordCategory = new RecordCategory().builder() | ||||
|             .id(category_manager.getId()) | ||||
|             .build(); | ||||
|         rmManager = roleService.createCollaboratorWithRMRoleAndPermission(testSite, recordCategory, | ||||
|             UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING); | ||||
|  | ||||
|  | ||||
|  | ||||
|         STEP("Create a collaboration folder with a rule set to declare and file version as record to a record folder"); | ||||
|         RecordCategoryChild folderWithRule = createFolder(recordCategory.getId(), getRandomName("recordFolder")); | ||||
|         RuleDefinition ruleDefinition = RuleDefinition.createNewRule().title("name").description("description") | ||||
|             .applyToChildren(true) | ||||
|             .actions(Collections.singletonList(ActionsOnRule.DECLARE_AS_RECORD.getActionValue())); | ||||
|         rulesAPI.createRule(getAdminUser().getUsername(), getAdminUser().getPassword(), NODE_PREFIX + folderWithRule.getId(), ruleDefinition); | ||||
|  | ||||
|         assertStatusCode(CREATED); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     public void declareVersionAsRecordRuleAsRMUserWithFilingPermissions() | ||||
|     { | ||||
|  | ||||
|         STEP("Create a collaboration folder"); | ||||
|         testFolder = dataContent.usingSite(testSite) | ||||
|             .usingUser(rmManager) | ||||
|             .createFolder(); | ||||
|  | ||||
|         STEP("Create a rule with Declare as Record action and check that user can select a record folder."); | ||||
|         RecordCategory recordCategory = new RecordCategory().builder() | ||||
|             .id(category_manager.getId()).build(); | ||||
|         RecordCategoryChild folderWithRule = createFolder(recordCategory.getId(), getRandomName("recordFolder")); | ||||
|         RuleDefinition ruleDefinition = RuleDefinition.createNewRule().title("name").description("description") | ||||
|             .applyToChildren(true) | ||||
|             .actions(Collections.singletonList(ActionsOnRule.DECLARE_AS_RECORD.getActionValue())); | ||||
|         rulesAPI.createRule(rmManager.getUsername(), rmManager.getPassword(), NODE_PREFIX + testFolder.getNodeRef(), ruleDefinition); | ||||
|  | ||||
|         assertStatusCode(CREATED); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     public void declareVersionAsRecordRuleAsNonRMUser() | ||||
|     { | ||||
|  | ||||
|         STEP("Create a collaboration folder"); | ||||
|         testFolder = dataContent.usingSite(testSite) | ||||
|             .usingUser(nonRMuser) | ||||
|             .createFolder(); | ||||
|  | ||||
|         STEP("Create a rule with Declare as Record action and check that user can select a record folder."); | ||||
|         RecordCategory recordCategory = new RecordCategory().builder() | ||||
|             .id(category_manager.getId()).build(); | ||||
|  | ||||
|         RuleDefinition ruleDefinition = RuleDefinition.createNewRule().title("name").description("description") | ||||
|             .applyToChildren(true) | ||||
|             .actions(Collections.singletonList(ActionsOnRule.DECLARE_AS_RECORD.getActionValue())); | ||||
|         rulesAPI.createRule(nonRMuser.getUsername(), nonRMuser.getPassword(), NODE_PREFIX + testFolder.getNodeRef(), ruleDefinition); | ||||
|  | ||||
|         assertStatusCode(CREATED); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     public void triggerDeclareToUnfiledRuleAsNonRMUser() throws Exception { | ||||
|  | ||||
|         STEP("Create a collaboration folder with a rule set to declare and file as record without a record folder location"); | ||||
|  | ||||
|  | ||||
|         FileModel inplaceRecord = dataContent.usingSite(testSite).usingUser(nonRMuser) | ||||
|             .createContent(new FileModel("declareAndFileToIntoUnfiledRecordFolder", | ||||
|                 FileType.TEXT_PLAIN)); | ||||
|  | ||||
|         RecordCategory recordCategory = new RecordCategory().builder() | ||||
|             .id(category_manager.getId()).build(); | ||||
|  | ||||
|         RuleDefinition ruleDefinition = RuleDefinition.createNewRule().title("name").description("description") | ||||
|             .applyToChildren(true) | ||||
|             .actions(Collections.singletonList(ActionsOnRule.DECLARE_AS_RECORD.getActionValue())); | ||||
|         rulesAPI.createRule(nonRMuser.getUsername(), nonRMuser.getPassword(), NODE_PREFIX + inplaceRecord.getNodeRef(), ruleDefinition); | ||||
|  | ||||
|         assertStatusCode(CREATED); | ||||
|  | ||||
|        STEP("Create as nonRMuser a new file into the previous folder in order to trigger the rule"); | ||||
|         inPlaceRecord = dataContent.usingUser(nonRMuser).usingResource(testFolder).createContent(CMISUtil.DocumentType.TEXT_PLAIN); | ||||
|  | ||||
|         // verify the declared record is in Unfilled Records folder | ||||
|         UnfiledContainerAPI unfiledContainersAPI = getRestAPIFactory().getUnfiledContainersAPI(); | ||||
|         List<UnfiledContainerChildEntry> matchingRecords = unfiledContainersAPI.getUnfiledContainerChildren(UNFILED_RECORDS_CONTAINER_ALIAS) | ||||
|             .getEntries() | ||||
|             .stream() | ||||
|             .filter(e -> e.getEntry().getId().equals(inplaceRecord.getNodeRefWithoutVersion())) | ||||
|             .collect(Collectors.toList()); | ||||
|  | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @AfterClass(alwaysRun = true) | ||||
|     public void cleanupDeclareVersionAsRecordRuleTests() | ||||
|     { | ||||
|  | ||||
|         STEP("Delete the collaboration site"); | ||||
|         dataSite.usingUser(nonRMuser).deleteSite(testSite); | ||||
|  | ||||
|         STEP("Delete Users"); | ||||
|         dataUser.deleteUser(nonRMuser); | ||||
|         dataUser.deleteUser(rmManager); | ||||
|  | ||||
|  | ||||
|         STEP("Delete categories"); | ||||
|         getRestAPIFactory().getFilePlansAPI().getRootRecordCategories(FILE_PLAN_ALIAS).getEntries().forEach(recordCategoryEntry -> | ||||
|             deleteRecordCategory(recordCategoryEntry.getEntry().getId())); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,237 @@ | ||||
| /* | ||||
|  * #%L | ||||
|  * Alfresco Records Management Module | ||||
|  * %% | ||||
|  * Copyright (C) 2005 - 2022 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.rest.rm.community.rules; | ||||
|  | ||||
| import org.alfresco.rest.model.RestNodeModel; | ||||
| import org.alfresco.rest.rm.community.base.BaseRMRestTest; | ||||
| import org.alfresco.rest.rm.community.model.fileplan.FilePlan; | ||||
| import org.alfresco.rest.rm.community.model.record.Record; | ||||
| import org.alfresco.rest.rm.community.model.recordcategory.RecordCategory; | ||||
| import org.alfresco.rest.rm.community.model.recordcategory.RecordCategoryChild; | ||||
| import org.alfresco.rest.rm.community.model.rules.ActionsOnRule; | ||||
| import org.alfresco.rest.rm.community.model.rules.ConditionsOnRule; | ||||
| import org.alfresco.rest.rm.community.model.rules.RuleDefinition; | ||||
| import org.alfresco.rest.rm.community.model.unfiledcontainer.UnfiledContainer; | ||||
| import org.alfresco.rest.rm.community.model.unfiledcontainer.UnfiledContainerChildEntry; | ||||
| import org.alfresco.rest.rm.community.model.user.UserRoles; | ||||
| import org.alfresco.rest.rm.community.requests.gscore.api.RecordFolderAPI; | ||||
| import org.alfresco.rest.rm.community.requests.gscore.api.UnfiledContainerAPI; | ||||
| import org.alfresco.rest.search.RestRequestQueryModel; | ||||
| import org.alfresco.rest.v0.HoldsAPI; | ||||
| import org.alfresco.rest.v0.RecordsAPI; | ||||
| import org.alfresco.rest.v0.RulesAPI; | ||||
| import org.alfresco.rest.v0.service.RoleService; | ||||
| import org.alfresco.utility.model.UserModel; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.testng.annotations.AfterClass; | ||||
| import org.testng.annotations.BeforeClass; | ||||
| import org.testng.annotations.Test; | ||||
|  | ||||
| import java.io.InputStream; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.Random; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| import static java.lang.Integer.MAX_VALUE; | ||||
| import static java.util.Arrays.asList; | ||||
| import static org.alfresco.rest.core.v0.BaseAPI.NODE_PREFIX; | ||||
| import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.FILE_PLAN_ALIAS; | ||||
| import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.UNFILED_RECORDS_CONTAINER_ALIAS; | ||||
| import static org.alfresco.rest.rm.community.util.CommonTestUtils.generateTestPrefix; | ||||
| import static org.alfresco.rest.rm.community.utils.CoreUtil.createBodyForMoveCopy; | ||||
| import static org.alfresco.rest.rm.community.utils.CoreUtil.toContentModel; | ||||
| import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.*; | ||||
| import static org.alfresco.utility.data.RandomData.getRandomAlphanumeric; | ||||
| import static org.alfresco.utility.data.RandomData.getRandomName; | ||||
| import static org.alfresco.utility.report.log.Step.STEP; | ||||
| import static org.springframework.http.HttpStatus.*; | ||||
| import static org.testng.Assert.assertNotNull; | ||||
|  | ||||
|  | ||||
| public class MoveToRuleOnFoldersTest extends BaseRMRestTest{ | ||||
|  | ||||
|  | ||||
|     private RecordCategoryChild recordFolder2; | ||||
|     private RecordCategoryChild recordFolder1; | ||||
|     private String nonElectronicId; | ||||
|  | ||||
|     public Record electronicRecord; | ||||
|  | ||||
|     private String ruleType = ConditionsOnRule.UPDATE.getWhenConditionValue(); | ||||
|     private UserModel rmAdmin; | ||||
|     public RecordCategory RecordCategoryOne; | ||||
|     private RecordCategoryChild recordFolder; | ||||
|     public static final String RECORD_FOLDER_ONE = "record-folder-one"; | ||||
|     private final String TEST_PREFIX = generateTestPrefix(MoveToRuleOnFoldersTest.class); | ||||
|  | ||||
|     private final String RECORD_CATEGORY_ONE = TEST_PREFIX + "category"; | ||||
|  | ||||
|     private final String recordName = "Test record"; | ||||
|     private final String recordTitle = recordName + " title"; | ||||
|     private final String recordDescription = recordName + " description"; | ||||
|     private Record nonElectrinicRecordModel; | ||||
|     private RecordFolderAPI recordFolderAPI; | ||||
|     public String title,description,box,file,shelf,storageLocation,name; | ||||
|     @Autowired | ||||
|     private RulesAPI rulesAPI; | ||||
|     @Autowired | ||||
|     private HoldsAPI holdsAPI; | ||||
|  | ||||
|     @Autowired | ||||
|     private RoleService roleService; | ||||
|  | ||||
|     @Autowired | ||||
|     public RecordsAPI recordsAPI; | ||||
|  | ||||
|  | ||||
|     @BeforeClass(alwaysRun = true) | ||||
|     public void precondition() | ||||
|     { | ||||
|         //create RM site | ||||
|         createRMSiteIfNotExists(); | ||||
|         rmAdmin = roleService.createUserWithRMRole(UserRoles.ROLE_RM_ADMIN.roleId); | ||||
|         //create root category, create folders , add electronic and non electronic records | ||||
|         RecordCategoryOne = createRootCategory(RECORD_CATEGORY_ONE); | ||||
|         recordFolder1=createRecordFolder(RecordCategoryOne.getId(), getRandomName("recFolder")); | ||||
|        // recordFolder1_id = createRecordFolder(RecordCategoryOne.getId(), getRandomName("recFolder")).getId(); | ||||
|         recordFolder2 = createFolder(getAdminUser(),RecordCategoryOne.getId(),getRandomName("recFolder")); | ||||
|  | ||||
|  | ||||
|         STEP("CREATE ELECTRONIC RECORD"); | ||||
|         recordFolderAPI = getRestAPIFactory().getRecordFolderAPI(); | ||||
|         electronicRecord = recordFolderAPI.createRecord(createElectronicRecordModel(), recordFolder1.getId(), getFile(IMAGE_FILE)); | ||||
|         STEP("Check the electronic record has been created"); | ||||
|         assertStatusCode(CREATED); | ||||
|  | ||||
|  | ||||
|         STEP("Create a non-electronic record by completing some of the fields"); | ||||
|         // Use these properties for non-electronic record to be created | ||||
|         title = "Title " + getRandomAlphanumeric(); | ||||
|         description = "Description " + getRandomAlphanumeric(); | ||||
|         box = "Box "+ getRandomAlphanumeric(); | ||||
|         file = "File " + getRandomAlphanumeric(); | ||||
|         shelf = "Shelf " + getRandomAlphanumeric(); | ||||
|         storageLocation = "Storage Location " + getRandomAlphanumeric(); | ||||
|         name = "Record " + getRandomAlphanumeric(); | ||||
|         Random random = new Random(); | ||||
|         Integer numberOfCopies = random.nextInt(MAX_VALUE); | ||||
|         Integer physicalSize = random.nextInt(MAX_VALUE); | ||||
|  | ||||
|         // Set values of all available properties for the non electronic records | ||||
|         nonElectrinicRecordModel = createFullNonElectronicRecordModel(name, title, description, box, file, shelf, storageLocation, numberOfCopies, physicalSize); | ||||
|         // Create non-electronic record | ||||
|         nonElectronicId = recordFolderAPI.createRecord(nonElectrinicRecordModel, recordFolder1.getId()).getId(); | ||||
|         STEP("Check the non-electronic record has been created"); | ||||
|         assertStatusCode(CREATED); | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     public void MoveToRuleFoldersTest() | ||||
|     { | ||||
|  | ||||
|         String CatName=RecordCategoryOne.getName(); | ||||
|         String folder2name=recordFolder2.getName(); | ||||
|         String recfolder2_path="/"+CatName+"/"+folder2name; | ||||
|  | ||||
|         STEP("create a rule MOVE_TO for folder 1"); | ||||
|         RuleDefinition ruleDefinition = RuleDefinition.createNewRule().title("name").description("description1") | ||||
|             .runInBackground(true).title(title) | ||||
|             .actions(Collections.singletonList(ActionsOnRule.MOVE_TO.getActionValue())).ruleType(ruleType).path(recfolder2_path); | ||||
|         rulesAPI.createRule(getAdminUser().getUsername(), getAdminUser().getPassword(), NODE_PREFIX +recordFolder1.getId() , ruleDefinition); | ||||
|  | ||||
|  | ||||
|  | ||||
|         STEP("Update metadata for Non-Electronic Record"); | ||||
|         updateRecordMetadata(); | ||||
|  | ||||
|         STEP("Delete ELECTRONIC AND NON-ELECTRONIC RECORDS IN FOLDER 2"); | ||||
|         org.alfresco.rest.rm.community.requests.gscore.api.RecordsAPI recordsAPI = getRestAPIFactory().getRecordsAPI(); | ||||
|         recordsAPI.deleteRecord(electronicRecord.getId()); | ||||
|         assertStatusCode(NO_CONTENT); | ||||
|         recordsAPI.deleteRecord(nonElectronicId); | ||||
|         assertStatusCode(NO_CONTENT); | ||||
|  | ||||
|         STEP("RULE CREATION FOR FOLDER 1 WITHOUT RUNNING IN BACKGROUND"); | ||||
|  | ||||
|         RuleDefinition ruleDefinition_notinbackground = RuleDefinition.createNewRule().title("name").description("description1") | ||||
|             .runInBackground(false).title(title) | ||||
|             .actions(Collections.singletonList(ActionsOnRule.MOVE_TO.getActionValue())).ruleType(ruleType).path(recfolder2_path); | ||||
|         rulesAPI.createRule(getAdminUser().getUsername(), getAdminUser().getPassword(), NODE_PREFIX +recordFolder1.getId() , ruleDefinition); | ||||
|  | ||||
|         STEP("CREATE ELECTRONIC AND NON-ELECTRONIC RECORDS"); | ||||
|         electronicRecord = recordFolderAPI.createRecord(createElectronicRecordModel(), recordFolder1.getId(), getFile(IMAGE_FILE)); | ||||
|         STEP("Check the electronic record has been created"); | ||||
|         assertStatusCode(CREATED); | ||||
|         nonElectronicId = recordFolderAPI.createRecord(nonElectrinicRecordModel, recordFolder1.getId()).getId(); | ||||
|         STEP("Check the non-electronic record has been created"); | ||||
|         assertStatusCode(CREATED); | ||||
|  | ||||
|         STEP("UPDATE METADATA"); | ||||
|         updateRecordMetadata(); | ||||
|  | ||||
|         STEP("CHECK IF ELECTRONIC AND NON-ELECTRONIC RECORDS MOVED  TO FOLDER2"); | ||||
|         updateRecordMetadata(); | ||||
|     } | ||||
|  | ||||
|     @AfterClass(alwaysRun = true) | ||||
|     public void cleanMoveToRuleOnFoldersTest() | ||||
|     { | ||||
|         deleteRecordCategory(RecordCategoryOne.getId()); | ||||
|  | ||||
|         getDataUser().deleteUser(rmAdmin); | ||||
|     } | ||||
|  | ||||
|     private String getModifiedPropertyValue(String originalValue) { | ||||
|         /* to be used to append to modifications */ | ||||
|         String MODIFIED_PREFIX = "modified_"; | ||||
|         return MODIFIED_PREFIX + originalValue; | ||||
|     } | ||||
|     private void updateRecordMetadata(){ | ||||
|         STEP("Update metadata for Non-Electronic Record"); | ||||
|         org.alfresco.rest.rm.community.requests.gscore.api.RecordsAPI recordsAPI = getRestAPIFactory().getRecordsAPI(); | ||||
|         Record nonelecrecord = recordsAPI.getRecord(nonElectronicId); | ||||
|         String nonelecnewName = getModifiedPropertyValue(nonElectrinicRecordModel.getName()); | ||||
|         String nonelecnewTitle = getModifiedPropertyValue(nonElectrinicRecordModel.getProperties().getTitle()); | ||||
|         String nonelecnewDescription = getModifiedPropertyValue(nonElectrinicRecordModel.getProperties().getDescription()); | ||||
|         recordsAPI.updateRecord(createRecordModel(nonelecnewName, nonelecnewDescription, nonelecnewTitle),nonelecrecord.getId()); | ||||
|         assertStatusCode(OK); | ||||
|  | ||||
|         STEP("Update metadata for Electronic Record"); | ||||
|         Record elecrecord = recordsAPI.getRecord(electronicRecord.getId()); | ||||
|         String elecnewName = getModifiedPropertyValue(electronicRecord.getName()); | ||||
|         String elecnewTitle = getModifiedPropertyValue(electronicRecord.getProperties().getTitle()); | ||||
|         String elecnewDescription = getModifiedPropertyValue(electronicRecord.getProperties().getDescription()); | ||||
|         recordsAPI.updateRecord(createRecordModel(elecnewName, elecnewDescription, elecnewTitle),elecrecord.getId()); | ||||
|         assertStatusCode(OK); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -7,7 +7,7 @@ | ||||
|    <parent> | ||||
|       <groupId>org.alfresco</groupId> | ||||
|       <artifactId>alfresco-governance-services-community-parent</artifactId> | ||||
|       <version>20.11</version> | ||||
|       <version>20.28-SNAPSHOT</version> | ||||
|    </parent> | ||||
|  | ||||
|    <modules> | ||||
|   | ||||
| @@ -1,3 +1,3 @@ | ||||
| SOLR6_TAG=2.0.5 | ||||
| SOLR6_TAG=2.0.5.1 | ||||
| POSTGRES_TAG=14.4 | ||||
| ACTIVEMQ_TAG=5.17.1-jre11-rockylinux8 | ||||
|   | ||||
| @@ -8,7 +8,7 @@ | ||||
|    <parent> | ||||
|       <groupId>org.alfresco</groupId> | ||||
|       <artifactId>alfresco-governance-services-community-repo-parent</artifactId> | ||||
|       <version>20.11</version> | ||||
|       <version>20.28-SNAPSHOT</version> | ||||
|    </parent> | ||||
|  | ||||
|    <properties> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-governance-services-community-repo-parent</artifactId> | ||||
|         <version>20.11</version> | ||||
|         <version>20.28-SNAPSHOT</version> | ||||
|     </parent> | ||||
|  | ||||
|     <build> | ||||
|   | ||||
| @@ -38,6 +38,9 @@ tags: | ||||
|     description: Retrieve and manage unfiled records containers | ||||
|   - name: unfiled-record-folders | ||||
|     description: Retrieve and manage unfiled record folders | ||||
|   - name: events | ||||
|     description: Retrieve and manage retention events | ||||
|      | ||||
| paths: | ||||
|   ## GS sites | ||||
|   '/gs-sites': | ||||
| @@ -2091,7 +2094,172 @@ paths: | ||||
|           description: Unexpected error | ||||
|           schema: | ||||
|             $ref: '#/definitions/Error' | ||||
|   '/events': | ||||
|     get: | ||||
|       tags: | ||||
|         - events | ||||
|       summary: List all available retention events | ||||
|       description: | | ||||
|         Gets the list of events that can be used by retention steps | ||||
|       operationId: getAllEvents | ||||
|       produces: | ||||
|         - application/json | ||||
|       parameters: | ||||
|         - $ref: '#/parameters/skipCountParam' | ||||
|         - $ref: '#/parameters/maxItemsParam' | ||||
|       responses: | ||||
|         '200': | ||||
|           description: Successful response | ||||
|           schema: | ||||
|             $ref: '#/definitions/EventPaging' | ||||
|         '400': | ||||
|           description: | | ||||
|             Invalid parameter: value of **maxItems** or **skipCount** is invalid | ||||
|         '401': | ||||
|           description: Authentication failed | ||||
|         default: | ||||
|           description: Unexpected error | ||||
|           schema: | ||||
|             $ref: '#/definitions/Error' | ||||
|  | ||||
|     post: | ||||
|       tags: | ||||
|         - events | ||||
|       summary: Create a new retention event | ||||
|       description: | | ||||
|         Creates a new event that can be used by retention schedules. | ||||
|       operationId: createEvent | ||||
|       parameters: | ||||
|         - in: body | ||||
|           name: eventBodyCreate | ||||
|           description: The new event. | ||||
|           required: true | ||||
|           schema: | ||||
|             $ref: '#/definitions/EventBody' | ||||
|       consumes: | ||||
|         - application/json | ||||
|       produces: | ||||
|         - application/json | ||||
|       responses: | ||||
|         '201': | ||||
|           description: Successful response | ||||
|           schema: | ||||
|             $ref: '#/definitions/EventEntry' | ||||
|         '400': | ||||
|           description: | | ||||
|             Invalid parameter: **name** or **type** is invalid | ||||
|         '401': | ||||
|           description: Authentication failed | ||||
|         '403': | ||||
|           description: Current user does not have permission to create event | ||||
|         '409': | ||||
|           description: Cannot create event. An event with the name **name** already exists | ||||
|         default: | ||||
|           description: Unexpected error | ||||
|           schema: | ||||
|             $ref: '#/definitions/Error' | ||||
|    | ||||
|   '/events/{eventId}': | ||||
|     get: | ||||
|       tags: | ||||
|         - events | ||||
|       summary: Return event for given eventId | ||||
|       description: | | ||||
|         Gets information about the retention event with id **eventId**. | ||||
|       operationId: getEvent | ||||
|       produces: | ||||
|         - application/json | ||||
|       parameters: | ||||
|         - $ref: '#/parameters/eventIdParam' | ||||
|       responses: | ||||
|         '200': | ||||
|           description: Successful response | ||||
|           schema: | ||||
|             $ref: '#/definitions/EventEntry' | ||||
|         '400': | ||||
|           description: | | ||||
|             Invalid parameter: **eventId** is invalid     | ||||
|         '401': | ||||
|           description: Authentication failed | ||||
|         '404': | ||||
|           description: "**eventId** does not exist" | ||||
|         default: | ||||
|           description: Unexpected error | ||||
|           schema: | ||||
|             $ref: '#/definitions/Error' | ||||
|     put: | ||||
|       tags: | ||||
|         - events | ||||
|       summary: Update event for given eventId | ||||
|       operationId: updateEvent | ||||
|       description: | | ||||
|         Updates retention event with id **eventId**. | ||||
|       produces: | ||||
|         - application/json | ||||
|       parameters: | ||||
|         - $ref: '#/parameters/eventIdParam' | ||||
|         - in: body | ||||
|           name: eventBodyUpdate | ||||
|           description: The event information to update. | ||||
|           required: true | ||||
|           schema: | ||||
|             $ref: '#/definitions/EventBody' | ||||
|       responses: | ||||
|         '200': | ||||
|           description: Successful response | ||||
|           schema: | ||||
|             $ref: '#/definitions/EventEntry' | ||||
|         '400': | ||||
|           description: | | ||||
|             Invalid parameter: The update request is invalid or **eventId** is not a valid format or **eventBodyUpdate** is invalid | ||||
|         '401': | ||||
|           description: Authentication failed | ||||
|         '403': | ||||
|           description: Current user does not have permission to update events | ||||
|         '404': | ||||
|           description: "**eventId** does not exist" | ||||
|         '409': | ||||
|           description: Cannot update event. An event with the name **name** already exists | ||||
|         default: | ||||
|           description: Unexpected error | ||||
|           schema: | ||||
|             $ref: '#/definitions/Error' | ||||
|    | ||||
|   '/event-types': | ||||
|     get: | ||||
|       tags: | ||||
|         - events | ||||
|       summary: List all the retention event types | ||||
|       description: | | ||||
|         Gets a list of all the retention event types. | ||||
|       operationId: getAllEventTypes | ||||
|       produces: | ||||
|         - application/json | ||||
|       parameters: | ||||
|         - $ref: '#/parameters/skipCountParam' | ||||
|         - $ref: '#/parameters/maxItemsParam' | ||||
|       responses: | ||||
|         '200': | ||||
|           description: Successful response | ||||
|           schema: | ||||
|             $ref: '#/definitions/EventTypePaging' | ||||
|         '400': | ||||
|           description: | | ||||
|             Invalid parameter: value of **maxItems** or **skipCount** is invalid | ||||
|         '401': | ||||
|           description: Authentication failed | ||||
|         default: | ||||
|           description: Unexpected error | ||||
|           schema: | ||||
|             $ref: '#/definitions/Error'           | ||||
| parameters: | ||||
|   ## event | ||||
|   eventIdParam: | ||||
|     name: eventId | ||||
|     in: path | ||||
|     description: The identifier of an event. | ||||
|     required: true | ||||
|     type: string | ||||
|   ## File plans | ||||
|   filePlanEntryIncludeParam: | ||||
|     name: include | ||||
| @@ -3760,3 +3928,91 @@ definitions: | ||||
|           - SiteCollaborator | ||||
|           - SiteContributor | ||||
|           - SiteManager | ||||
|   EventPaging: | ||||
|     type: object | ||||
|     properties: | ||||
|       list: | ||||
|         type: object | ||||
|         properties: | ||||
|           pagination: | ||||
|             $ref: '#/definitions/Pagination' | ||||
|           entries: | ||||
|             type: array | ||||
|             items: | ||||
|               $ref: '#/definitions/EventEntry' | ||||
|   EventEntry: | ||||
|     type: object | ||||
|     required: | ||||
|       - entry | ||||
|     properties: | ||||
|       entry: | ||||
|         $ref: '#/definitions/Event' | ||||
|   Event: | ||||
|     type: object | ||||
|     required: | ||||
|       - id | ||||
|       - name | ||||
|       - type | ||||
|     properties: | ||||
|       id: | ||||
|         type: string | ||||
|         description: this is the id of the event | ||||
|       name: | ||||
|         type: string | ||||
|         description: This is the unique display label of the event | ||||
|       type: | ||||
|         type: string | ||||
|         description: this is event type | ||||
|   EventBody: | ||||
|     type: object | ||||
|     required: | ||||
|       - name | ||||
|     properties: | ||||
|       name: | ||||
|         type: string | ||||
|         description: This is the unique display label of the event | ||||
|       type: | ||||
|         type: string | ||||
|         description: this is event type | ||||
|         default: Simple | ||||
|   EventTypePaging: | ||||
|     type: object | ||||
|     properties: | ||||
|       list: | ||||
|         type: object | ||||
|         properties: | ||||
|           pagination: | ||||
|             $ref: '#/definitions/Pagination' | ||||
|           entries: | ||||
|             type: array | ||||
|             items: | ||||
|               $ref: '#/definitions/EventTypeEntry' | ||||
|   EventTypeEntry: | ||||
|     type: object | ||||
|     required: | ||||
|       - entry | ||||
|     properties: | ||||
|       entry: | ||||
|         $ref: '#/definitions/EventType' | ||||
|   EventType: | ||||
|     type: object | ||||
|     required: | ||||
|       - id | ||||
|       - name | ||||
|     properties: | ||||
|       id: | ||||
|         type: string | ||||
|         description: this is the event type id | ||||
|       name: | ||||
|         type: string | ||||
|         description: this is event type name | ||||
|       isAutomatic: | ||||
|         type: boolean | ||||
|         description: Whether events of this type need completing manually or can be completed automatically | ||||
|         default: true | ||||
|       associationName: | ||||
|         type: string | ||||
|         description: The association used to determine whether automatic events of this type are complete | ||||
|       actionOnAssociatedNode: | ||||
|         type: string | ||||
|         description: If an association name is set for this event type then it is possible to require an action to be completed on the associated node | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo</artifactId> | ||||
|         <version>20.11</version> | ||||
|         <version>20.28-SNAPSHOT</version> | ||||
|     </parent> | ||||
|  | ||||
|     <modules> | ||||
|   | ||||
| @@ -8,7 +8,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-amps</artifactId> | ||||
|         <version>20.11</version> | ||||
|         <version>20.28-SNAPSHOT</version> | ||||
|     </parent> | ||||
|  | ||||
|     <properties> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|    <parent> | ||||
|       <groupId>org.alfresco</groupId> | ||||
|       <artifactId>alfresco-community-repo</artifactId> | ||||
|       <version>20.11</version> | ||||
|       <version>20.28-SNAPSHOT</version> | ||||
|    </parent> | ||||
|  | ||||
|    <dependencies> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo</artifactId> | ||||
|         <version>20.11</version> | ||||
|         <version>20.28-SNAPSHOT</version> | ||||
|     </parent> | ||||
|  | ||||
|     <properties> | ||||
| @@ -134,7 +134,7 @@ | ||||
|         <dependency> | ||||
|             <groupId>com.fasterxml.woodstox</groupId> | ||||
|             <artifactId>woodstox-core</artifactId> | ||||
|             <version>6.3.1</version> | ||||
|             <version>6.4.0</version> | ||||
|         </dependency> | ||||
|  | ||||
|         <!-- the cxf libs were updated, see dependencyManagement section --> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo</artifactId> | ||||
|         <version>20.11</version> | ||||
|         <version>20.28-SNAPSHOT</version> | ||||
|     </parent> | ||||
|  | ||||
|     <dependencies> | ||||
|   | ||||
| @@ -9,6 +9,6 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-packaging</artifactId> | ||||
|         <version>20.11</version> | ||||
|         <version>20.28-SNAPSHOT</version> | ||||
|     </parent> | ||||
| </project> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-packaging</artifactId> | ||||
|         <version>20.11</version> | ||||
|         <version>20.28-SNAPSHOT</version> | ||||
|     </parent> | ||||
|  | ||||
|     <properties> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo</artifactId> | ||||
|         <version>20.11</version> | ||||
|         <version>20.28-SNAPSHOT</version> | ||||
|     </parent> | ||||
|  | ||||
|     <modules> | ||||
|   | ||||
| @@ -1,3 +1,3 @@ | ||||
| SOLR6_TAG=2.0.5 | ||||
| SOLR6_TAG=2.0.5.1 | ||||
| POSTGRES_TAG=14.4 | ||||
| ACTIVEMQ_TAG=5.17.1-jre11-rockylinux8 | ||||
|   | ||||
| @@ -6,7 +6,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-packaging</artifactId> | ||||
|         <version>20.11</version> | ||||
|         <version>20.28-SNAPSHOT</version> | ||||
|     </parent> | ||||
|  | ||||
|     <modules> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-tests</artifactId> | ||||
|         <version>20.11</version> | ||||
|         <version>20.28-SNAPSHOT</version> | ||||
|     </parent> | ||||
|  | ||||
|     <organization> | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-tests</artifactId> | ||||
|         <version>20.11</version> | ||||
|         <version>20.28-SNAPSHOT</version> | ||||
|     </parent> | ||||
|  | ||||
|     <developers> | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-tests</artifactId> | ||||
|         <version>20.11</version> | ||||
|         <version>20.28-SNAPSHOT</version> | ||||
|     </parent> | ||||
|  | ||||
|     <developers> | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-tests</artifactId> | ||||
|         <version>20.11</version> | ||||
|         <version>20.28-SNAPSHOT</version> | ||||
|     </parent> | ||||
|  | ||||
|     <developers> | ||||
|   | ||||
| @@ -0,0 +1,103 @@ | ||||
| package org.alfresco.rest.actions.email; | ||||
|  | ||||
| import static java.util.Objects.requireNonNull; | ||||
|  | ||||
| import static org.hamcrest.Matchers.notNullValue; | ||||
|  | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.io.Serializable; | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
| import javax.json.JsonObject; | ||||
|  | ||||
| import org.alfresco.rest.RestTest; | ||||
| import org.alfresco.rest.core.JsonBodyGenerator; | ||||
| import org.alfresco.utility.model.FileModel; | ||||
| import org.alfresco.utility.model.FileType; | ||||
| import org.alfresco.utility.model.FolderModel; | ||||
| import org.alfresco.utility.model.UserModel; | ||||
|  | ||||
| import org.springframework.http.HttpStatus; | ||||
| import org.testng.annotations.BeforeClass; | ||||
| import org.testng.annotations.Test; | ||||
|  | ||||
| public class EmailTemplateTest extends RestTest { | ||||
|  | ||||
|     public static final String MAIL_ACTION = "mail"; | ||||
|  | ||||
|     private UserModel adminUser; | ||||
|     private UserModel testUser; | ||||
|     private FolderModel testFolder; | ||||
|  | ||||
|     @BeforeClass(alwaysRun = true) | ||||
|     public void dataPreparation() throws Exception { | ||||
|         adminUser = dataUser.getAdminUser(); | ||||
|  | ||||
|         testUser = dataUser.createRandomTestUser(); | ||||
|         testSite = dataSite.usingUser(testUser) | ||||
|                            .createPublicRandomSite(); | ||||
|         testFolder = dataContent.usingUser(testUser) | ||||
|                                 .usingSite(testSite) | ||||
|                                 .createFolder(); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     public void adminCanSendEmailUsingTemplateWithModelAsString() throws Exception | ||||
|     { | ||||
|         String templateId = uploadEmailTemplate("simpleEmailTemplate.ftl"); | ||||
|  | ||||
|         // Create the model for use with email template | ||||
|         JsonObject args = JsonBodyGenerator.defineJSON() | ||||
|                                            .add("args", JsonBodyGenerator.defineJSON() | ||||
|                                                                          .add("name", "testname") | ||||
|                                                                          .build()) | ||||
|                                            .build(); | ||||
|         String emailModel = args.toString(); | ||||
|  | ||||
|         // Send an email using the template | ||||
|         restClient.authenticateUser(adminUser) | ||||
|                   .withCoreAPI() | ||||
|                   .usingActions() | ||||
|                   .executeAction(MAIL_ACTION, testFolder, createMailWithTemplateParameters(adminUser, testUser, templateId, emailModel)); | ||||
|  | ||||
|         restClient.onResponse() | ||||
|                   .assertThat().statusCode(HttpStatus.ACCEPTED.value()) | ||||
|                   .assertThat().body("entry.id", notNullValue()); | ||||
|     } | ||||
|  | ||||
|     private String uploadEmailTemplate(String templateName) throws IOException | ||||
|     { | ||||
|         final String templateContent = getTemplateContent(templateName); | ||||
|         final FileModel templateToCreate = new FileModel(templateName, FileType.TEXT_PLAIN, templateContent); | ||||
|  | ||||
|         final FileModel createdTemplate = dataContent.usingAdmin() | ||||
|                                                      .usingResource(testFolder) | ||||
|                                                      .createContent(templateToCreate); | ||||
|  | ||||
|         return createdTemplate.getNodeRef(); | ||||
|     } | ||||
|  | ||||
|     private String getTemplateContent(String templateName) throws IOException | ||||
|     { | ||||
|         final String templateClasspathLocation = "/shared-resources/testdata/" + templateName; | ||||
|         try (InputStream templateStream = getClass().getResourceAsStream(templateClasspathLocation)) | ||||
|         { | ||||
|             requireNonNull(templateStream, "Couldn't locate `" + templateClasspathLocation + "`"); | ||||
|             return new String(templateStream.readAllBytes()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static Map<String, Serializable> createMailWithTemplateParameters(UserModel sender, UserModel recipient, String templateId, Serializable model) | ||||
|     { | ||||
|         Map<String, Serializable> parameterValues = new HashMap<>(); | ||||
|  | ||||
|         parameterValues.put("from", sender.getEmailAddress()); | ||||
|         parameterValues.put("to", recipient.getEmailAddress()); | ||||
|         parameterValues.put("subject", "Test"); | ||||
|         parameterValues.put("template", "workspace://SpacesStore/" + templateId); | ||||
|         parameterValues.put("template_model", model); | ||||
|  | ||||
|         return parameterValues; | ||||
|     } | ||||
| } | ||||
| @@ -14,6 +14,7 @@ import org.alfresco.utility.testrail.annotation.TestRail; | ||||
| import org.springframework.http.HttpMethod; | ||||
| import org.springframework.http.HttpStatus; | ||||
| import org.testng.annotations.BeforeClass; | ||||
| import org.testng.annotations.Ignore; | ||||
| import org.testng.annotations.Test; | ||||
|  | ||||
| public class AddFavoritesTests extends RestTest | ||||
| @@ -354,6 +355,7 @@ public class AddFavoritesTests extends RestTest | ||||
|     @TestRail(section = { TestGroup.REST_API, TestGroup.FAVORITES }, executionType = ExecutionType.REGRESSION, | ||||
|             description = "Verify add file favorite with tag id returns status code 404") | ||||
|     @Test(groups = { TestGroup.REST_API, TestGroup.FAVORITES, TestGroup.REGRESSION }) | ||||
|     @Ignore | ||||
|     public void addFileFavoriteUsingTagId() throws Exception | ||||
|     { | ||||
|         FileModel file = dataContent.usingSite(siteModel).usingUser(adminUserModel).createContent(CMISUtil.DocumentType.TEXT_PLAIN); | ||||
|   | ||||
| @@ -25,6 +25,7 @@ | ||||
|  */ | ||||
| package org.alfresco.rest.rules; | ||||
|  | ||||
| import static org.alfresco.rest.requests.RuleSettings.IS_INHERITANCE_ENABLED; | ||||
| import static org.alfresco.utility.report.log.Step.STEP; | ||||
| import static org.testng.Assert.assertEquals; | ||||
|  | ||||
| @@ -38,10 +39,12 @@ import org.alfresco.rest.model.RestRuleModelsCollection; | ||||
| import org.alfresco.rest.model.RestRuleSetLinkModel; | ||||
| import org.alfresco.rest.model.RestRuleSetModel; | ||||
| import org.alfresco.rest.model.RestRuleSetModelsCollection; | ||||
| import org.alfresco.rest.model.RestRuleSettingsModel; | ||||
| import org.alfresco.utility.model.FolderModel; | ||||
| import org.alfresco.utility.model.SiteModel; | ||||
| import org.alfresco.utility.model.TestGroup; | ||||
| import org.alfresco.utility.model.UserModel; | ||||
| import org.springframework.http.HttpStatus; | ||||
| import org.testng.annotations.BeforeClass; | ||||
| import org.testng.annotations.Test; | ||||
|  | ||||
| @@ -71,26 +74,96 @@ public class GetInheritedRulesTests extends RestTest | ||||
|         STEP("Create a parent and child folder, each with inheriting rules"); | ||||
|         FolderModel parent = dataContent.usingUser(user).usingSite(site).createFolder(); | ||||
|         FolderModel child = dataContent.usingUser(user).usingResource(parent).createFolder(); | ||||
|         RestRuleModel parentRule = rulesUtils.createRuleModelWithDefaultValues(); | ||||
|         RestRuleModel parentRule = rulesUtils.createInheritableRuleModel(); | ||||
|         parentRule = restClient.authenticateUser(user).withPrivateAPI().usingNode(parent).usingDefaultRuleSet().createSingleRule(parentRule); | ||||
|         restClient.assertStatusCodeIs(HttpStatus.CREATED); | ||||
|  | ||||
|         RestRuleSettingsModel enabled = new RestRuleSettingsModel(); | ||||
|         enabled.setValue(true); | ||||
|         restClient.authenticateUser(user).withPrivateAPI().usingNode(child).usingRuleSetting(IS_INHERITANCE_ENABLED).updateSetting(enabled); | ||||
|         restClient.assertStatusCodeIs(HttpStatus.OK); | ||||
|  | ||||
|         RestRuleModel childRule = rulesUtils.createRuleModelWithDefaultValues(); | ||||
|         childRule = restClient.authenticateUser(user).withPrivateAPI().usingNode(child).usingDefaultRuleSet().createSingleRule(childRule); | ||||
|         restClient.assertStatusCodeIs(HttpStatus.CREATED); | ||||
|  | ||||
|         STEP("Get the rules in the default rule set for the child folder"); | ||||
|         RestRuleModelsCollection rules = restClient.authenticateUser(user).withPrivateAPI().usingNode(child).usingDefaultRuleSet().getListOfRules(); | ||||
|         restClient.assertStatusCodeIs(HttpStatus.OK); | ||||
|         rules.assertThat().entriesListContains("id", childRule.getId()) | ||||
|              .and().entriesListCountIs(1); | ||||
|  | ||||
|         STEP("Get the rules in the inherited rule set for the child folder"); | ||||
|         RestRuleSetModelsCollection ruleSets = restClient.authenticateUser(user).withPrivateAPI().usingNode(child).include("inclusionType").getListOfRuleSets(); | ||||
|         restClient.assertStatusCodeIs(HttpStatus.OK); | ||||
|         String inheritedRuleSetId = ruleSets.getEntries().stream() | ||||
|                                             .filter(ruleSet -> ruleSet.onModel().getInclusionType().equals("inherited")) | ||||
|                                             .findFirst().get().onModel().getId(); | ||||
|  | ||||
|         RestRuleModelsCollection inheritedRules = restClient.authenticateUser(user).withPrivateAPI().usingNode(child).usingRuleSet(inheritedRuleSetId).getListOfRules(); | ||||
|         restClient.assertStatusCodeIs(HttpStatus.OK); | ||||
|         inheritedRules.assertThat().entriesListContains("id", parentRule.getId()) | ||||
|                       .and().entriesListCountIs(1); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Check we get no (inherited) rules when inheritance is disabled in the child folder. | ||||
|      */ | ||||
|     @Test (groups = { TestGroup.REST_API, TestGroup.RULES, TestGroup.SANITY }) | ||||
|     public void getInheritedRules_childFolderInheritanceDisabled() | ||||
|     { | ||||
|         STEP("Create a parent and child folder, with inheritable parent rule"); | ||||
|         FolderModel parent = dataContent.usingUser(user).usingSite(site).createFolder(); | ||||
|         FolderModel child = dataContent.usingUser(user).usingResource(parent).createFolder(); | ||||
|         RestRuleModel parentRule = rulesUtils.createInheritableRuleModel(); | ||||
|         restClient.authenticateUser(user).withPrivateAPI().usingNode(parent).usingDefaultRuleSet().createSingleRule(parentRule); | ||||
|         restClient.assertStatusCodeIs(HttpStatus.CREATED); | ||||
|  | ||||
|         STEP("Disable inheritance in the child folder"); | ||||
|         RestRuleSettingsModel enabledInheritance = new RestRuleSettingsModel(); | ||||
|         enabledInheritance.setValue(false); | ||||
|         restClient.authenticateUser(user).withPrivateAPI().usingNode(child).usingRuleSetting(IS_INHERITANCE_ENABLED).updateSetting(enabledInheritance); | ||||
|         restClient.assertStatusCodeIs(HttpStatus.OK); | ||||
|  | ||||
|         STEP("The child folder should have no rule sets"); | ||||
|         RestRuleSetModelsCollection ruleSets = restClient.authenticateUser(user).withPrivateAPI().usingNode(child).getListOfRuleSets(); | ||||
|         restClient.assertStatusCodeIs(HttpStatus.OK); | ||||
|         ruleSets.assertThat().entriesListIsEmpty(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Check that non-inheritable rules owned by the parent folder are not found inside the child folder. | ||||
|      */ | ||||
|     @Test (groups = { TestGroup.REST_API, TestGroup.RULES, TestGroup.SANITY }) | ||||
|     public void inheritance_test() | ||||
|     { | ||||
|         STEP("Create a parent and child folder, with an inheritable and a non-inheritable parent rule"); | ||||
|         FolderModel parent = dataContent.usingUser(user).usingSite(site).createFolder(); | ||||
|         FolderModel child = dataContent.usingUser(user).usingResource(parent).createFolder(); | ||||
|  | ||||
|         RestRuleModel inheritableRule = rulesUtils.createInheritableRuleModel(); | ||||
|         inheritableRule = restClient.authenticateUser(user).withPrivateAPI().usingNode(parent).usingDefaultRuleSet().createSingleRule(inheritableRule); | ||||
|         restClient.assertStatusCodeIs(HttpStatus.CREATED); | ||||
|  | ||||
|         RestRuleModel nonInheritableRule = rulesUtils.createRuleModelWithDefaultValues(); | ||||
|         nonInheritableRule = restClient.authenticateUser(user).withPrivateAPI().usingNode(parent).usingDefaultRuleSet().createSingleRule(nonInheritableRule); | ||||
|         restClient.assertStatusCodeIs(HttpStatus.CREATED); | ||||
|  | ||||
|         STEP("The inherited rule set for the child folder should only return the inheritable rule"); | ||||
|         RestRuleSetModelsCollection ruleSets = restClient.authenticateUser(user).withPrivateAPI().usingNode(child).include("inclusionType").getListOfRuleSets(); | ||||
|         restClient.assertStatusCodeIs(HttpStatus.OK); | ||||
|  | ||||
|         String inheritedRuleSetId = ruleSets.getEntries().stream() | ||||
|                 .filter(ruleSet -> ruleSet.onModel().getInclusionType().equals("inherited")) | ||||
|                 .findFirst().get().onModel().getId(); | ||||
|  | ||||
|         RestRuleModelsCollection inheritedRules = restClient.authenticateUser(user).withPrivateAPI().usingNode(child).usingRuleSet(inheritedRuleSetId).getListOfRules(); | ||||
|         restClient.assertStatusCodeIs(HttpStatus.OK); | ||||
|         inheritedRules.assertThat().entriesListContains("id", inheritableRule.getId()) | ||||
|                              .and().entriesListDoesNotContain("id",nonInheritableRule.getId()) | ||||
|                              .and().entriesListCountIs(1); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Check that we only get each rule once with linking and inheritance, and the order is correct. | ||||
|      * <p> | ||||
| @@ -110,9 +183,14 @@ public class GetInheritedRulesTests extends RestTest | ||||
|         FolderModel folderB = dataContent.usingUser(user).usingResource(folderA).createFolder(); | ||||
|         FolderModel folderC = dataContent.usingUser(user).usingResource(folderB).createFolder(); | ||||
|         FolderModel folderD = dataContent.usingUser(user).usingResource(folderC).createFolder(); | ||||
|         RestRuleModel ruleB = restClient.authenticateUser(user).withPrivateAPI().usingNode(folderB).usingDefaultRuleSet().createSingleRule(rulesUtils.createRuleModelWithDefaultValues()); | ||||
|         RestRuleModel ruleC = restClient.authenticateUser(user).withPrivateAPI().usingNode(folderC).usingDefaultRuleSet().createSingleRule(rulesUtils.createRuleModelWithDefaultValues()); | ||||
|         RestRuleModel ruleB = restClient.authenticateUser(user).withPrivateAPI().usingNode(folderB).usingDefaultRuleSet().createSingleRule(rulesUtils.createInheritableRuleModel()); | ||||
|         RestRuleModel ruleC = restClient.authenticateUser(user).withPrivateAPI().usingNode(folderC).usingDefaultRuleSet().createSingleRule(rulesUtils.createInheritableRuleModel()); | ||||
|         RestRuleModel ruleD = restClient.authenticateUser(user).withPrivateAPI().usingNode(folderD).usingDefaultRuleSet().createSingleRule(rulesUtils.createRuleModelWithDefaultValues()); | ||||
|         RestRuleSettingsModel enabled = new RestRuleSettingsModel(); | ||||
|         enabled.setValue(true); | ||||
|         restClient.authenticateUser(user).withPrivateAPI().usingNode(folderC).usingRuleSetting(IS_INHERITANCE_ENABLED).updateSetting(enabled); | ||||
|         restClient.authenticateUser(user).withPrivateAPI().usingNode(folderD).usingRuleSetting(IS_INHERITANCE_ENABLED).updateSetting(enabled); | ||||
|  | ||||
|         STEP("Link folderA to ruleSetD"); | ||||
|         RestRuleSetLinkModel linkModel = new RestRuleSetLinkModel(); | ||||
|         linkModel.setId(folderD.getNodeRef()); | ||||
|   | ||||
| @@ -46,6 +46,7 @@ import org.alfresco.utility.model.FolderModel; | ||||
| import org.alfresco.utility.model.SiteModel; | ||||
| import org.alfresco.utility.model.TestGroup; | ||||
| import org.alfresco.utility.model.UserModel; | ||||
| import org.springframework.http.HttpStatus; | ||||
| import org.testng.annotations.BeforeClass; | ||||
| import org.testng.annotations.Test; | ||||
|  | ||||
| @@ -172,6 +173,56 @@ public class RuleSetLinksTests extends RestTest | ||||
|                 .get(0).onModel().assertThat().isEqualTo(expectedRuleSet); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Check we can link to a rule set when linking from a folder which has inherited rules. | ||||
|      */ | ||||
|     @Test(groups = {TestGroup.REST_API, TestGroup.RULES}) | ||||
|     public void linkFromFolderWithInheritedRules() | ||||
|     { | ||||
|         STEP("Create folders"); | ||||
|         final FolderModel parentFolder = dataContent.usingUser(user).usingSite(site).createFolder(); | ||||
|         final FolderModel childFolder = dataContent.usingUser(user).usingResource(parentFolder).createFolder(); | ||||
|         final FolderModel linkedToFolder = dataContent.usingUser(user).usingSite(site).createFolder(); | ||||
|  | ||||
|         STEP("Create rules in the parent folder and the linking folder"); | ||||
|         RestRuleModel parentRule = rulesUtils.createInheritableRuleModel(); | ||||
|         parentRule = restClient.authenticateUser(user).withPrivateAPI().usingNode(parentFolder).usingDefaultRuleSet().createSingleRule(parentRule); | ||||
|         restClient.assertStatusCodeIs(CREATED); | ||||
|  | ||||
|         RestRuleModel linkingFolderRule = rulesUtils.createRuleModelWithDefaultValues(); | ||||
|         restClient.authenticateUser(user).withPrivateAPI().usingNode(linkedToFolder).usingDefaultRuleSet().createSingleRule(linkingFolderRule); | ||||
|         restClient.assertStatusCodeIs(CREATED); | ||||
|  | ||||
|         STEP("Get the rule sets for the linking folder and find the rule set id"); | ||||
|         final RestRuleSetModelsCollection ruleSets = restClient.authenticateUser(user).withPrivateAPI().usingNode(linkedToFolder).getListOfRuleSets(); | ||||
|         restClient.assertStatusCodeIs(OK); | ||||
|         ruleSets.assertThat().entriesListCountIs(1); | ||||
|         final String ruleSetId = ruleSets.getEntries().get(0).onModel().getId(); | ||||
|  | ||||
|         STEP("Link the child folder to the target folder"); | ||||
|         final RestRuleSetLinkModel request = new RestRuleSetLinkModel(); | ||||
|         request.setId(linkedToFolder.getNodeRef()); | ||||
|         final RestRuleSetLinkModel ruleLink = restClient.authenticateUser(user).withPrivateAPI().usingNode(childFolder).createRuleLink(request); | ||||
|         restClient.assertStatusCodeIs(CREATED); | ||||
|  | ||||
|         STEP("Assert link result"); | ||||
|         final RestRuleSetLinkModel expectedLink = new RestRuleSetLinkModel(); | ||||
|         expectedLink.setId(ruleSetId); | ||||
|         ruleLink.assertThat().isEqualTo(expectedLink); | ||||
|  | ||||
|         STEP("Assert that the child folder has also inherited the parent rule"); | ||||
|         RestRuleSetModelsCollection ruleSetsInh = restClient.authenticateUser(user).withPrivateAPI().usingNode(childFolder).include("inclusionType").getListOfRuleSets(); | ||||
|         restClient.assertStatusCodeIs(OK); | ||||
|         String inheritedRuleSetId = ruleSetsInh.getEntries().stream() | ||||
|                 .filter(ruleSet -> ruleSet.onModel().getInclusionType().equals("inherited")) | ||||
|                 .findFirst().get().onModel().getId(); | ||||
|  | ||||
|         RestRuleModelsCollection inheritedRules = restClient.authenticateUser(user).withPrivateAPI().usingNode(childFolder).usingRuleSet(inheritedRuleSetId).getListOfRules(); | ||||
|         restClient.assertStatusCodeIs(OK); | ||||
|         inheritedRules.assertThat().entriesListContains("id", parentRule.getId()) | ||||
|                 .and().entriesListCountIs(1); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Check we get 404 when linking to a non-existing rule set/folder. | ||||
|      */ | ||||
|   | ||||
| @@ -213,6 +213,13 @@ public class RulesTestsUtils | ||||
|         return createRuleModel(RULE_NAME_DEFAULT); | ||||
|     } | ||||
|  | ||||
|     public RestRuleModel createInheritableRuleModel() | ||||
|     { | ||||
|         RestRuleModel ruleModel = createRuleModel(RULE_NAME_DEFAULT); | ||||
|         ruleModel.setIsInheritable(true); | ||||
|         return ruleModel; | ||||
|     } | ||||
|  | ||||
|     public RestRuleModel createRuleModel(String name) | ||||
|     { | ||||
|         return createRuleModel(name, List.of(createAddAudioAspectAction())); | ||||
|   | ||||
| @@ -0,0 +1,6 @@ | ||||
| <html> | ||||
| <head></head> | ||||
| <body> | ||||
| Hello ${args.name}! | ||||
| </body> | ||||
| </html> | ||||
| @@ -9,7 +9,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-tests</artifactId> | ||||
|         <version>20.11</version> | ||||
|         <version>20.28-SNAPSHOT</version> | ||||
|     </parent> | ||||
|  | ||||
|     <developers> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-packaging</artifactId> | ||||
|         <version>20.11</version> | ||||
|         <version>20.28-SNAPSHOT</version> | ||||
|     </parent> | ||||
|  | ||||
|     <properties> | ||||
|   | ||||
							
								
								
									
										28
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								pom.xml
									
									
									
									
									
								
							| @@ -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>20.11</version> | ||||
|     <version>20.28-SNAPSHOT</version> | ||||
|     <packaging>pom</packaging> | ||||
|     <name>Alfresco Community Repo Parent</name> | ||||
|  | ||||
| @@ -53,17 +53,17 @@ | ||||
|         <dependency.activiti.version>5.23.0</dependency.activiti.version> | ||||
|         <dependency.alfresco-transform-service.version>2.0.0</dependency.alfresco-transform-service.version> | ||||
|         <dependency.alfresco-transform-core.version>3.0.0</dependency.alfresco-transform-core.version> | ||||
|         <dependency.alfresco-greenmail.version>6.4</dependency.alfresco-greenmail.version> | ||||
|         <dependency.alfresco-greenmail.version>6.5</dependency.alfresco-greenmail.version> | ||||
|         <dependency.acs-event-model.version>0.0.16</dependency.acs-event-model.version> | ||||
|  | ||||
|         <dependency.spring.version>5.3.23</dependency.spring.version> | ||||
|         <dependency.antlr.version>3.5.3</dependency.antlr.version> | ||||
|         <dependency.jackson.version>2.14.0-rc1</dependency.jackson.version> | ||||
|         <dependency.cxf.version>3.5.3</dependency.cxf.version> | ||||
|         <dependency.jackson.version>2.14.0</dependency.jackson.version> | ||||
|         <dependency.cxf.version>3.5.4</dependency.cxf.version> | ||||
|         <dependency.opencmis.version>1.0.0</dependency.opencmis.version> | ||||
|         <dependency.webscripts.version>8.32</dependency.webscripts.version> | ||||
|         <dependency.bouncycastle.version>1.70</dependency.bouncycastle.version> | ||||
|         <dependency.mockito-core.version>4.6.1</dependency.mockito-core.version> | ||||
|         <dependency.mockito-core.version>4.9.0</dependency.mockito-core.version> | ||||
|         <dependency.assertj.version>3.23.1</dependency.assertj.version> | ||||
|         <dependency.org-json.version>20220320</dependency.org-json.version> | ||||
|         <dependency.commons-dbcp.version>2.9.0</dependency.commons-dbcp.version> | ||||
| @@ -77,7 +77,7 @@ | ||||
|         <dependency.gytheio.version>0.17</dependency.gytheio.version> | ||||
|         <dependency.groovy.version>3.0.12</dependency.groovy.version> | ||||
|         <dependency.tika.version>2.4.1</dependency.tika.version> | ||||
|         <dependency.spring-security.version>5.7.3</dependency.spring-security.version> | ||||
|         <dependency.spring-security.version>5.7.5</dependency.spring-security.version> | ||||
|         <dependency.truezip.version>7.7.10</dependency.truezip.version> | ||||
|         <dependency.poi.version>5.2.2</dependency.poi.version> | ||||
|         <dependency.poi-ooxml-lite.version>5.2.3</dependency.poi-ooxml-lite.version> | ||||
| @@ -88,11 +88,11 @@ | ||||
|         <dependency.netty.qpid.version>4.1.72.Final</dependency.netty.qpid.version> <!-- must be in sync with camels transitive dependencies: native-unix-common/native-epoll/native-kqueue --> | ||||
|         <dependency.netty-tcnative.version>2.0.53.Final</dependency.netty-tcnative.version> <!-- must be in sync with camels transitive dependencies --> | ||||
|         <dependency.activemq.version>5.17.1</dependency.activemq.version> | ||||
|         <dependency.apache-compress.version>1.21</dependency.apache-compress.version> | ||||
|         <dependency.apache-compress.version>1.22</dependency.apache-compress.version> | ||||
|         <dependency.apache.taglibs.version>1.2.5</dependency.apache.taglibs.version> | ||||
|         <dependency.awaitility.version>4.2.0</dependency.awaitility.version> | ||||
|         <dependency.swagger-ui.version>3.38.0</dependency.swagger-ui.version> | ||||
|         <dependency.swagger-parser.version>1.0.61</dependency.swagger-parser.version> | ||||
|         <dependency.swagger-parser.version>1.0.63</dependency.swagger-parser.version> | ||||
|         <dependency.maven-filtering.version>3.1.1</dependency.maven-filtering.version> | ||||
|         <dependency.maven-artifact.version>3.8.6</dependency.maven-artifact.version> | ||||
|         <dependency.jdom2.version>2.0.6.1</dependency.jdom2.version> | ||||
| @@ -109,7 +109,7 @@ | ||||
|         <dependency.jakarta-json-path.version>2.7.0</dependency.jakarta-json-path.version> | ||||
|         <dependency.jakarta-rpc-api.version>1.1.4</dependency.jakarta-rpc-api.version> | ||||
|  | ||||
|         <alfresco.googledrive.version>3.3.1-A2</alfresco.googledrive.version> | ||||
|         <alfresco.googledrive.version>3.3.1-A3</alfresco.googledrive.version> | ||||
|         <alfresco.aos-module.version>1.5.0</alfresco.aos-module.version> | ||||
|         <alfresco.api-explorer.version>7.3.0</alfresco.api-explorer.version> <!-- Also in alfresco-enterprise-share --> | ||||
|  | ||||
| @@ -123,7 +123,7 @@ | ||||
|         <dependency.tas-utility.version>3.0.56</dependency.tas-utility.version> | ||||
|         <dependency.rest-assured.version>5.2.0</dependency.rest-assured.version> | ||||
|         <dependency.tas-restapi.version>1.135</dependency.tas-restapi.version> | ||||
|         <dependency.tas-email.version>1.9</dependency.tas-email.version> | ||||
|         <dependency.tas-email.version>1.11</dependency.tas-email.version> | ||||
|         <dependency.tas-webdav.version>1.7</dependency.tas-webdav.version> | ||||
|         <dependency.tas-ftp.version>1.7</dependency.tas-ftp.version> | ||||
|         <dependency.tas-dataprep.version>2.6</dependency.tas-dataprep.version> | ||||
| @@ -149,7 +149,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>20.11</tag> | ||||
|         <tag>HEAD</tag> | ||||
|     </scm> | ||||
|  | ||||
|     <distributionManagement> | ||||
| @@ -651,7 +651,7 @@ | ||||
|             <dependency> | ||||
|                 <groupId>com.github.junrar</groupId> | ||||
|                 <artifactId>junrar</artifactId> | ||||
|                 <version>7.5.3</version> | ||||
|                 <version>7.5.4</version> | ||||
|             </dependency> | ||||
|             <dependency> | ||||
|                 <groupId>com.github.fge</groupId> | ||||
| @@ -736,7 +736,7 @@ | ||||
|             <dependency> | ||||
|                 <groupId>joda-time</groupId> | ||||
|                 <artifactId>joda-time</artifactId> | ||||
|                 <version>2.11.1</version> | ||||
|                 <version>2.12.1</version> | ||||
|             </dependency> | ||||
|  | ||||
|             <!-- provided dependencies --> | ||||
| @@ -936,7 +936,7 @@ | ||||
|                 </plugin> | ||||
|                 <plugin> | ||||
|                     <artifactId>maven-jar-plugin</artifactId> | ||||
|                     <version>3.2.2</version> | ||||
|                     <version>3.3.0</version> | ||||
|                 </plugin> | ||||
|                 <plugin> | ||||
|                     <artifactId>maven-war-plugin</artifactId> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo</artifactId> | ||||
|         <version>20.11</version> | ||||
|         <version>20.28-SNAPSHOT</version> | ||||
|     </parent> | ||||
|  | ||||
|     <dependencies> | ||||
|   | ||||
| @@ -363,13 +363,13 @@ public class ActionsImpl implements Actions | ||||
|                         Map.Entry::getValue)); | ||||
|     } | ||||
|  | ||||
|     private Map<String, Serializable> extractActionParams(org.alfresco.service.cmr.action.ActionDefinition actionDefinition, Map<String, String> params) | ||||
|     private Map<String, Serializable> extractActionParams(org.alfresco.service.cmr.action.ActionDefinition actionDefinition, Map<String, ?> params) | ||||
|     { | ||||
|         Map<String, Serializable> parameterValues = new HashMap<>(); | ||||
|  | ||||
|         try | ||||
|         { | ||||
|             for (Map.Entry<String, String> entry : params.entrySet()) | ||||
|             for (Map.Entry<String, ?> entry : params.entrySet()) | ||||
|             { | ||||
|                 String propertyName = entry.getKey(); | ||||
|                 Object propertyValue = entry.getValue(); | ||||
|   | ||||
| @@ -170,7 +170,7 @@ public class RuleSetsImpl implements RuleSets | ||||
|         } | ||||
|  | ||||
|         //The folder shouldn't have any pre-existing rules | ||||
|         if (ruleService.hasRules(folderNodeRef)) { | ||||
|         if (ruleService.hasNonInheritedRules(folderNodeRef)) { | ||||
|             throw new InvalidArgumentException("Unable to link to a rule set because the folder has pre-existing rules or is already linked to a rule set."); | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -26,17 +26,12 @@ | ||||
|  | ||||
| package org.alfresco.rest.api.impl.rules; | ||||
|  | ||||
| import java.io.Serializable; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| import org.alfresco.repo.action.ActionImpl; | ||||
| import org.alfresco.repo.action.access.ActionAccessRestriction; | ||||
| import org.alfresco.repo.action.executer.ExecuteAllRulesActionExecuter; | ||||
| import org.alfresco.rest.api.Rules; | ||||
| import org.alfresco.rest.api.model.mapper.RestModelMapper; | ||||
| import org.alfresco.rest.api.model.rules.InclusionType; | ||||
| import org.alfresco.rest.api.model.rules.Rule; | ||||
| import org.alfresco.rest.api.model.rules.RuleExecution; | ||||
| import org.alfresco.rest.api.model.rules.RuleSet; | ||||
| @@ -53,6 +48,14 @@ import org.apache.commons.collections.CollectionUtils; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| import java.io.Serializable; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| import static org.alfresco.rest.api.impl.rules.RuleSetLoader.INCLUSION_TYPE; | ||||
|  | ||||
| @Experimental | ||||
| public class RulesImpl implements Rules | ||||
| { | ||||
| @@ -63,6 +66,7 @@ public class RulesImpl implements Rules | ||||
|     private RuleService ruleService; | ||||
|     private NodeValidator validator; | ||||
|     private RuleLoader ruleLoader; | ||||
|     private RuleSetLoader ruleSetLoader; | ||||
|     private ActionPermissionValidator actionPermissionValidator; | ||||
|     private RestModelMapper<Rule, org.alfresco.service.cmr.rule.Rule> ruleMapper; | ||||
|  | ||||
| @@ -75,8 +79,10 @@ public class RulesImpl implements Rules | ||||
|         final NodeRef folderNodeRef = validator.validateFolderNode(folderNodeId, false); | ||||
|         NodeRef ruleSetNode = validator.validateRuleSetNode(ruleSetId, folderNodeRef); | ||||
|         NodeRef owningFolder = ruleService.getOwningNodeRef(ruleSetNode); | ||||
|         RuleSet ruleSet = ruleSetLoader.loadRuleSet(ruleSetNode, folderNodeRef, List.of(INCLUSION_TYPE)); | ||||
|  | ||||
|         final List<Rule> rules = ruleService.getRules(owningFolder, false).stream() | ||||
|                 .filter(ruleModel -> ruleSet.getInclusionType() != InclusionType.INHERITED || ruleModel.isAppliedToChildren()) | ||||
|                 .map(ruleModel -> ruleLoader.loadRule(ruleModel, includes)) | ||||
|                 .collect(Collectors.toList()); | ||||
|  | ||||
| @@ -182,6 +188,11 @@ public class RulesImpl implements Rules | ||||
|         this.ruleLoader = ruleLoader; | ||||
|     } | ||||
|  | ||||
|     public void setRuleSetLoader(RuleSetLoader ruleSetLoader) | ||||
|     { | ||||
|         this.ruleSetLoader = ruleSetLoader; | ||||
|     } | ||||
|  | ||||
|     public void setActionPermissionValidator(ActionPermissionValidator actionPermissionValidator) | ||||
|     { | ||||
|         this.actionPermissionValidator = actionPermissionValidator; | ||||
|   | ||||
| @@ -32,7 +32,7 @@ public class Action | ||||
|     private String id; | ||||
|     private String actionDefinitionId; | ||||
|     private String targetId; | ||||
|     Map<String, String> params; | ||||
|     private Map<String, ?> params; | ||||
|  | ||||
|     public String getId() | ||||
|     { | ||||
| @@ -64,12 +64,12 @@ public class Action | ||||
|         this.targetId = targetId; | ||||
|     } | ||||
|  | ||||
|     public Map<String, String> getParams() | ||||
|     public Map<String, ?> getParams() | ||||
|     { | ||||
|         return params; | ||||
|     } | ||||
|  | ||||
|     public void setParams(Map<String, String> params) | ||||
|     public void setParams(Map<String, ? extends Object> params) | ||||
|     { | ||||
|         this.params = params; | ||||
|     } | ||||
|   | ||||
| @@ -929,6 +929,7 @@ | ||||
|         <property name="validator" ref="nodeValidator"/> | ||||
|         <property name="ruleService" ref="RuleService" /> | ||||
|         <property name="ruleLoader" ref="ruleLoader"/> | ||||
|         <property name="ruleSetLoader" ref="ruleSetLoader"/> | ||||
|         <property name="actionPermissionValidator" ref="actionPermissionValidator"/> | ||||
|         <property name="ruleMapper" ref="ruleMapper"/> | ||||
|     </bean> | ||||
|   | ||||
| @@ -3,12 +3,13 @@ function main() | ||||
|    // Get the args | ||||
|    var siteShortName = url.templateArgs.shortname, | ||||
|       site = siteService.getSite(siteShortName), | ||||
|       filter = ((args.filter != null) ? args.filter : (args.shortNameFilter != null) ? args.shortNameFilter : "" )+ " [hint:useCQ]", | ||||
|       maxResults = (args.maxResults == null) ? 10 : parseInt(args.maxResults, 10), | ||||
|       authorityType = args.authorityType, | ||||
|       zone = args.zone, | ||||
|       sortBy = args.sortBy, | ||||
|       sortAsc = args.dir != "desc"; | ||||
|     | ||||
|   var filter; | ||||
|  | ||||
|  | ||||
|    if (authorityType != null) | ||||
| @@ -28,6 +29,7 @@ function main() | ||||
|  | ||||
|    if (authorityType == null || authorityType == "USER") | ||||
|    { | ||||
|       filter = ((args.filter != null) ? args.filter : (args.shortNameFilter != null) ? args.shortNameFilter : "" )+ " [hint:useCQ]"; | ||||
|       // Get the collection of people | ||||
|       peopleFound = sortBy != null ? people.getPeople(filter, maxResults, sortBy, sortAsc) : people.getPeople(filter, maxResults); | ||||
|  | ||||
| @@ -67,6 +69,7 @@ function main() | ||||
|  | ||||
|    if (authorityType == null || authorityType == "GROUP") | ||||
|    { | ||||
|       filter = (args.filter != null) ? args.filter : (args.shortNameFilter != null) ? args.shortNameFilter : ""; | ||||
|       // Get the collection of groups | ||||
|       paging = utils.createPaging(maxResults, -1); | ||||
|       groupsFound = groups.getGroupsInZone(filter, zone, paging, "displayName"); | ||||
| @@ -96,4 +99,4 @@ function contains(arr, value) { | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| main(); | ||||
| main(); | ||||
|   | ||||
| @@ -257,7 +257,7 @@ public class RuleSetsImplTest extends TestCase | ||||
|         String actual = ruleSets.linkToRuleSet(FOLDER_ID,LINK_TO_NODE_ID).getId(); | ||||
|  | ||||
|         then(ruleServiceMock).should().hasRules(LINK_TO_NODE); | ||||
|         then(ruleServiceMock).should().hasRules(FOLDER_NODE); | ||||
|         then(ruleServiceMock).should().hasNonInheritedRules(FOLDER_NODE); | ||||
|         then(runtimeRuleServiceMock).should().getSavedRuleFolderAssoc(LINK_TO_NODE); | ||||
|         then(runtimeRuleServiceMock).shouldHaveNoMoreInteractions(); | ||||
|         then(nodeServiceMock).should().addChild(FOLDER_NODE, childNodeRef, RuleModel.ASSOC_RULE_FOLDER, RuleModel.ASSOC_RULE_FOLDER); | ||||
| @@ -284,7 +284,7 @@ public class RuleSetsImplTest extends TestCase | ||||
|         then(nodeValidatorMock).should().validateRuleSetNode(LINK_TO_NODE_ID,false); | ||||
|         then(nodeValidatorMock).shouldHaveNoMoreInteractions(); | ||||
|         then(ruleServiceMock).should().hasRules(LINK_TO_NODE); | ||||
|         then(ruleServiceMock).should().hasRules(FOLDER_NODE); | ||||
|         then(ruleServiceMock).should().hasNonInheritedRules(FOLDER_NODE); | ||||
|         then(runtimeRuleServiceMock).should().getSavedRuleFolderAssoc(LINK_TO_NODE); | ||||
|         then(runtimeRuleServiceMock).shouldHaveNoMoreInteractions(); | ||||
|         then(nodeServiceMock).should().addChild(FOLDER_NODE, childNodeRef, RuleModel.ASSOC_RULE_FOLDER, RuleModel.ASSOC_RULE_FOLDER); | ||||
| @@ -312,7 +312,8 @@ public class RuleSetsImplTest extends TestCase | ||||
|     @Test | ||||
|     public void testLinkToRuleSet_folderShouldntHavePreExistingRules() | ||||
|     { | ||||
|         given(ruleServiceMock.hasRules(any(NodeRef.class))).willReturn(true, true); | ||||
|         given(ruleServiceMock.hasRules(any(NodeRef.class))).willReturn(true); | ||||
|         given(ruleServiceMock.hasNonInheritedRules(any(NodeRef.class))).willReturn(true); | ||||
|  | ||||
|         //when | ||||
|         assertThatExceptionOfType(InvalidArgumentException.class).isThrownBy( | ||||
| @@ -320,7 +321,7 @@ public class RuleSetsImplTest extends TestCase | ||||
|  | ||||
|         then(nodeServiceMock).shouldHaveNoMoreInteractions(); | ||||
|         then(ruleServiceMock).should().hasRules(LINK_TO_NODE); | ||||
|         then(ruleServiceMock).should().hasRules(FOLDER_NODE); | ||||
|         then(ruleServiceMock).should().hasNonInheritedRules(FOLDER_NODE); | ||||
|         then(ruleServiceMock).shouldHaveNoMoreInteractions(); | ||||
|         then(runtimeRuleServiceMock).shouldHaveNoInteractions(); | ||||
|     } | ||||
|   | ||||
| @@ -28,6 +28,7 @@ package org.alfresco.rest.api.impl.rules; | ||||
|  | ||||
| import static java.util.Collections.emptyList; | ||||
|  | ||||
| import static org.alfresco.rest.api.impl.rules.RuleSetLoader.INCLUSION_TYPE; | ||||
| import static org.alfresco.rest.api.model.rules.RuleSet.DEFAULT_ID; | ||||
| import static org.assertj.core.api.Assertions.assertThat; | ||||
| import static org.assertj.core.api.Assertions.assertThatExceptionOfType; | ||||
| @@ -51,8 +52,10 @@ import org.alfresco.repo.action.executer.ExecuteAllRulesActionExecuter; | ||||
| import org.alfresco.rest.api.Nodes; | ||||
| import org.alfresco.rest.api.model.mapper.RestModelMapper; | ||||
| import org.alfresco.rest.api.model.rules.Action; | ||||
| import org.alfresco.rest.api.model.rules.InclusionType; | ||||
| import org.alfresco.rest.api.model.rules.Rule; | ||||
| import org.alfresco.rest.api.model.rules.RuleExecution; | ||||
| import org.alfresco.rest.api.model.rules.RuleSet; | ||||
| import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException; | ||||
| import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException; | ||||
| import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException; | ||||
| @@ -80,6 +83,7 @@ public class RulesImplTest extends TestCase | ||||
|     private static final String FOLDER_NODE_ID = "dummy-folder-node-id"; | ||||
|     private static final String RULE_SET_ID = "dummy-rule-set-id"; | ||||
|     private static final String RULE_ID = "dummy-rule-id"; | ||||
|     private static final String RULE_ID_INHERITED = "dummy-rule-id-inherited"; | ||||
|     private static final NodeRef FOLDER_NODE_REF = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, FOLDER_NODE_ID); | ||||
|     private static final NodeRef RULE_SET_NODE_REF = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, RULE_SET_ID); | ||||
|     private static final NodeRef RULE_NODE_REF = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, RULE_ID); | ||||
| @@ -101,15 +105,20 @@ public class RulesImplTest extends TestCase | ||||
|     @Mock | ||||
|     private RuleLoader ruleLoaderMock; | ||||
|     @Mock | ||||
|     private RuleSetLoader ruleSetLoaderMock; | ||||
|     @Mock | ||||
|     private ActionPermissionValidator actionPermissionValidatorMock; | ||||
|     @Mock | ||||
|     private org.alfresco.service.cmr.rule.Rule serviceRuleMock; | ||||
|     @Mock | ||||
|     private Rule ruleMock; | ||||
|     @Mock | ||||
|     private RuleSet ruleSetMock; | ||||
|     @Mock | ||||
|     private Action actionMock; | ||||
|  | ||||
|     private org.alfresco.service.cmr.rule.Rule ruleModel = createRule(RULE_ID); | ||||
|     private org.alfresco.service.cmr.rule.Rule ruleModelInherited = createRule(RULE_ID_INHERITED); | ||||
|  | ||||
|     @InjectMocks | ||||
|     private RulesImpl rules; | ||||
| @@ -118,6 +127,9 @@ public class RulesImplTest extends TestCase | ||||
|     @Override | ||||
|     public void setUp() throws Exception | ||||
|     { | ||||
|         ruleModel.applyToChildren(true); | ||||
|         ruleModelInherited.applyToChildren(true); | ||||
|  | ||||
|         given(nodeValidatorMock.validateFolderNode(any(), anyBoolean())).willReturn(FOLDER_NODE_REF); | ||||
|         given(nodeValidatorMock.validateRuleSetNode(any(), any())).willReturn(RULE_SET_NODE_REF); | ||||
|         given(nodeValidatorMock.validateRuleNode(any(), any())).willReturn(RULE_NODE_REF); | ||||
| @@ -126,13 +138,15 @@ public class RulesImplTest extends TestCase | ||||
|         given(ruleServiceMock.getRules(FOLDER_NODE_REF, false)).willReturn(List.of(ruleModel)); | ||||
|         given(ruleServiceMock.getOwningNodeRef(RULE_SET_NODE_REF)).willReturn(FOLDER_NODE_REF); | ||||
|  | ||||
|         given(ruleSetMock.getInclusionType()).willReturn(InclusionType.INHERITED); | ||||
|  | ||||
|         given(ruleLoaderMock.loadRule(ruleModel, INCLUDE)).willReturn(ruleMock); | ||||
|         given(ruleSetLoaderMock.loadRuleSet(RULE_SET_NODE_REF, FOLDER_NODE_REF, List.of(INCLUSION_TYPE))).willReturn(ruleSetMock); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     public void testGetRules() | ||||
|     { | ||||
|         given(ruleLoaderMock.loadRule(ruleModel, emptyList())).willReturn(ruleMock); | ||||
|  | ||||
|         // when | ||||
|         final CollectionWithPagingInfo<Rule> rulesPage = rules.getRules(FOLDER_NODE_ID, RULE_SET_ID, INCLUDE, PAGING); | ||||
| @@ -141,6 +155,66 @@ public class RulesImplTest extends TestCase | ||||
|         then(nodeValidatorMock).should().validateRuleSetNode(RULE_SET_ID, FOLDER_NODE_REF); | ||||
|         then(nodeValidatorMock).shouldHaveNoMoreInteractions(); | ||||
|         then(ruleServiceMock).should().getOwningNodeRef(RULE_SET_NODE_REF); | ||||
|         then(ruleSetLoaderMock).should().loadRuleSet(RULE_SET_NODE_REF, FOLDER_NODE_REF, List.of(INCLUSION_TYPE)); | ||||
|         then(ruleSetLoaderMock).shouldHaveNoMoreInteractions(); | ||||
|         then(ruleServiceMock).should().getRules(FOLDER_NODE_REF, false); | ||||
|         then(ruleServiceMock).shouldHaveNoMoreInteractions(); | ||||
|         then(ruleLoaderMock).should().loadRule(ruleModel, emptyList()); | ||||
|         then(ruleLoaderMock).shouldHaveNoMoreInteractions(); | ||||
|         assertThat(rulesPage) | ||||
|                 .isNotNull() | ||||
|                 .extracting(CollectionWithPagingInfo::getCollection) | ||||
|                 .isNotNull() | ||||
|                 .extracting(Collection::size) | ||||
|                 .isEqualTo(1); | ||||
|         assertThat(rulesPage.getCollection().stream().findFirst().get()).isEqualTo(ruleMock); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     public void testGetRules_ruleNotAppliedToChildren() | ||||
|     { | ||||
|         given(ruleSetMock.getInclusionType()).willReturn(InclusionType.INHERITED); | ||||
|         given(ruleServiceMock.getRules(FOLDER_NODE_REF, false)).willReturn(List.of(ruleModel, ruleModelInherited)); | ||||
|         ruleModelInherited.applyToChildren(false); | ||||
|  | ||||
|         // when | ||||
|         final CollectionWithPagingInfo<Rule> rulesPage = rules.getRules(FOLDER_NODE_ID, RULE_SET_ID, INCLUDE, PAGING); | ||||
|  | ||||
|         then(nodeValidatorMock).should().validateFolderNode(FOLDER_NODE_ID, false); | ||||
|         then(nodeValidatorMock).should().validateRuleSetNode(RULE_SET_ID, FOLDER_NODE_REF); | ||||
|         then(nodeValidatorMock).shouldHaveNoMoreInteractions(); | ||||
|         then(ruleServiceMock).should().getOwningNodeRef(RULE_SET_NODE_REF); | ||||
|         then(ruleSetLoaderMock).should().loadRuleSet(RULE_SET_NODE_REF, FOLDER_NODE_REF, List.of(INCLUSION_TYPE)); | ||||
|         then(ruleSetLoaderMock).shouldHaveNoMoreInteractions(); | ||||
|         then(ruleServiceMock).should().getRules(FOLDER_NODE_REF, false); | ||||
|         then(ruleServiceMock).shouldHaveNoMoreInteractions(); | ||||
|         then(ruleLoaderMock).should().loadRule(ruleModel, emptyList()); | ||||
|         then(ruleLoaderMock).shouldHaveNoMoreInteractions(); | ||||
|         assertThat(rulesPage) | ||||
|                 .isNotNull() | ||||
|                 .extracting(CollectionWithPagingInfo::getCollection) | ||||
|                 .isNotNull() | ||||
|                 .extracting(Collection::size) | ||||
|                 .isEqualTo(1); | ||||
|         assertThat(rulesPage.getCollection().stream().findFirst().get()).isEqualTo(ruleMock); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     public void testGetRules_inheritedRuleSet() | ||||
|     { | ||||
|         given(ruleSetMock.getInclusionType()).willReturn(InclusionType.INHERITED); | ||||
|         ruleModelInherited.applyToChildren(false); | ||||
|         given(ruleServiceMock.getRules(FOLDER_NODE_REF, false)).willReturn(List.of(ruleModel, ruleModelInherited)); | ||||
|  | ||||
|         // when | ||||
|         final CollectionWithPagingInfo<Rule> rulesPage = rules.getRules(FOLDER_NODE_ID, RULE_SET_ID, INCLUDE, PAGING); | ||||
|  | ||||
|         then(nodeValidatorMock).should().validateFolderNode(FOLDER_NODE_ID, false); | ||||
|         then(nodeValidatorMock).should().validateRuleSetNode(RULE_SET_ID, FOLDER_NODE_REF); | ||||
|         then(nodeValidatorMock).shouldHaveNoMoreInteractions(); | ||||
|         then(ruleServiceMock).should().getOwningNodeRef(RULE_SET_NODE_REF); | ||||
|         then(ruleSetLoaderMock).should().loadRuleSet(RULE_SET_NODE_REF, FOLDER_NODE_REF, List.of(INCLUSION_TYPE)); | ||||
|         then(ruleSetLoaderMock).shouldHaveNoMoreInteractions(); | ||||
|         then(ruleServiceMock).should().getRules(FOLDER_NODE_REF, false); | ||||
|         then(ruleServiceMock).shouldHaveNoMoreInteractions(); | ||||
|         then(ruleLoaderMock).should().loadRule(ruleModel, emptyList()); | ||||
| @@ -163,6 +237,8 @@ public class RulesImplTest extends TestCase | ||||
|         final CollectionWithPagingInfo<Rule> rulesPage = rules.getRules(FOLDER_NODE_ID, RULE_SET_ID, INCLUDE, PAGING); | ||||
|  | ||||
|         then(ruleServiceMock).should().getOwningNodeRef(RULE_SET_NODE_REF); | ||||
|         then(ruleSetLoaderMock).should().loadRuleSet(RULE_SET_NODE_REF, FOLDER_NODE_REF, List.of(INCLUSION_TYPE)); | ||||
|         then(ruleSetLoaderMock).shouldHaveNoMoreInteractions(); | ||||
|         then(ruleServiceMock).should().getRules(FOLDER_NODE_REF, false); | ||||
|         then(ruleServiceMock).shouldHaveNoMoreInteractions(); | ||||
|         assertThat(rulesPage) | ||||
|   | ||||
| @@ -78,7 +78,7 @@ public class Action extends org.alfresco.rest.api.model.Action implements Serial | ||||
|         String id = (String) jsonObject.get("id"); | ||||
|         String actionDefinitionId = (String) jsonObject.get("actionDefinitionId"); | ||||
|         String targetId = (String) jsonObject.get("targetId"); | ||||
|         Map<String, String> params = (Map<String, String>) jsonObject.get("params"); | ||||
|         Map<String, Object> params = (Map<String, Object>) jsonObject.get("params"); | ||||
|  | ||||
|         Action action = new Action(); | ||||
|         action.setId(id); | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo</artifactId> | ||||
|         <version>20.11</version> | ||||
|         <version>20.28-SNAPSHOT</version> | ||||
|     </parent> | ||||
|  | ||||
|     <dependencies> | ||||
| @@ -126,7 +126,7 @@ | ||||
|         <dependency> | ||||
|             <groupId>com.ibm.icu</groupId> | ||||
|             <artifactId>icu4j</artifactId> | ||||
|             <version>71.1</version> | ||||
|             <version>72.1</version> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>com.googlecode.json-simple</groupId> | ||||
| @@ -374,7 +374,7 @@ | ||||
|         <dependency> | ||||
|             <groupId>com.fasterxml.woodstox</groupId> | ||||
|             <artifactId>woodstox-core</artifactId> | ||||
|             <version>6.3.1</version> | ||||
|             <version>6.4.0</version> | ||||
|         </dependency> | ||||
|  | ||||
|         <!-- GData --> | ||||
|   | ||||
| @@ -25,6 +25,10 @@ | ||||
|  */ | ||||
| package org.alfresco.repo.content.transform; | ||||
|  | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.util.StringJoiner; | ||||
|  | ||||
| import org.alfresco.error.AlfrescoRuntimeException; | ||||
| import org.alfresco.service.cmr.repository.ContentReader; | ||||
| import org.alfresco.service.cmr.repository.ContentWriter; | ||||
| @@ -44,9 +48,6 @@ import org.apache.http.impl.client.CloseableHttpClient; | ||||
| import org.apache.http.impl.client.HttpClients; | ||||
| import org.apache.http.util.EntityUtils; | ||||
|  | ||||
| import java.io.IOException; | ||||
| import java.util.StringJoiner; | ||||
|  | ||||
| /** | ||||
|  * Client class that transfers content (from a ContentReader) to a remote transformation agent together with | ||||
|  * request parameters that will be used to transform the content. The transformed content is then returned and | ||||
| @@ -86,44 +87,33 @@ public class RemoteTransformerClient | ||||
|     } | ||||
|  | ||||
|     public void request(ContentReader reader, ContentWriter writer, String sourceMimetype, String sourceExtension, | ||||
|                         String targetExtension, long timeoutMs, Log logger, String... args) | ||||
|             String targetExtension, long timeoutMs, Log logger, String... args) | ||||
|     { | ||||
|  | ||||
|         if (args.length % 2 != 0) | ||||
|         { | ||||
|             throw new IllegalArgumentException("There should be a value for each request property"); | ||||
|         } | ||||
|  | ||||
|         StringJoiner sj = new StringJoiner(" "); | ||||
|         HttpEntity reqEntity = getRequestEntity(reader, sourceMimetype, sourceExtension, targetExtension, timeoutMs, args, sj); | ||||
|  | ||||
|         request(logger, sourceExtension, targetExtension, reqEntity, writer, sj.toString()); | ||||
|         try (InputStream contentStream = reader.getContentInputStream()) | ||||
|         { | ||||
|             HttpEntity reqEntity = getRequestEntity(contentStream, sourceMimetype, sourceExtension, targetExtension, timeoutMs, | ||||
|                     args, sj); | ||||
|  | ||||
|             request(logger, sourceExtension, targetExtension, reqEntity, writer, sj.toString()); | ||||
|         } | ||||
|         catch (IOException e) | ||||
|         { | ||||
|             throw new AlfrescoRuntimeException("Failed to read content from reader", e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     HttpEntity getRequestEntity(ContentReader reader, String sourceMimetype, String sourceExtension, | ||||
|                                         String targetExtension, long timeoutMs, String[] args, StringJoiner sj) | ||||
|     HttpEntity getRequestEntity(ContentReader reader, String sourceMimetype, String sourceExtension, String targetExtension, | ||||
|             long timeoutMs, String[] args, StringJoiner sj) | ||||
|     { | ||||
|         MultipartEntityBuilder builder = MultipartEntityBuilder.create(); | ||||
|         ContentType contentType = ContentType.create(sourceMimetype); | ||||
|         builder.addBinaryBody("file", reader.getContentInputStream(), contentType, "tmp."+sourceExtension); | ||||
|         builder.addTextBody("targetExtension", targetExtension); | ||||
|         sj.add("targetExtension" + '=' + targetExtension); | ||||
|         for (int i=0; i< args.length; i+=2) | ||||
|         { | ||||
|             if (args[i+1] != null) | ||||
|             { | ||||
|                 builder.addTextBody(args[i], args[i + 1]); | ||||
|  | ||||
|                 sj.add(args[i] + '=' + args[i + 1]); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (timeoutMs > 0) | ||||
|         { | ||||
|             String timeoutMsString = Long.toString(timeoutMs); | ||||
|             builder.addTextBody("timeout", timeoutMsString); | ||||
|             sj.add("timeout=" + timeoutMsString); | ||||
|         } | ||||
|         return builder.build(); | ||||
|         return getRequestEntity(reader.getContentInputStream(), sourceMimetype, sourceExtension, targetExtension, timeoutMs, args, sj); | ||||
|     } | ||||
|  | ||||
|     void request(Log logger, String sourceExtension, String targetExtension, HttpEntity reqEntity, ContentWriter writer, String args) | ||||
| @@ -331,6 +321,33 @@ public class RemoteTransformerClient | ||||
|         return httpclient.execute(httpGet); | ||||
|     } | ||||
|  | ||||
|     private HttpEntity getRequestEntity(InputStream contentStream, String sourceMimetype, String sourceExtension, | ||||
|             String targetExtension, long timeoutMs, String[] args, StringJoiner sj) | ||||
|     { | ||||
|         MultipartEntityBuilder builder = MultipartEntityBuilder.create(); | ||||
|         ContentType contentType = ContentType.create(sourceMimetype); | ||||
|         builder.addBinaryBody("file", contentStream, contentType, "tmp." + sourceExtension); | ||||
|         builder.addTextBody("targetExtension", targetExtension); | ||||
|         sj.add("targetExtension" + '=' + targetExtension); | ||||
|         for (int i = 0; i < args.length; i += 2) | ||||
|         { | ||||
|             if (args[i + 1] != null) | ||||
|             { | ||||
|                 builder.addTextBody(args[i], args[i + 1]); | ||||
|  | ||||
|                 sj.add(args[i] + '=' + args[i + 1]); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (timeoutMs > 0) | ||||
|         { | ||||
|             String timeoutMsString = Long.toString(timeoutMs); | ||||
|             builder.addTextBody("timeout", timeoutMsString); | ||||
|             sj.add("timeout=" + timeoutMsString); | ||||
|         } | ||||
|         return builder.build(); | ||||
|     } | ||||
|  | ||||
|     // Strip out just the error message in the response | ||||
|     private String getErrorMessage(HttpEntity resEntity) throws IOException | ||||
|     { | ||||
|   | ||||
| @@ -0,0 +1,201 @@ | ||||
| /* | ||||
|  * #%L | ||||
|  * Alfresco Repository | ||||
|  * %% | ||||
|  * Copyright (C) 2005 - 2022 Alfresco Software Limited | ||||
|  * %% | ||||
|  * This file is part of the Alfresco software.  | ||||
|  * If the software was purchased under a paid Alfresco license, the terms of  | ||||
|  * the paid license agreement will prevail.  Otherwise, the software is  | ||||
|  * provided under the following open source license terms: | ||||
|  *  | ||||
|  * Alfresco is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Lesser General Public License as published by | ||||
|  * the Free Software Foundation, either version 3 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  *  | ||||
|  * Alfresco is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Lesser General Public License for more details. | ||||
|  *  | ||||
|  * You should have received a copy of the GNU Lesser General Public License | ||||
|  * along with Alfresco. If not, see <http://www.gnu.org/licenses/>. | ||||
|  * #L% | ||||
|  */ | ||||
| package org.alfresco.repo.jscript; | ||||
|  | ||||
| import org.apache.commons.logging.Log; | ||||
| import org.apache.commons.logging.LogFactory; | ||||
| import org.mozilla.javascript.Callable; | ||||
| import org.mozilla.javascript.Context; | ||||
| import org.mozilla.javascript.ContextFactory; | ||||
| import org.mozilla.javascript.Scriptable; | ||||
|  | ||||
| /** | ||||
|  * Custom factory that allows to apply configured limits during script executions | ||||
|  *  | ||||
|  * @see ContextFactory | ||||
|  */ | ||||
| public class AlfrescoContextFactory extends ContextFactory | ||||
| { | ||||
|     private static final Log LOGGER = LogFactory.getLog(AlfrescoContextFactory.class); | ||||
|  | ||||
|     private int optimizationLevel = -1; | ||||
|     private int maxScriptExecutionSeconds = -1; | ||||
|     private int maxStackDepth = -1; | ||||
|     private long maxMemoryUsedInBytes = -1L; | ||||
|     private int observeInstructionCount = -1; | ||||
|  | ||||
|     private AlfrescoScriptThreadMxBeanWrapper threadMxBeanWrapper; | ||||
|  | ||||
|     private final int INTERPRETIVE_MODE = -1; | ||||
|  | ||||
|     @Override | ||||
|     protected Context makeContext() | ||||
|     { | ||||
|         AlfrescoScriptContext context = new AlfrescoScriptContext(); | ||||
|  | ||||
|         context.setOptimizationLevel(optimizationLevel); | ||||
|  | ||||
|         // Needed for both time and memory measurement | ||||
|         if (maxScriptExecutionSeconds > 0 || maxMemoryUsedInBytes > 0L) | ||||
|         { | ||||
|             if (observeInstructionCount > 0) | ||||
|             { | ||||
|                 LOGGER.info("Enabling observer count..."); | ||||
|                 context.setGenerateObserverCount(true); | ||||
|                 context.setInstructionObserverThreshold(observeInstructionCount); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 LOGGER.info("Disabling observer count..."); | ||||
|                 context.setGenerateObserverCount(false); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Memory limit | ||||
|         if (maxMemoryUsedInBytes > 0) | ||||
|         { | ||||
|             context.setThreadId(Thread.currentThread().getId()); | ||||
|         } | ||||
|  | ||||
|         // Max stack depth | ||||
|         if (maxStackDepth > 0) | ||||
|         { | ||||
|             if (optimizationLevel != INTERPRETIVE_MODE) | ||||
|             { | ||||
|                 LOGGER.warn("Changing optimization level from " + optimizationLevel + " to " + INTERPRETIVE_MODE); | ||||
|             } | ||||
|             // stack depth can only be set when no optimizations are applied | ||||
|             context.setOptimizationLevel(INTERPRETIVE_MODE); | ||||
|             context.setMaximumInterpreterStackDepth(maxStackDepth); | ||||
|         } | ||||
|  | ||||
|         return context; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void observeInstructionCount(Context cx, int instructionCount) | ||||
|     { | ||||
|         AlfrescoScriptContext acx = (AlfrescoScriptContext) cx; | ||||
|  | ||||
|         if (acx.isLimitsEnabled()) | ||||
|         { | ||||
|             // Time limit | ||||
|             if (maxScriptExecutionSeconds > 0) | ||||
|             { | ||||
|                 long currentTime = System.currentTimeMillis(); | ||||
|                 if (currentTime - acx.getStartTime() > maxScriptExecutionSeconds * 1000) | ||||
|                 { | ||||
|                     throw new Error("Maximum script time of " + maxScriptExecutionSeconds + " seconds exceeded"); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // Memory | ||||
|             if (maxMemoryUsedInBytes > 0 && threadMxBeanWrapper != null && threadMxBeanWrapper.isThreadAllocatedMemorySupported()) | ||||
|             { | ||||
|  | ||||
|                 if (acx.getStartMemory() <= 0) | ||||
|                 { | ||||
|                     acx.setStartMemory(threadMxBeanWrapper.getThreadAllocatedBytes(acx.getThreadId())); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     long currentAllocatedBytes = threadMxBeanWrapper.getThreadAllocatedBytes(acx.getThreadId()); | ||||
|                     if (currentAllocatedBytes - acx.getStartMemory() >= maxMemoryUsedInBytes) | ||||
|                     { | ||||
|                         throw new Error("Memory limit of " + maxMemoryUsedInBytes + " bytes reached"); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected Object doTopCall(Callable callable, Context cx, Scriptable scope, Scriptable thisObj, Object[] args) | ||||
|     { | ||||
|         AlfrescoScriptContext acx = (AlfrescoScriptContext) cx; | ||||
|         acx.setStartTime(System.currentTimeMillis()); | ||||
|         return super.doTopCall(callable, cx, scope, thisObj, args); | ||||
|     } | ||||
|  | ||||
|     public int getOptimizationLevel() | ||||
|     { | ||||
|         return optimizationLevel; | ||||
|     } | ||||
|  | ||||
|     public void setOptimizationLevel(int optimizationLevel) | ||||
|     { | ||||
|         this.optimizationLevel = optimizationLevel; | ||||
|     } | ||||
|  | ||||
|     public int getMaxScriptExecutionSeconds() | ||||
|     { | ||||
|         return maxScriptExecutionSeconds; | ||||
|     } | ||||
|  | ||||
|     public void setMaxScriptExecutionSeconds(int maxScriptExecutionSeconds) | ||||
|     { | ||||
|         this.maxScriptExecutionSeconds = maxScriptExecutionSeconds; | ||||
|     } | ||||
|  | ||||
|     public int getMaxStackDepth() | ||||
|     { | ||||
|         return maxStackDepth; | ||||
|     } | ||||
|  | ||||
|     public void setMaxStackDepth(int maxStackDepth) | ||||
|     { | ||||
|         this.maxStackDepth = maxStackDepth; | ||||
|     } | ||||
|  | ||||
|     public long getMaxMemoryUsedInBytes() | ||||
|     { | ||||
|         return maxMemoryUsedInBytes; | ||||
|     } | ||||
|  | ||||
|     public void setMaxMemoryUsedInBytes(long maxMemoryUsedInBytes) | ||||
|     { | ||||
|         this.maxMemoryUsedInBytes = maxMemoryUsedInBytes; | ||||
|         if (maxMemoryUsedInBytes > 0) | ||||
|         { | ||||
|             this.threadMxBeanWrapper = new AlfrescoScriptThreadMxBeanWrapper(); | ||||
|             if (!threadMxBeanWrapper.isThreadAllocatedMemorySupported()) | ||||
|             { | ||||
|                 LOGGER.warn("com.sun.management.ThreadMXBean was not found on the classpath. " | ||||
|                         + "This means that the limiting the memory usage for a script will NOT work."); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public int getObserveInstructionCount() | ||||
|     { | ||||
|         return observeInstructionCount; | ||||
|     } | ||||
|  | ||||
|     public void setObserveInstructionCount(int observeInstructionCount) | ||||
|     { | ||||
|         this.observeInstructionCount = observeInstructionCount; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,81 @@ | ||||
| /* | ||||
|  * #%L | ||||
|  * Alfresco Repository | ||||
|  * %% | ||||
|  * Copyright (C) 2005 - 2022 Alfresco Software Limited | ||||
|  * %% | ||||
|  * This file is part of the Alfresco software.  | ||||
|  * If the software was purchased under a paid Alfresco license, the terms of  | ||||
|  * the paid license agreement will prevail.  Otherwise, the software is  | ||||
|  * provided under the following open source license terms: | ||||
|  *  | ||||
|  * Alfresco is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Lesser General Public License as published by | ||||
|  * the Free Software Foundation, either version 3 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  *  | ||||
|  * Alfresco is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Lesser General Public License for more details. | ||||
|  *  | ||||
|  * You should have received a copy of the GNU Lesser General Public License | ||||
|  * along with Alfresco. If not, see <http://www.gnu.org/licenses/>. | ||||
|  * #L% | ||||
|  */ | ||||
| package org.alfresco.repo.jscript; | ||||
|  | ||||
| import org.mozilla.javascript.Context; | ||||
|  | ||||
| /** | ||||
|  * Custom Rhino context that holds data as start time and memory | ||||
|  *  | ||||
|  * @see Context | ||||
|  */ | ||||
| public class AlfrescoScriptContext extends Context | ||||
| { | ||||
|     private long startTime; | ||||
|     private long threadId; | ||||
|     private long startMemory; | ||||
|     private boolean limitsEnabled = false; | ||||
|  | ||||
|     public long getStartTime() | ||||
|     { | ||||
|         return startTime; | ||||
|     } | ||||
|  | ||||
|     public void setStartTime(long startTime) | ||||
|     { | ||||
|         this.startTime = startTime; | ||||
|     } | ||||
|  | ||||
|     public long getThreadId() | ||||
|     { | ||||
|         return threadId; | ||||
|     } | ||||
|  | ||||
|     public void setThreadId(long threadId) | ||||
|     { | ||||
|         this.threadId = threadId; | ||||
|     } | ||||
|  | ||||
|     public long getStartMemory() | ||||
|     { | ||||
|         return startMemory; | ||||
|     } | ||||
|  | ||||
|     public void setStartMemory(long startMemory) | ||||
|     { | ||||
|         this.startMemory = startMemory; | ||||
|     } | ||||
|  | ||||
|     public boolean isLimitsEnabled() | ||||
|     { | ||||
|         return limitsEnabled; | ||||
|     } | ||||
|  | ||||
|     public void setLimitsEnabled(boolean limitsEnabled) | ||||
|     { | ||||
|         this.limitsEnabled = limitsEnabled; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,78 @@ | ||||
| /* | ||||
|  * #%L | ||||
|  * Alfresco Repository | ||||
|  * %% | ||||
|  * Copyright (C) 2005 - 2022 Alfresco Software Limited | ||||
|  * %% | ||||
|  * This file is part of the Alfresco software.  | ||||
|  * If the software was purchased under a paid Alfresco license, the terms of  | ||||
|  * the paid license agreement will prevail.  Otherwise, the software is  | ||||
|  * provided under the following open source license terms: | ||||
|  *  | ||||
|  * Alfresco is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Lesser General Public License as published by | ||||
|  * the Free Software Foundation, either version 3 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  *  | ||||
|  * Alfresco is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Lesser General Public License for more details. | ||||
|  *  | ||||
|  * You should have received a copy of the GNU Lesser General Public License | ||||
|  * along with Alfresco. If not, see <http://www.gnu.org/licenses/>. | ||||
|  * #L% | ||||
|  */ | ||||
| package org.alfresco.repo.jscript; | ||||
|  | ||||
| import java.lang.management.ManagementFactory; | ||||
| import java.lang.management.ThreadMXBean; | ||||
|  | ||||
| /** | ||||
|  * Allows to monitor memory usage | ||||
|  */ | ||||
| public class AlfrescoScriptThreadMxBeanWrapper | ||||
| { | ||||
|  | ||||
|     private ThreadMXBean threadMXBean = null; | ||||
|     private boolean threadAllocatedMemorySupported = false; | ||||
|  | ||||
|     private final String THREAD_MX_BEAN_SUN = "com.sun.management.ThreadMXBean"; | ||||
|  | ||||
|     public AlfrescoScriptThreadMxBeanWrapper() | ||||
|     { | ||||
|         checkThreadAllocatedMemory(); | ||||
|     } | ||||
|  | ||||
|     public long getThreadAllocatedBytes(long threadId) | ||||
|     { | ||||
|         if (threadMXBean != null && threadAllocatedMemorySupported) | ||||
|         { | ||||
|             return ((com.sun.management.ThreadMXBean) threadMXBean).getThreadAllocatedBytes(threadId); | ||||
|         } | ||||
|  | ||||
|         return -1; | ||||
|     } | ||||
|  | ||||
|     public void checkThreadAllocatedMemory() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             Class<?> clazz = Class.forName(THREAD_MX_BEAN_SUN); | ||||
|             if (clazz != null) | ||||
|             { | ||||
|                 this.threadAllocatedMemorySupported = true; | ||||
|                 this.threadMXBean = (com.sun.management.ThreadMXBean) ManagementFactory.getThreadMXBean(); | ||||
|             } | ||||
|         } | ||||
|         catch (Exception e) | ||||
|         { | ||||
|             this.threadAllocatedMemorySupported = false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public boolean isThreadAllocatedMemorySupported() | ||||
|     { | ||||
|         return threadAllocatedMemorySupported; | ||||
|     } | ||||
| } | ||||
| @@ -57,10 +57,12 @@ import org.alfresco.service.namespace.QName; | ||||
| import org.apache.commons.logging.Log; | ||||
| import org.apache.commons.logging.LogFactory; | ||||
| import org.mozilla.javascript.Context; | ||||
| import org.mozilla.javascript.ContextFactory; | ||||
| import org.mozilla.javascript.ImporterTopLevel; | ||||
| import org.mozilla.javascript.Script; | ||||
| import org.mozilla.javascript.Scriptable; | ||||
| import org.mozilla.javascript.ScriptableObject; | ||||
| import org.mozilla.javascript.ScriptableObject; | ||||
| import org.mozilla.javascript.Undefined; | ||||
| import org.mozilla.javascript.WrapFactory; | ||||
| import org.mozilla.javascript.WrappedException; | ||||
| import org.springframework.beans.factory.InitializingBean; | ||||
| @@ -108,7 +110,24 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess | ||||
|     /** Cache of runtime compiled script instances */ | ||||
|     private final Map<String, Script> scriptCache = new ConcurrentHashMap<String, Script>(256); | ||||
|      | ||||
|      | ||||
|     /** Rhino optimization level */ | ||||
|     private int optimizationLevel = -1; | ||||
|  | ||||
|     /** Maximum seconds a script is allowed to run */ | ||||
|     private int maxScriptExecutionSeconds = -1; | ||||
|  | ||||
|     /** Maximum of call stack depth (in terms of number of call frames) */ | ||||
|     private int maxStackDepth = -1; | ||||
|  | ||||
|     /** Maximum memory (bytes) a script can use */ | ||||
|     private long maxMemoryUsedInBytes = -1L; | ||||
|  | ||||
|     /** Number of (bytecode) instructions that will trigger the observer */ | ||||
|     private int observerInstructionCount = 100; | ||||
|  | ||||
|     /** Custom context factory */ | ||||
|     public static AlfrescoContextFactory contextFactory; | ||||
|  | ||||
|     /** | ||||
|      * Set the default store reference | ||||
|      *  | ||||
| @@ -143,6 +162,51 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess | ||||
|     { | ||||
|         this.shareSealedScopes = shareSealedScopes; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param optimizationLevel | ||||
|      *            -1 interpretive mode, 0 no optimizations, 1-9 optimizations performed | ||||
|      */ | ||||
|     public void setOptimizationLevel(int optimizationLevel) | ||||
|     { | ||||
|         this.optimizationLevel = optimizationLevel; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param maxScriptExecutionSeconds | ||||
|      *            the number of seconds a script is allowed to run | ||||
|      */ | ||||
|     public void setMaxScriptExecutionSeconds(int maxScriptExecutionSeconds) | ||||
|     { | ||||
|         this.maxScriptExecutionSeconds = maxScriptExecutionSeconds; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param maxStackDepth | ||||
|      *            the number of call stack depth allowed | ||||
|      */ | ||||
|     public void setMaxStackDepth(int maxStackDepth) | ||||
|     { | ||||
|         this.maxStackDepth = maxStackDepth; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param maxMemoryUsedInBytes | ||||
|      *            the number of memory a script can use | ||||
|      */ | ||||
|     public void setMaxMemoryUsedInBytes(long maxMemoryUsedInBytes) | ||||
|     { | ||||
|         this.maxMemoryUsedInBytes = maxMemoryUsedInBytes; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param observerInstructionCount | ||||
|      *            the number of instructions that will trigger {@link ContextFactory#observeInstructionCount} | ||||
|      */ | ||||
|     public void setObserverInstructionCount(int observerInstructionCount) | ||||
|     { | ||||
|         this.observerInstructionCount = observerInstructionCount; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @see org.alfresco.service.cmr.repository.ScriptProcessor#reset() | ||||
| @@ -449,6 +513,8 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess | ||||
|     private Object executeScriptImpl(Script script, Map<String, Object> model, boolean secure, String debugScriptName) | ||||
|         throws AlfrescoRuntimeException | ||||
|     { | ||||
|         Scriptable scope = null; | ||||
|  | ||||
|         long startTime = 0; | ||||
|         if (callLogger.isDebugEnabled()) | ||||
|         { | ||||
| @@ -465,14 +531,16 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess | ||||
|             // Create a thread-specific scope from one of the shared scopes. | ||||
|             // See http://www.mozilla.org/rhino/scopes.html | ||||
|             cx.setWrapFactory(secure ? wrapFactory : sandboxFactory); | ||||
|             Scriptable scope; | ||||
|  | ||||
|             // Enables or disables execution limits based on secure flag | ||||
|             enableLimits(cx, secure); | ||||
|  | ||||
|             if (this.shareSealedScopes) | ||||
|             { | ||||
|                 Scriptable sharedScope = secure ? this.nonSecureScope : this.secureScope; | ||||
|                 scope = cx.newObject(sharedScope); | ||||
|                 scope.setPrototype(sharedScope); | ||||
|                 scope.setParentScope(null); | ||||
|  | ||||
|             } | ||||
|             else | ||||
|             { | ||||
| @@ -545,7 +613,8 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess | ||||
|             throw new AlfrescoRuntimeException(err.getMessage(), err); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|         { | ||||
|             unsetScope(model, scope); | ||||
|             Context.exit(); | ||||
|              | ||||
|             if (callLogger.isDebugEnabled()) | ||||
| @@ -638,6 +707,9 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess | ||||
|      */ | ||||
|     public void afterPropertiesSet() throws Exception | ||||
|     { | ||||
|         // Initialize context factory | ||||
|         initContextFactory(); | ||||
|  | ||||
|         // Initialize the secure scope | ||||
|         Context cx = Context.enter(); | ||||
|         try | ||||
| @@ -695,4 +767,129 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess | ||||
|         } | ||||
|         return scope; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Clean supplied scope and unset it from any model instance where it has been injected before | ||||
|      * | ||||
|      * @param model | ||||
|      *            Data model containing objects from where scope will be unset | ||||
|      * @param scope | ||||
|      *            The scope to clean | ||||
|      */ | ||||
|     private void unsetScope(Map<String, Object> model, Scriptable scope) | ||||
|     { | ||||
|         if (scope != null) | ||||
|         { | ||||
|             Object[] ids = scope.getIds(); | ||||
|             if (ids != null) | ||||
|             { | ||||
|                 for (Object id : ids) | ||||
|                 { | ||||
|                     try | ||||
|                     { | ||||
|                         deleteProperty(scope, id.toString()); | ||||
|                     } | ||||
|                     catch (Exception e) | ||||
|                     { | ||||
|                         logger.info("Unable to delete id: " + id, e); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (model != null) | ||||
|         { | ||||
|             for (String key : model.keySet()) | ||||
|             { | ||||
|                 try | ||||
|                 { | ||||
|                     deleteProperty(scope, key); | ||||
|  | ||||
|                     Object obj = model.get(key); | ||||
|                     if (obj instanceof Scopeable) | ||||
|                     { | ||||
|                         ((Scopeable) obj).setScope(null); | ||||
|                     } | ||||
|                 } | ||||
|                 catch (Exception e) | ||||
|                 { | ||||
|                     logger.info("Unable to unset model object " + key + " : ", e); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Deletes a property from the supplied scope, if property is not removable, then is set to null | ||||
|      * | ||||
|      * @param scope | ||||
|      *            the scope object from where property will be removed | ||||
|      * @param name | ||||
|      *            the property name to delete | ||||
|      */ | ||||
|     private void deleteProperty(Scriptable scope, String name) | ||||
|     { | ||||
|         if (scope != null && name != null) | ||||
|         { | ||||
|             if (!ScriptableObject.deleteProperty(scope, name)) | ||||
|             { | ||||
|                 ScriptableObject.putProperty(scope, name, null); | ||||
|             } | ||||
|             scope.delete(name); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Initializes the context factory with limits configuration | ||||
|      */ | ||||
|     private synchronized void initContextFactory() | ||||
|     { | ||||
|         if (contextFactory == null) | ||||
|         { | ||||
|             contextFactory = new AlfrescoContextFactory(); | ||||
|             contextFactory.setOptimizationLevel(optimizationLevel); | ||||
|  | ||||
|             if (maxScriptExecutionSeconds > 0) | ||||
|             { | ||||
|                 contextFactory.setMaxScriptExecutionSeconds(maxScriptExecutionSeconds); | ||||
|             } | ||||
|  | ||||
|             if (maxMemoryUsedInBytes > 0L) | ||||
|             { | ||||
|                 contextFactory.setMaxMemoryUsedInBytes(maxMemoryUsedInBytes); | ||||
|             } | ||||
|  | ||||
|             if (maxStackDepth > 0) | ||||
|             { | ||||
|                 contextFactory.setMaxStackDepth(maxStackDepth); | ||||
|             } | ||||
|  | ||||
|             if (maxScriptExecutionSeconds > 0 || maxMemoryUsedInBytes > 0L) | ||||
|             { | ||||
|                 contextFactory.setObserveInstructionCount(observerInstructionCount); | ||||
|             } | ||||
|  | ||||
|             ContextFactory.initGlobal(contextFactory); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * If script is considered secure no limits will be applied, otherwise, the limits are enabled and the script can be | ||||
|      * interrupted in case a limit has been reached. | ||||
|      * | ||||
|      * @param cx | ||||
|      *            the Rhino scope | ||||
|      * @param secure | ||||
|      *            true if script execution is considered secure (e.g, deployed at classpath level) | ||||
|      */ | ||||
|     private void enableLimits(Context cx, boolean secure) | ||||
|     { | ||||
|         if (cx != null) | ||||
|         { | ||||
|             if (cx instanceof AlfrescoScriptContext) | ||||
|             { | ||||
|                 ((AlfrescoScriptContext) cx).setLimitsEnabled(!secure); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -459,7 +459,13 @@ public class RuleServiceImpl | ||||
|     public boolean hasRules(NodeRef nodeRef) | ||||
|     { | ||||
|         return getRules(nodeRef).size() != 0; | ||||
|     }  | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean hasNonInheritedRules(NodeRef nodeRef) | ||||
|     { | ||||
|         return getRules(nodeRef, false).size() != 0; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<Rule> getRules(NodeRef nodeRef) | ||||
|   | ||||
| @@ -165,6 +165,15 @@ public interface RuleService | ||||
|     @Auditable(parameters = {"nodeRef"}) | ||||
|     public boolean hasRules(NodeRef nodeRef); | ||||
|  | ||||
|     /** | ||||
|      * Indicates whether the node in question has any non-inherited rules associated with it. | ||||
|      * | ||||
|      * @param nodeRef   the node reference | ||||
|      * @return          true if the node has rules associated, false otherwise | ||||
|      */ | ||||
|     @Auditable(parameters = {"nodeRef"}) | ||||
|     public boolean hasNonInheritedRules(NodeRef nodeRef); | ||||
|  | ||||
|     /** | ||||
|      * Get all the rules associated with an actionable node, including those | ||||
|      * inherited from parents. | ||||
|   | ||||
| @@ -1351,3 +1351,18 @@ import.zip.compressionRatioThreshold=100 | ||||
| # "zip bomb" and the import extraction process cancelled. No value (or a negative long) will be taken to mean that no | ||||
| # limit should be applied. | ||||
| import.zip.uncompressedBytesLimit= | ||||
|  | ||||
| # Rhino optimization level | ||||
| scripts.execution.optimizationLevel=0 | ||||
|  | ||||
| # Max seconds a script is allowed to run | ||||
| scripts.execution.maxScriptExecutionSeconds=-1 | ||||
|  | ||||
| # Max call stack depth | ||||
| scripts.execution.maxStackDepth=-1 | ||||
|  | ||||
| # Max memory (bytes) a script can use | ||||
| scripts.execution.maxMemoryUsedInBytes=-1 | ||||
|  | ||||
| # Number of instructions that will trigger the observer | ||||
| scripts.execution.observerInstructionCount=-1 | ||||
|   | ||||
| @@ -45,6 +45,21 @@ | ||||
|         <property name="storePath"> | ||||
|             <value>${spaces.company_home.childname}</value> | ||||
|         </property> | ||||
|         <property name="optimizationLevel"> | ||||
|             <value>${scripts.execution.optimizationLevel}</value> | ||||
|         </property> | ||||
|         <property name="maxScriptExecutionSeconds"> | ||||
|             <value>${scripts.execution.maxScriptExecutionSeconds}</value> | ||||
|         </property> | ||||
|         <property name="maxStackDepth"> | ||||
|             <value>${scripts.execution.maxStackDepth}</value> | ||||
|         </property> | ||||
|         <property name="maxMemoryUsedInBytes"> | ||||
|             <value>${scripts.execution.maxMemoryUsedInBytes}</value> | ||||
|         </property> | ||||
|         <property name="observerInstructionCount"> | ||||
|             <value>${scripts.execution.observerInstructionCount}</value> | ||||
|         </property> | ||||
|     </bean> | ||||
|  | ||||
|     <!-- base config implementation that script extension beans extend from - for auto registration | ||||
|   | ||||
| @@ -44,7 +44,6 @@ import org.alfresco.service.cmr.repository.ContentService; | ||||
| import org.alfresco.service.cmr.repository.ContentWriter; | ||||
| import org.alfresco.service.cmr.repository.NodeRef; | ||||
| import org.alfresco.service.cmr.repository.NodeService; | ||||
| import org.alfresco.service.cmr.repository.ScriptProcessor; | ||||
| import org.alfresco.service.cmr.repository.ScriptService; | ||||
| import org.alfresco.service.cmr.repository.StoreRef; | ||||
| import org.alfresco.service.namespace.QName; | ||||
| @@ -53,8 +52,11 @@ import org.alfresco.test_category.OwnJVMTestsCategory; | ||||
| import org.alfresco.util.ApplicationContextHelper; | ||||
| import org.junit.experimental.categories.Category; | ||||
| import org.mozilla.javascript.Context; | ||||
| import org.mozilla.javascript.ImporterTopLevel; | ||||
| import org.mozilla.javascript.Scriptable; | ||||
| import org.mozilla.javascript.ScriptableObject; | ||||
| import org.mozilla.javascript.Undefined; | ||||
| import org.mozilla.javascript.UniqueTag; | ||||
| import org.springframework.context.ApplicationContext; | ||||
|  | ||||
| import junit.framework.TestCase; | ||||
| @@ -445,6 +447,67 @@ public class RhinoScriptTest extends TestCase | ||||
|         assertTrue("Script should have been executed (secure = true)", executed); | ||||
|     } | ||||
|  | ||||
|     // MNT-23158 | ||||
|     public void testScopeData() | ||||
|     { | ||||
|         transactionService.getRetryingTransactionHelper().doInTransaction( | ||||
|             new RetryingTransactionCallback<Object>() | ||||
|             { | ||||
|                 public Object execute() throws Exception | ||||
|                 { | ||||
|                     Context cx = Context.enter(); | ||||
|                     try | ||||
|                     { | ||||
|                         Scriptable sharedScope = new ImporterTopLevel(cx, true); | ||||
|                         Scriptable scope = cx.newObject(sharedScope); | ||||
|                         scope.setPrototype(sharedScope); | ||||
|                         scope.setParentScope(null); | ||||
|  | ||||
|                         // Executes a first script | ||||
|                         Object result = cx.evaluateString(scope, "var a = 10; var b = 20; var sum = a+b;", "TestJS1", 1, null); | ||||
|                         assertTrue(Undefined.isUndefined(result)); | ||||
|  | ||||
|                         // Test sum value | ||||
|                         Object sum = scope.get("sum", scope); | ||||
|                         assertEquals(30.0, Context.toNumber(sum)); | ||||
|  | ||||
|                         // No 'sum' property should be found in the shared scope | ||||
|                         sum = sharedScope.get("sum", sharedScope); | ||||
|                         assertEquals(sum, UniqueTag.NOT_FOUND); | ||||
|  | ||||
|                         // No 'b' property should be found in the shared scope | ||||
|                         Object b = ScriptableObject.getProperty(sharedScope, "b"); | ||||
|                         assertEquals(b, UniqueTag.NOT_FOUND); | ||||
|  | ||||
|                         // Cleans scope | ||||
|                         unsetScope(scope); | ||||
|  | ||||
|                         // Executes a second script using the same scope | ||||
|                         result = cx.evaluateString(scope, "var test = 'test';", "TestJS2", 1, null); | ||||
|  | ||||
|                         // 'sum' property should be null | ||||
|                         sum = scope.get("sum", scope); | ||||
|                         assertNull(sum); | ||||
|  | ||||
|                         // New scope initialization | ||||
|                         scope = cx.newObject(sharedScope); | ||||
|                         scope.setPrototype(sharedScope); | ||||
|                         scope.setParentScope(null); | ||||
|  | ||||
|                         // check 'test' property | ||||
|                         Object test = scope.get("test", scope); | ||||
|                         assertEquals(test, UniqueTag.NOT_FOUND); | ||||
|                     } | ||||
|                     finally | ||||
|                     { | ||||
|                         Context.exit(); | ||||
|                     } | ||||
|  | ||||
|                     return null; | ||||
|                 } | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     private boolean executeSecureScriptString(String script, Boolean secure) | ||||
|     { | ||||
|         return transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback<Boolean>() | ||||
| @@ -475,6 +538,41 @@ public class RhinoScriptTest extends TestCase | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     private void unsetScope(Scriptable scope) | ||||
|     { | ||||
|         if (scope != null) | ||||
|         { | ||||
|             Object[] ids = scope.getIds(); | ||||
|  | ||||
|             if (ids != null) | ||||
|             { | ||||
|                 for (Object id : ids) | ||||
|                 { | ||||
|                     try | ||||
|                     { | ||||
|                         deleteProperty(scope, id.toString()); | ||||
|                     } | ||||
|                     catch (Exception e) | ||||
|                     { | ||||
|                         // Do nothing | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void deleteProperty(Scriptable scope, String name) | ||||
|     { | ||||
|         if (scope != null && name != null) | ||||
|         { | ||||
|             if (!ScriptableObject.deleteProperty(scope, name)) | ||||
|             { | ||||
|                 ScriptableObject.putProperty(scope, name, null); | ||||
|             } | ||||
|             scope.delete(name); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static final String TESTSCRIPT_CLASSPATH1 = "org/alfresco/repo/jscript/test_script1.js"; | ||||
|     private static final String TESTSCRIPT_CLASSPATH2 = "org/alfresco/repo/jscript/test_script2.js"; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user