mirror of
				https://github.com/Alfresco/alfresco-community-repo.git
				synced 2025-10-29 15:21:53 +00:00 
			
		
		
		
	Compare commits
	
		
			137 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 8c69432052 | ||
|  | 124f87ee21 | ||
|  | 3cd3b2c2d6 | ||
|  | 14da8d2002 | ||
|  | 6a4bbb021c | ||
|  | 42d70b17c7 | ||
|  | c7eba0ddc8 | ||
|  | 266094c0e1 | ||
|  | e442b4acf0 | ||
|  | fd1028a685 | ||
|  | 0a7e275a9c | ||
|  | d1bbba7286 | ||
|  | e1baddebee | ||
|  | 3263dcaf2f | ||
|  | 8926f7f9a7 | ||
|  | 764a1b656c | ||
|  | cf265f2dea | ||
|  | fd0d5204eb | ||
|  | f9b8a4b42d | ||
|  | fcdc1438e7 | ||
|  | 7cd1416561 | ||
|  | f197757f94 | ||
|  | af995f1087 | ||
|  | 2cfcd3dfa7 | ||
|  | 89e09b0162 | ||
|  | 495808b172 | ||
|  | 57060af84b | ||
|  | 60261aafd1 | ||
|  | 8dad225394 | ||
|  | 5cc21c55e7 | ||
|  | c71aaf7537 | ||
|  | b7d16ac915 | ||
|  | 1a436b06e4 | ||
|  | be02be5a8b | ||
|  | a674e574c5 | ||
|  | aacaa62ff9 | ||
|  | 371bd1543d | ||
|  | 4cb16f046f | ||
|  | 2fb7de9ace | ||
|  | ed972c79d7 | ||
|  | 0f3e2dc4cc | ||
|  | 4e7d0ccae3 | ||
|  | 1b5636a339 | ||
|  | 164ce720af | ||
|  | 258738e3dd | ||
|  | fefd937c89 | ||
|  | 91f9467a99 | ||
|  | 97b1515f7c | ||
|  | 7f235f1e2b | ||
|  | 109bdeee0f | ||
|  | 7c97f49574 | ||
|  | 2088b8b553 | ||
|  | 280a873cb6 | ||
|  | 9683c18448 | ||
|  | 21b36a7100 | ||
|  | 96481daae1 | ||
|  | 7ef573699b | ||
|  | a000df7ceb | ||
|  | 4a22735120 | ||
|  | 94d84799be | ||
|  | 754776e30c | ||
|  | 28b8bb85e4 | ||
|  | 4910028d51 | ||
|  | 75d0825295 | ||
|  | 964cedaebd | ||
|  | 2bda7d7231 | ||
|  | 82d316d802 | ||
|  | 1840d1056d | ||
|  | 334e8c84df | ||
|  | 24309cf4b6 | ||
|  | 2d28742a94 | ||
|  | d6503ac1de | ||
|  | 73ef1ed9ff | ||
|  | d3bc9e2b60 | ||
|  | 174186d1ff | ||
|  | 57331afe8f | ||
|  | 7c87595b0c | ||
|  | b7191b175e | ||
|  | 39b19d1ceb | ||
|  | 9e23b99078 | ||
|  | ac36ac07e8 | ||
|  | cc10339577 | ||
|  | 8aa975fbc3 | ||
|  | 01620b75ff | ||
|  | 9327218f17 | ||
|  | b57373fbe3 | ||
|  | 613fb458b9 | ||
|  | 31cd97b9d2 | ||
|  | 255fe46c8e | ||
|  | 88d32748b1 | ||
|  | 90e1522a56 | ||
|  | 7e61befc21 | ||
|  | be6dc14330 | ||
|  | ab4bc1af9f | ||
|  | eaec23ae7a | ||
|  | 8ff727c95d | ||
|  | 48475fdfaa | ||
|  | 812959be2e | ||
|  | 5b8c52db67 | ||
|  | 20c42b6561 | ||
|  | 52dfea9b21 | ||
|  | d04dada44e | ||
|  | 2e85de7c81 | ||
|  | 42324368e5 | ||
|  | 8d885220d8 | ||
|  | 6367f5304d | ||
|  | f17b309c27 | ||
|  | bb2cc1765d | ||
|  | d20e8ee158 | ||
|  | 254193f9aa | ||
|  | 4293f21618 | ||
|  | e0eb43c479 | ||
|  | f1bbb6cce7 | ||
|  | e6e2a2d8ac | ||
|  | c2cfcdc35a | ||
|  | 6b2ac86b1d | ||
|  | 8b212dc4cf | ||
|  | e2c357c1e0 | ||
|  | 4a024e510d | ||
|  | 3f75c9b15f | ||
|  | 96f94a98be | ||
|  | bfc0445aeb | ||
|  | 977f6f12d4 | ||
|  | 626640ddc7 | ||
|  | 2b00e550a9 | ||
|  | f3dca482ff | ||
|  | 94e957cb73 | ||
|  | 8868e64a6a | ||
|  | f4af65943f | ||
|  | 6fe1b50741 | ||
|  | f300bd6b3a | ||
|  | f7195ef16a | ||
|  | ef228f0614 | ||
|  | 6c0f231316 | ||
|  | 33b521b421 | ||
|  | eff4e0738c | ||
|  | 5685fc3b17 | 
							
								
								
									
										38
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										38
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| @@ -38,7 +38,7 @@ jobs: | ||||
|       !contains(github.event.head_commit.message, '[skip tests]') && | ||||
|       !contains(github.event.head_commit.message, '[force') | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - uses: actions/checkout@v5 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1 | ||||
| @@ -61,7 +61,7 @@ jobs: | ||||
|       !contains(github.event.head_commit.message, '[skip tests]') && | ||||
|       !contains(github.event.head_commit.message, '[force') | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - uses: actions/checkout@v5 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1 | ||||
| @@ -84,7 +84,7 @@ jobs: | ||||
|       !contains(github.event.head_commit.message, '[skip tests]') && | ||||
|       !contains(github.event.head_commit.message, '[force') | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - uses: actions/checkout@v5 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1 | ||||
| @@ -173,7 +173,7 @@ jobs: | ||||
|             testModule: mmt | ||||
|             testAttributes: "-Dtest=AllMmtUnitTestSuite" | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - uses: actions/checkout@v5 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1 | ||||
| @@ -210,7 +210,7 @@ jobs: | ||||
|     env: | ||||
|       REQUIRES_INSTALLED_ARTIFACTS: true | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - uses: actions/checkout@v5 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1 | ||||
| @@ -245,7 +245,7 @@ jobs: | ||||
|       matrix: | ||||
|         version: ['10.5', '10.6'] | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - uses: actions/checkout@v5 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1 | ||||
| @@ -272,7 +272,7 @@ jobs: | ||||
|       !contains(github.event.head_commit.message, '[skip tests]') && | ||||
|       !contains(github.event.head_commit.message, '[force') | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - uses: actions/checkout@v5 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1 | ||||
| @@ -299,7 +299,7 @@ jobs: | ||||
|       !contains(github.event.head_commit.message, '[skip tests]') && | ||||
|       !contains(github.event.head_commit.message, '[force') | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - uses: actions/checkout@v5 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1 | ||||
| @@ -325,7 +325,7 @@ jobs: | ||||
|       !contains(github.event.head_commit.message, '[skip tests]') && | ||||
|       !contains(github.event.head_commit.message, '[force') | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - uses: actions/checkout@v5 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1 | ||||
| @@ -351,7 +351,7 @@ jobs: | ||||
|             !contains(github.event.head_commit.message, '[skip tests]') && | ||||
|             !contains(github.event.head_commit.message, '[force') | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - uses: actions/checkout@v5 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1 | ||||
| @@ -377,7 +377,7 @@ jobs: | ||||
|       !contains(github.event.head_commit.message, '[skip tests]') && | ||||
|       !contains(github.event.head_commit.message, '[force') | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - uses: actions/checkout@v5 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1 | ||||
| @@ -401,7 +401,7 @@ jobs: | ||||
|       !contains(github.event.head_commit.message, '[skip tests]') && | ||||
|       !contains(github.event.head_commit.message, '[force') | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - uses: actions/checkout@v5 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1 | ||||
| @@ -457,7 +457,7 @@ jobs: | ||||
|             disabledHostnameVerification: false | ||||
|             mvn-options: '-Dencryption.ssl.keystore.location=${CI_WORKSPACE}/keystores/alfresco/alfresco.keystore -Dencryption.ssl.truststore.location=${CI_WORKSPACE}/keystores/alfresco/alfresco.truststore' | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - uses: actions/checkout@v5 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1 | ||||
| @@ -527,7 +527,7 @@ jobs: | ||||
|     env: | ||||
|       REQUIRES_LOCAL_IMAGES: true | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - uses: actions/checkout@v5 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1 | ||||
| @@ -566,7 +566,7 @@ jobs: | ||||
|       !contains(github.event.head_commit.message, '[skip tests]') && | ||||
|       !contains(github.event.head_commit.message, '[force') | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - uses: actions/checkout@v5 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1 | ||||
| @@ -596,7 +596,7 @@ jobs: | ||||
|     env: | ||||
|       REQUIRES_INSTALLED_ARTIFACTS: true | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - uses: actions/checkout@v5 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1 | ||||
| @@ -628,7 +628,7 @@ jobs: | ||||
|     env: | ||||
|       REQUIRES_INSTALLED_ARTIFACTS: true | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - uses: actions/checkout@v5 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1 | ||||
| @@ -656,7 +656,7 @@ jobs: | ||||
|     env: | ||||
|       REQUIRES_LOCAL_IMAGES: true | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - uses: actions/checkout@v5 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1 | ||||
| @@ -702,7 +702,7 @@ jobs: | ||||
|       !contains(github.event.head_commit.message, '[skip tests]') && | ||||
|       !contains(github.event.head_commit.message, '[force]') | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - uses: actions/checkout@v5 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1 | ||||
|   | ||||
							
								
								
									
										4
									
								
								.github/workflows/master_release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/master_release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -31,7 +31,7 @@ jobs: | ||||
|       !contains(github.event.head_commit.message, '[no release]') && | ||||
|       github.event_name != 'pull_request' | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - uses: actions/checkout@v5 | ||||
|         with: | ||||
|           persist-credentials: false | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1 | ||||
| @@ -60,7 +60,7 @@ jobs: | ||||
|       !contains(github.event.head_commit.message, '[no downstream]') && | ||||
|       github.event_name != 'pull_request' | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - uses: actions/checkout@v5 | ||||
|         with: | ||||
|           persist-credentials: false | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1 | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/precommit_formatter.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/precommit_formatter.yml
									
									
									
									
										vendored
									
									
								
							| @@ -11,7 +11,7 @@ jobs: | ||||
|     runs-on: ubuntu-latest | ||||
|     if: contains(github.event.head_commit.message, '[reformat code]') | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - uses: actions/checkout@v5 | ||||
|       - name: Set up Python ${{ inputs.python-version }} | ||||
|         uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 | ||||
|         with: | ||||
|   | ||||
| @@ -1242,7 +1242,7 @@ | ||||
|         "filename": "repository/src/main/resources/alfresco/repository.properties", | ||||
|         "hashed_secret": "1459a56410378e4d3ab470eff570e5eae1742762", | ||||
|         "is_verified": false, | ||||
|         "line_number": 312, | ||||
|         "line_number": 314, | ||||
|         "is_secret": false | ||||
|       }, | ||||
|       { | ||||
| @@ -1250,7 +1250,7 @@ | ||||
|         "filename": "repository/src/main/resources/alfresco/repository.properties", | ||||
|         "hashed_secret": "84551ae5442affc9f1a2d3b4c86ae8b24860149d", | ||||
|         "is_verified": false, | ||||
|         "line_number": 771, | ||||
|         "line_number": 773, | ||||
|         "is_secret": false | ||||
|       } | ||||
|     ], | ||||
| @@ -1845,5 +1845,5 @@ | ||||
|       } | ||||
|     ] | ||||
|   }, | ||||
|   "generated_at": "2025-06-09T16:43:14Z" | ||||
|   "generated_at": "2025-07-23T08:25:11Z" | ||||
| } | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|    <parent> | ||||
|       <groupId>org.alfresco</groupId> | ||||
|       <artifactId>alfresco-community-repo-amps</artifactId> | ||||
|       <version>25.3.0.20</version> | ||||
|       <version>25.3.0.62</version> | ||||
|    </parent> | ||||
|  | ||||
|    <modules> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|    <parent> | ||||
|       <groupId>org.alfresco</groupId> | ||||
|       <artifactId>alfresco-governance-services-community-parent</artifactId> | ||||
|       <version>25.3.0.20</version> | ||||
|       <version>25.3.0.62</version> | ||||
|    </parent> | ||||
|  | ||||
|    <modules> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|    <parent> | ||||
|       <groupId>org.alfresco</groupId> | ||||
|       <artifactId>alfresco-governance-services-automation-community-repo</artifactId> | ||||
|       <version>25.3.0.20</version> | ||||
|       <version>25.3.0.62</version> | ||||
|    </parent> | ||||
|  | ||||
|    <build> | ||||
|   | ||||
| @@ -43,7 +43,7 @@ import com.github.dockerjava.core.command.LogContainerResultCallback; | ||||
| import com.github.dockerjava.netty.NettyDockerCmdExecFactory; | ||||
| import lombok.Getter; | ||||
| import lombok.Setter; | ||||
| import org.apache.commons.lang.SystemUtils; | ||||
| import org.apache.commons.lang3.SystemUtils; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
|   | ||||
| @@ -37,7 +37,7 @@ import static org.alfresco.rest.rm.community.util.CommonTestUtils.generateTestPr | ||||
|  | ||||
| import java.util.Collections; | ||||
|  | ||||
| import org.apache.commons.lang.StringUtils; | ||||
| import org.apache.commons.lang3.StringUtils; | ||||
| import org.json.JSONObject; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.testng.annotations.Test; | ||||
| @@ -56,7 +56,7 @@ import org.alfresco.test.AlfrescoTest; | ||||
|  | ||||
| /** | ||||
|  * Add Relationship tests | ||||
|  *  | ||||
|  * | ||||
|  * @author Kavit Shah | ||||
|  */ | ||||
| public class AddRelationshipTests extends BaseRMRestTest | ||||
|   | ||||
| @@ -45,7 +45,7 @@ import java.time.Instant; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
|  | ||||
| import org.apache.commons.lang.StringUtils; | ||||
| import org.apache.commons.lang3.StringUtils; | ||||
| import org.apache.http.HttpEntity; | ||||
| import org.apache.http.HttpResponse; | ||||
| import org.apache.http.HttpStatus; | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|    <parent> | ||||
|       <groupId>org.alfresco</groupId> | ||||
|       <artifactId>alfresco-governance-services-community-parent</artifactId> | ||||
|       <version>25.3.0.20</version> | ||||
|       <version>25.3.0.62</version> | ||||
|    </parent> | ||||
|  | ||||
|    <modules> | ||||
|   | ||||
| @@ -15,6 +15,13 @@ | ||||
|       <parameter property="end" jdbcType="BIGINT" javaType="java.lang.Long"/> | ||||
|    </parameterMap> | ||||
|  | ||||
|    <parameterMap id="parameter_NodeIdsWhichReferenceContentUrl" type="map"> | ||||
|       <parameter property="contentUrlShort" jdbcType="VARCHAR" javaType="java.lang.String"/> | ||||
|       <parameter property="contentUrlCrc" jdbcType="BIGINT" javaType="java.lang.Long"/> | ||||
|       <parameter property="localName" jdbcType="VARCHAR" javaType="java.lang.String"/> | ||||
|       <parameter property="uri" jdbcType="VARCHAR" javaType="java.lang.String"/> | ||||
|    </parameterMap> | ||||
|  | ||||
|    <resultMap id="result_NodeRefEntity" type="org.alfresco.module.org_alfresco_module_rm.query.NodeRefEntity"> | ||||
|       <result property="row" column="row" jdbcType="BIGINT" javaType="java.lang.Long"/> | ||||
|       <result property="protocol" column="protocol" jdbcType="VARCHAR" javaType="java.lang.String"/> | ||||
| @@ -55,18 +62,21 @@ | ||||
|  | ||||
|    <!-- Get list of node ids which reference given content url --> | ||||
|    <select id="select_NodeIdsWhichReferenceContentUrl" | ||||
|            parameterType="ContentUrl" | ||||
|            parameterMap="parameter_NodeIdsWhichReferenceContentUrl" | ||||
|            resultMap="result_NodeIds"> | ||||
|       select | ||||
|          p.node_id | ||||
|       from | ||||
|          alf_content_url cu | ||||
|       LEFT OUTER JOIN alf_content_data cd ON (cd.content_url_id = cu.id) | ||||
|       LEFT OUTER JOIN alf_node_properties p ON (p.long_value = cd.id) | ||||
|       WHERE | ||||
|          content_url_short = #{contentUrlShort} and | ||||
|          content_url_crc = #{contentUrlCrc} | ||||
|  | ||||
|          left outer join alf_content_data cd ON (cd.content_url_id = cu.id) | ||||
|          left outer join alf_node_properties p ON (p.long_value = cd.id) | ||||
|          left outer join alf_qname q ON (q.id = p.qname_id) | ||||
|          left outer join alf_namespace n ON (n.id = q.ns_id) | ||||
|       where | ||||
|          cu.content_url_short = ? and | ||||
|          cu.content_url_crc = ? and | ||||
|          q.local_name = ? and | ||||
|          n.uri = ? | ||||
|    </select> | ||||
|  | ||||
|    <select id="select_RecordFoldersWithSchedules" | ||||
|   | ||||
| @@ -8,7 +8,7 @@ | ||||
|    <parent> | ||||
|       <groupId>org.alfresco</groupId> | ||||
|       <artifactId>alfresco-governance-services-community-repo-parent</artifactId> | ||||
|       <version>25.3.0.20</version> | ||||
|       <version>25.3.0.62</version> | ||||
|    </parent> | ||||
|  | ||||
|    <properties> | ||||
|   | ||||
| @@ -39,6 +39,7 @@ import org.apache.commons.logging.Log; | ||||
| import org.apache.commons.logging.LogFactory; | ||||
| import org.mybatis.spring.SqlSessionTemplate; | ||||
|  | ||||
| import org.alfresco.model.ContentModel; | ||||
| import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; | ||||
| import org.alfresco.repo.domain.contentdata.ContentUrlEntity; | ||||
| import org.alfresco.repo.domain.node.NodeDAO; | ||||
| @@ -191,13 +192,19 @@ public class RecordsManagementQueryDAOImpl implements RecordsManagementQueryDAO, | ||||
|         ContentUrlEntity contentUrlEntity = new ContentUrlEntity(); | ||||
|         contentUrlEntity.setContentUrl(contentUrl.toLowerCase()); | ||||
|  | ||||
|         Map<String, Object> params = new HashMap<>(4); | ||||
|         params.put("contentUrlShort", contentUrlEntity.getContentUrlShort()); | ||||
|         params.put("contentUrlCrc", contentUrlEntity.getContentUrlCrc()); | ||||
|         params.put("localName", ContentModel.PROP_CONTENT.getLocalName()); | ||||
|         params.put("uri", ContentModel.PROP_CONTENT.getNamespaceURI()); | ||||
|  | ||||
|         if (logger.isDebugEnabled()) | ||||
|         { | ||||
|             logger.debug("Executing query " + SELECT_NODE_IDS_WHICH_REFERENCE_CONTENT_URL); | ||||
|         } | ||||
|  | ||||
|         // Get all the node ids which reference the given content url | ||||
|         List<Long> nodeIds = template.selectList(SELECT_NODE_IDS_WHICH_REFERENCE_CONTENT_URL, contentUrlEntity); | ||||
|         List<Long> nodeIds = template.selectList(SELECT_NODE_IDS_WHICH_REFERENCE_CONTENT_URL, params); | ||||
|  | ||||
|         if (logger.isDebugEnabled()) | ||||
|         { | ||||
|   | ||||
| @@ -31,11 +31,13 @@ import static org.alfresco.service.cmr.security.PermissionService.GROUP_PREFIX; | ||||
|  | ||||
| import java.util.Collections; | ||||
| import java.util.HashSet; | ||||
| import java.util.LinkedHashSet; | ||||
| import java.util.List; | ||||
| import java.util.Set; | ||||
|  | ||||
| import org.springframework.context.ApplicationListener; | ||||
| import org.springframework.context.event.ContextRefreshedEvent; | ||||
| import org.springframework.dao.ConcurrencyFailureException; | ||||
| import org.springframework.extensions.webscripts.ui.common.StringUtils; | ||||
|  | ||||
| import org.alfresco.model.ContentModel; | ||||
| @@ -246,7 +248,7 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl | ||||
|      */ | ||||
|     private Set<String> getAuthorities(String group) | ||||
|     { | ||||
|         Set<String> result = new HashSet<>(); | ||||
|         Set<String> result = new LinkedHashSet<>(); | ||||
|         result.addAll(authorityService.getContainedAuthorities(null, group, true)); | ||||
|         return result; | ||||
|     } | ||||
| @@ -649,8 +651,8 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl | ||||
|         } | ||||
|         catch (DuplicateChildNodeNameException ex) | ||||
|         { | ||||
|             // the group was concurrently created | ||||
|             group = authorityService.getName(AuthorityType.GROUP, groupShortName); | ||||
|             // Rethrow as ConcurrencyFailureException so that is can be retried and linked to the group created by the concurrent transaction | ||||
|             throw new ConcurrencyFailureException("IPR group creation failed due to concurrent duplicate group name creation: " + groupShortName); | ||||
|         } | ||||
|  | ||||
|         return group; | ||||
|   | ||||
| @@ -33,6 +33,7 @@ import java.io.Serializable; | ||||
| import java.util.Arrays; | ||||
| import java.util.Collections; | ||||
| import java.util.HashSet; | ||||
| import java.util.LinkedHashSet; | ||||
| import java.util.List; | ||||
| import java.util.Set; | ||||
|  | ||||
| @@ -325,8 +326,8 @@ public class ExtendedPermissionServiceImpl extends PermissionServiceImpl impleme | ||||
|             return aclReaders; | ||||
|         } | ||||
|  | ||||
|         HashSet<String> assigned = new HashSet<>(); | ||||
|         HashSet<String> readers = new HashSet<>(); | ||||
|         Set<String> assigned = new LinkedHashSet<>(); | ||||
|         Set<String> readers = new LinkedHashSet<>(); | ||||
|  | ||||
|         for (AccessControlEntry ace : acl.getEntries()) | ||||
|         { | ||||
| @@ -412,8 +413,8 @@ public class ExtendedPermissionServiceImpl extends PermissionServiceImpl impleme | ||||
|             return aclWriters; | ||||
|         } | ||||
|  | ||||
|         HashSet<String> assigned = new HashSet<>(); | ||||
|         HashSet<String> readers = new HashSet<>(); | ||||
|         Set<String> assigned = new LinkedHashSet<>(); | ||||
|         Set<String> readers = new LinkedHashSet<>(); | ||||
|  | ||||
|         for (AccessControlEntry ace : acl.getEntries()) | ||||
|         { | ||||
| @@ -485,7 +486,7 @@ public class ExtendedPermissionServiceImpl extends PermissionServiceImpl impleme | ||||
|         Set<String> writers = getWriters(aclId); | ||||
|  | ||||
|         // add the current owner to the list of extended writers | ||||
|         Set<String> modifiedWrtiers = new HashSet<>(writers); | ||||
|         Set<String> modifiedWrtiers = new LinkedHashSet<>(writers); | ||||
|         String owner = ownableService.getOwner(nodeRef); | ||||
|         if (StringUtils.isNotBlank(owner) && | ||||
|                 !owner.equals(OwnableService.NO_OWNER) && | ||||
|   | ||||
| @@ -29,14 +29,23 @@ package org.alfresco.module.org_alfresco_module_rm.test.legacy.service; | ||||
|  | ||||
| import java.util.HashSet; | ||||
| import java.util.Set; | ||||
| import java.util.concurrent.CompletableFuture; | ||||
|  | ||||
| import org.junit.Assert; | ||||
| import org.springframework.dao.ConcurrencyFailureException; | ||||
|  | ||||
| import org.alfresco.model.ContentModel; | ||||
| import org.alfresco.module.org_alfresco_module_rm.test.util.BaseRMTestCase; | ||||
| import org.alfresco.query.PagingRequest; | ||||
| import org.alfresco.query.PagingResults; | ||||
| import org.alfresco.repo.security.authentication.AuthenticationUtil; | ||||
| import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; | ||||
| import org.alfresco.repo.site.SiteModel; | ||||
| import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; | ||||
| import org.alfresco.service.cmr.repository.NodeRef; | ||||
| import org.alfresco.service.cmr.security.AccessPermission; | ||||
| import org.alfresco.service.cmr.security.AccessStatus; | ||||
| import org.alfresco.service.cmr.security.AuthorityType; | ||||
| import org.alfresco.service.cmr.site.SiteService; | ||||
| import org.alfresco.service.cmr.site.SiteVisibility; | ||||
| import org.alfresco.util.GUID; | ||||
| @@ -206,7 +215,8 @@ public class ExtendedSecurityServiceImplTest extends BaseRMTestCase | ||||
|         final NodeRef record = doTestInTransaction(new Test<NodeRef>() { | ||||
|             public NodeRef run() throws Exception | ||||
|             { | ||||
|                 NodeRef record = fileFolderService.create(documentLibrary, GUID.generate(), ContentModel.TYPE_CONTENT).getNodeRef(); | ||||
|                 NodeRef record = fileFolderService.create(documentLibrary, GUID.generate(), ContentModel.TYPE_CONTENT) | ||||
|                         .getNodeRef(); | ||||
|                 recordService.createRecord(filePlan, record); | ||||
|                 return record; | ||||
|             } | ||||
| @@ -279,4 +289,238 @@ public class ExtendedSecurityServiceImplTest extends BaseRMTestCase | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     public void testConcurrentSetWithRetry() | ||||
|     { | ||||
|         Set<String> extendedReaders = new HashSet<>(2); | ||||
|         Set<String> extendedWriters = new HashSet<>(2); | ||||
|  | ||||
|         Set<NodeRef> documents = setupConcurrentTestCase(10, extendedReaders, extendedWriters); | ||||
|  | ||||
|         // For each record created previously, spawn a thread to set extended security so we cause concurrency | ||||
|         // failure trying to create IPR groups with the same name | ||||
|         fireParallelExecutionOfSetExtendedSecurity(documents, extendedReaders, extendedWriters, true); | ||||
|  | ||||
|         // Look for duplicated IPR groups and verify all documents have the same groups assigned | ||||
|         verifyCreatedGroups(documents, false); | ||||
|  | ||||
|         AuthenticationUtil.clearCurrentSecurityContext(); | ||||
|     } | ||||
|  | ||||
|     public void testConcurrentSetWithoutRetry() | ||||
|     { | ||||
|         Set<String> extendedReaders = new HashSet<>(2); | ||||
|         Set<String> extendedWriters = new HashSet<>(2); | ||||
|  | ||||
|         Set<NodeRef> documents = setupConcurrentTestCase(10, extendedReaders, extendedWriters); | ||||
|  | ||||
|         // For each record created previously, spawn a thread to set extended security so we cause concurrency | ||||
|         // failure trying to create IPR groups with the same name. | ||||
|         // Since there is no retry, we expect to get a ConcurrencyFailureException | ||||
|         Assert.assertThrows(ConcurrencyFailureException.class, () -> { | ||||
|             fireParallelExecutionOfSetExtendedSecurity(documents, extendedReaders, extendedWriters, false); | ||||
|         }); | ||||
|  | ||||
|         // Look for duplicated IPR groups and verify all documents have the same groups assigned | ||||
|         // Since there was a ConcurrencyFailureException some threads failed to set extended security so some | ||||
|         // documents may not have IPR groups created. | ||||
|         verifyCreatedGroups(documents, true); | ||||
|  | ||||
|         AuthenticationUtil.clearCurrentSecurityContext(); | ||||
|     } | ||||
|  | ||||
|     private Set<NodeRef> setupConcurrentTestCase(int concurrentThreads, Set<String> extendedReaders, Set<String> extendedWriters) | ||||
|     { | ||||
|         final String usera = createTestUser(); | ||||
|         final String userb = createTestUser(); | ||||
|         final String owner = createTestUser(); | ||||
|  | ||||
|         extendedReaders.add(usera); | ||||
|         extendedReaders.add(userb); | ||||
|         extendedWriters.add(usera); | ||||
|         extendedWriters.add(userb); | ||||
|  | ||||
|         AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser(); | ||||
|  | ||||
|         // Create a site | ||||
|         NodeRef documentLib = createSite(new HashSet<>(), new HashSet<>()); | ||||
|  | ||||
|         // Create records in the site document library | ||||
|         return createRecords(concurrentThreads, documentLib, owner); | ||||
|     } | ||||
|  | ||||
|     private NodeRef createSite(Set<String> readers, Set<String> writers) | ||||
|     { | ||||
|         return retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<NodeRef>() { | ||||
|             @Override | ||||
|             public NodeRef execute() throws Throwable | ||||
|             { | ||||
|                 final String siteShortName = GUID.generate(); | ||||
|                 siteService.createSite(null, siteShortName, "test", "test", SiteVisibility.PRIVATE); | ||||
|                 readers.forEach(reader -> siteService.setMembership(siteShortName, reader, SiteModel.SITE_CONSUMER)); | ||||
|                 writers.forEach(writer -> siteService.setMembership(siteShortName, writer, SiteModel.SITE_COLLABORATOR)); | ||||
|                 return siteService.createContainer(siteShortName, SiteService.DOCUMENT_LIBRARY, null, null); | ||||
|             } | ||||
|         }, false, true); | ||||
|     } | ||||
|  | ||||
|     private Set<NodeRef> createRecords(int numRecords, NodeRef parent, String owner) | ||||
|     { | ||||
|         return retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Set<NodeRef>>() { | ||||
|             @Override | ||||
|             public Set<NodeRef> execute() throws Throwable | ||||
|             { | ||||
|                 int createdRecords = 0; | ||||
|                 Set<NodeRef> documents = new HashSet<>(); | ||||
|                 while (createdRecords < numRecords) | ||||
|                 { | ||||
|                     final NodeRef doc = fileFolderService.create(parent, GUID.generate(), ContentModel.TYPE_CONTENT).getNodeRef(); | ||||
|                     ownableService.setOwner(doc, owner); | ||||
|                     recordService.createRecord(filePlan, doc, rmFolder, true); | ||||
|                     recordService.file(doc); | ||||
|                     recordService.complete(doc); | ||||
|                     documents.add(doc); | ||||
|                     createdRecords++; | ||||
|                 } | ||||
|                 return documents; | ||||
|             } | ||||
|         }, false, true); | ||||
|     } | ||||
|  | ||||
|     private void setExtendedSecurity(NodeRef doc, Set<String> readers, Set<String> writers, boolean useRetry) | ||||
|     { | ||||
|         if (!useRetry) | ||||
|         { | ||||
|             setExtendedSecurity(doc, readers, writers); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Void>() { | ||||
|             @Override | ||||
|             public Void execute() throws Throwable | ||||
|             { | ||||
|                 setExtendedSecurity(doc, readers, writers); | ||||
|                 return null; | ||||
|             } | ||||
|         }, false, true); | ||||
|     } | ||||
|  | ||||
|     private void setExtendedSecurity(NodeRef doc, Set<String> readers, Set<String> writers) | ||||
|     { | ||||
|         AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser(); | ||||
|         extendedSecurityService.set(doc, readers, writers); | ||||
|     } | ||||
|  | ||||
|     private void fireParallelExecutionOfSetExtendedSecurity(Set<NodeRef> documents, Set<String> extendedReaders, Set<String> extendedWriters, boolean useRetry) | ||||
|     { | ||||
|         CompletableFuture<?>[] futures = documents.stream() | ||||
|                 .map(doc -> CompletableFuture.runAsync(() -> setExtendedSecurity(doc, extendedReaders, extendedWriters, useRetry))) | ||||
|                 .toArray(CompletableFuture[]::new); | ||||
|  | ||||
|         try | ||||
|         { | ||||
|             CompletableFuture.allOf(futures).join(); | ||||
|         } | ||||
|         catch (Exception e) | ||||
|         { | ||||
|             Throwable cause = e.getCause(); | ||||
|             if (cause instanceof ConcurrencyFailureException) | ||||
|             { | ||||
|                 throw (ConcurrencyFailureException) cause; | ||||
|             } | ||||
|             throw new RuntimeException("Error during parallel execution", e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void verifyCreatedGroups(Set<NodeRef> documents, boolean onlyDuplicatesValidation) | ||||
|     { | ||||
|         retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Void>() { | ||||
|             @Override | ||||
|             public Void execute() throws Throwable | ||||
|             { | ||||
|                 Set<String> expectedAuthorities = null; | ||||
|                 Set<Set<String>> errors = new HashSet<>(); | ||||
|                 for (NodeRef doc : documents) | ||||
|                 { | ||||
|                     Set<AccessPermission> permissions = permissionService.getAllSetPermissions(doc); | ||||
|                     Set<String> authorities = getDocumentAuthorities(permissions); | ||||
|                     Set<String> authoritiesById = getAuthorityIds(authorities); | ||||
|  | ||||
|                     verifyIPRGroups(authorities, onlyDuplicatesValidation); | ||||
|  | ||||
|                     if (onlyDuplicatesValidation) | ||||
|                     { | ||||
|                         // Some documents may not have IPR groups created if there was a ConcurrencyFailureException | ||||
|                         continue; | ||||
|                     } | ||||
|  | ||||
|                     // All documents should have the same exact set of groups assigned | ||||
|                     if (expectedAuthorities == null) | ||||
|                     { | ||||
|                         expectedAuthorities = authoritiesById; | ||||
|                     } | ||||
|  | ||||
|                     if (!expectedAuthorities.equals(authoritiesById)) | ||||
|                     { | ||||
|                         errors.add(authoritiesById); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 assertTrue("Unexpected authorities linked to document", errors.isEmpty()); | ||||
|  | ||||
|                 return null; | ||||
|             } | ||||
|         }, false, true); | ||||
|     } | ||||
|  | ||||
|     private Set<String> getDocumentAuthorities(Set<AccessPermission> permissions) | ||||
|     { | ||||
|         Set<String> authorities = new HashSet<>(); | ||||
|  | ||||
|         for (AccessPermission accessPermission : permissions) | ||||
|         { | ||||
|             String authority = accessPermission.getAuthority(); | ||||
|             String authName = authorityService.getName(AuthorityType.GROUP, authority); | ||||
|             authorities.add(authName); | ||||
|  | ||||
|         } | ||||
|         return authorities; | ||||
|     } | ||||
|  | ||||
|     private Set<String> getAuthorityIds(Set<String> authorities) | ||||
|     { | ||||
|         Set<String> authorityIds = new HashSet<>(); | ||||
|         for (String authority : authorities) | ||||
|         { | ||||
|             String authId = authorityService.getAuthorityNodeRef(authority) != null | ||||
|                     ? authorityService.getAuthorityNodeRef(authority).getId() | ||||
|                     : null; | ||||
|             authorityIds.add(authId); | ||||
|         } | ||||
|         return authorityIds; | ||||
|     } | ||||
|  | ||||
|     private void verifyIPRGroups(Set<String> authorities, boolean onlyDuplicatesValidation) | ||||
|     { | ||||
|         boolean hasGroupIPR = false; | ||||
|  | ||||
|         for (String authorityName : authorities) | ||||
|         { | ||||
|             String shortName = authorityService.getShortName(authorityName); | ||||
|  | ||||
|             if (authorityName.startsWith("GROUP_IPR")) | ||||
|             { | ||||
|                 hasGroupIPR = true; | ||||
|                 PagingResults<String> results = authorityService.getAuthorities(AuthorityType.GROUP, null, shortName, false, | ||||
|                         false, new PagingRequest(0, 10)); | ||||
|  | ||||
|                 assertEquals("No duplicated IPR group expected", 1, results.getPage().size()); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (!onlyDuplicatesValidation) | ||||
|         { | ||||
|             assertTrue("No IPR Groups created", hasGroupIPR); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-governance-services-community-repo-parent</artifactId> | ||||
|         <version>25.3.0.20</version> | ||||
|         <version>25.3.0.62</version> | ||||
|     </parent> | ||||
|  | ||||
|     <build> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo</artifactId> | ||||
|         <version>25.3.0.20</version> | ||||
|         <version>25.3.0.62</version> | ||||
|     </parent> | ||||
|  | ||||
|     <modules> | ||||
|   | ||||
| @@ -8,7 +8,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-amps</artifactId> | ||||
|         <version>25.3.0.20</version> | ||||
|         <version>25.3.0.62</version> | ||||
|     </parent> | ||||
|  | ||||
|     <properties> | ||||
| @@ -51,8 +51,8 @@ | ||||
|             </exclusions> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>commons-lang</groupId> | ||||
|             <artifactId>commons-lang</artifactId> | ||||
|             <groupId>org.apache.commons</groupId> | ||||
|             <artifactId>commons-lang3</artifactId> | ||||
|             <scope>provided</scope> | ||||
|         </dependency> | ||||
|  | ||||
|   | ||||
| @@ -28,7 +28,7 @@ import java.util.ResourceBundle; | ||||
| import java.util.regex.Matcher; | ||||
| import java.util.regex.Pattern; | ||||
|  | ||||
| import org.apache.commons.lang.StringEscapeUtils; | ||||
| import org.apache.commons.lang3.StringEscapeUtils; | ||||
| import org.json.simple.JSONObject; | ||||
| import org.springframework.extensions.webscripts.Cache; | ||||
| import org.springframework.extensions.webscripts.Status; | ||||
| @@ -92,7 +92,7 @@ public class WikiPageGet extends AbstractWikiWebScript | ||||
|                 { | ||||
|                     links.add(link); | ||||
|                     // build the list of available pages | ||||
|                     WikiPageInfo wikiPage = wikiService.getWikiPage(site.getShortName(), StringEscapeUtils.unescapeHtml(link)); | ||||
|                     WikiPageInfo wikiPage = wikiService.getWikiPage(site.getShortName(), StringEscapeUtils.unescapeHtml4(link)); | ||||
|                     if (wikiPage != null) | ||||
|                     { | ||||
|                         pageTitles.add(wikiPage.getTitle()); | ||||
|   | ||||
| @@ -91,6 +91,15 @@ function doclist_getAllNodes(parsedArgs, filterParams, query, totalItemCount) | ||||
|    }; | ||||
| } | ||||
|  | ||||
| function sanitizeJunkFavouriteKeys(favourites){ | ||||
|    for (var key in favourites) { | ||||
|       if (!key || key.trim() === "") { | ||||
|          delete favourites[key]; | ||||
|       } | ||||
|    } | ||||
|    return favourites; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Main entry point: Create collection of documents and folders in the given space | ||||
|  * | ||||
| @@ -123,6 +132,47 @@ function doclist_main() | ||||
|     | ||||
|    if (logger.isLoggingEnabled()) | ||||
|       logger.log("doclist.lib.js - NodeRef: " + parsedArgs.nodeRef + " Query: " + query); | ||||
|  | ||||
|    favourites = sanitizeJunkFavouriteKeys(favourites); | ||||
|    if(query === null) | ||||
|    { | ||||
|       return { | ||||
|          luceneQuery: "", | ||||
|          paging: { | ||||
|             totalRecords: 0, | ||||
|             startIndex: 0 | ||||
|          }, | ||||
|          container: parsedArgs.rootNode, | ||||
|          parent: null, | ||||
|          onlineEditing: utils.moduleInstalled("org.alfresco.module.vti"), | ||||
|          itemCount: { | ||||
|             folders: 0, | ||||
|             documents: 0 | ||||
|          }, | ||||
|          items: [], | ||||
|          customJSON: slingshotDocLib.getJSON() | ||||
|       }; | ||||
|    } | ||||
|  | ||||
|    if(Object.keys(favourites).length === 0 && query === null) | ||||
|    { | ||||
|       return { | ||||
|          luceneQuery: "", | ||||
|          paging: { | ||||
|             totalRecords: 0, | ||||
|             startIndex: 0 | ||||
|          }, | ||||
|          container: parsedArgs.rootNode, | ||||
|          parent: null, | ||||
|          onlineEditing: utils.moduleInstalled("org.alfresco.module.vti"), | ||||
|          itemCount: { | ||||
|             folders: 0, | ||||
|             documents: 0 | ||||
|          }, | ||||
|          items: [], | ||||
|          customJSON: slingshotDocLib.getJSON() | ||||
|       }; | ||||
|    } | ||||
|     | ||||
|    var totalItemCount = filterParams.limitResults ? parseInt(filterParams.limitResults, 10) : -1; | ||||
|    // For all sites documentLibrary query we pull in all available results and post filter | ||||
|   | ||||
| @@ -182,11 +182,14 @@ var Filters = | ||||
|          case "favourites": | ||||
|             for (var favourite in favourites) | ||||
|             { | ||||
|                if (filterQuery) | ||||
|                if (favourite && favourite.trim() !== "") | ||||
|                { | ||||
|                   filterQuery += " OR "; | ||||
|                   if (filterQuery) | ||||
|                   { | ||||
|                      filterQuery += " OR "; | ||||
|                   } | ||||
|                   filterQuery += "ID:\"" + favourite + "\""; | ||||
|                } | ||||
|                filterQuery += "ID:\"" + favourite + "\""; | ||||
|             } | ||||
|              | ||||
|             if (filterQuery.length !== 0) | ||||
| @@ -201,7 +204,13 @@ var Filters = | ||||
|             else | ||||
|             { | ||||
|                // empty favourites query | ||||
|                filterQuery = "+ID:\"\""; | ||||
|                logger.warn("No favourites found for user: " + person.properties.userName); | ||||
|                return { | ||||
|                   query: null, | ||||
|                   limitResults: 0, | ||||
|                   sort: [], | ||||
|                   language: "lucene" | ||||
|                }; | ||||
|             } | ||||
|              | ||||
|             filterParams.query = filterQuery; | ||||
| @@ -224,15 +233,15 @@ var Filters = | ||||
|             filterParams.query = "+ID:\"" + parsedArgs.nodeRef + "\""; | ||||
|             break; | ||||
|  | ||||
|          case "tag": | ||||
|             // Remove any trailing "/" character | ||||
|             if (filterData.charAt(filterData.length - 1) == "/") | ||||
|             { | ||||
|                filterData = filterData.slice(0, -1); | ||||
|             } | ||||
|             filterQuery = this.constructPathQuery(parsedArgs); | ||||
|             filterParams.query = filterQuery + " +PATH:\"/cm:taggable/cm:" + search.ISO9075Encode(filterData) + "/member\""; | ||||
|             break; | ||||
|           case "tag": | ||||
|               // Remove any trailing "/" character | ||||
|               if (filterData.charAt(filterData.length - 1) == "/") | ||||
|               { | ||||
|                   filterData = filterData.slice(0, -1); | ||||
|               } | ||||
|               filterQuery = this.constructPathQuery(parsedArgs); | ||||
|               filterParams.query = filterQuery + " +TAG:\"" + search.ISO9075Encode(filterData) + "\""; | ||||
|               break; | ||||
|  | ||||
|          case "category": | ||||
|             // Remove any trailing "/" character | ||||
| @@ -240,8 +249,15 @@ var Filters = | ||||
|             { | ||||
|                filterData = filterData.slice(0, -1); | ||||
|             } | ||||
|             filterQuery = this.constructPathQuery(parsedArgs); | ||||
|             filterParams.query = filterQuery + " +PATH:\"/cm:categoryRoot/cm:generalclassifiable" + Filters.iso9075EncodePath(filterData) + "/member\""; | ||||
|  | ||||
|             var categoryNodeRef = this.getCategoryNodeRef(filterData); | ||||
|  | ||||
|             if (categoryNodeRef && search.findNode(categoryNodeRef) != null) { | ||||
|                filterParams.query = filterQuery + ' +@cm\\:categories:"' + categoryNodeRef + '"'; | ||||
|             } else { | ||||
|                logger.warn("category filter: skipping invalid category node : " + categoryNodeRef); | ||||
|             } | ||||
|             filterParams.language = "fts-alfresco"; | ||||
|             break; | ||||
|  | ||||
|          case "aspect": | ||||
| @@ -262,11 +278,24 @@ var Filters = | ||||
|       { | ||||
|          filterParams.query += " " + (Filters.TYPE_MAP[parsedArgs.type] || ""); | ||||
|       } | ||||
|  | ||||
|       logger.warn("Final Query : " + filterParams.query); | ||||
|       return filterParams; | ||||
|    }, | ||||
|     | ||||
|    constructPathQuery: function constructPathQuery(parsedArgs) | ||||
|  | ||||
|     getCategoryNodeRef: function(categoryName) { | ||||
|         var results = search.luceneSearch( | ||||
|             'PATH:"/cm:categoryRoot/cm:generalclassifiable//*" AND @cm\\:name:"' + categoryName + '"' | ||||
|         ); | ||||
|  | ||||
|         if (results && results.length > 0) { | ||||
|             return results[0].nodeRef.toString(); | ||||
|         } | ||||
|  | ||||
|         logger.warn("Category not found: " + categoryName); | ||||
|         return null; | ||||
|     }, | ||||
|  | ||||
|    constructPathQuery: function(parsedArgs) | ||||
|    { | ||||
|       var pathQuery = ""; | ||||
|       if (parsedArgs.libraryRoot != companyhome || parsedArgs.nodeRef != "alfresco://company/home") | ||||
|   | ||||
| @@ -25,7 +25,7 @@ import java.util.regex.Matcher; | ||||
| import java.util.regex.Pattern; | ||||
| import jakarta.transaction.UserTransaction; | ||||
|  | ||||
| import org.apache.commons.lang.StringEscapeUtils; | ||||
| import org.apache.commons.lang3.StringEscapeUtils; | ||||
| import org.apache.commons.logging.Log; | ||||
| import org.apache.commons.logging.LogFactory; | ||||
| import org.json.JSONArray; | ||||
| @@ -958,7 +958,7 @@ public class WikiRestApiTest extends BaseWebScriptTest | ||||
|                 String link = m.group(1); | ||||
|                 link += "?title=<script>alert('xss');</script>"; | ||||
|                 WikiPageInfo wikiPage2 = this.wikiService.getWikiPage(SITE_SHORT_NAME_WIKI, link); | ||||
|                 WikiPageInfo wikiPage1 = this.wikiService.getWikiPage(SITE_SHORT_NAME_WIKI, StringEscapeUtils.unescapeHtml(link)); | ||||
|                 WikiPageInfo wikiPage1 = this.wikiService.getWikiPage(SITE_SHORT_NAME_WIKI, StringEscapeUtils.unescapeHtml4(link)); | ||||
|                 assertEquals(wikiPage2, wikiPage1); | ||||
|             } | ||||
|  | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|    <parent> | ||||
|       <groupId>org.alfresco</groupId> | ||||
|       <artifactId>alfresco-community-repo</artifactId> | ||||
|       <version>25.3.0.20</version> | ||||
|       <version>25.3.0.62</version> | ||||
|    </parent> | ||||
|  | ||||
|    <dependencies> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo</artifactId> | ||||
|         <version>25.3.0.20</version> | ||||
|         <version>25.3.0.62</version> | ||||
|     </parent> | ||||
|  | ||||
|     <properties> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo</artifactId> | ||||
|         <version>25.3.0.20</version> | ||||
|         <version>25.3.0.62</version> | ||||
|     </parent> | ||||
|  | ||||
|     <dependencies> | ||||
|   | ||||
| @@ -9,6 +9,6 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-packaging</artifactId> | ||||
|         <version>25.3.0.20</version> | ||||
|         <version>25.3.0.62</version> | ||||
|     </parent> | ||||
| </project> | ||||
|   | ||||
| @@ -37,8 +37,7 @@ commons-fileupload  http://jakarta.apache.org/commons/ | ||||
| commons-httpclient  http://jakarta.apache.org/commons/  | ||||
| commons-io  http://jakarta.apache.org/commons/  | ||||
| commons-jxpath  http://jakarta.apache.org/commons/  | ||||
| commons-lang    http://jakarta.apache.org/commons/  | ||||
| commons-lang3   http://jakarta.apache.org/commons/  | ||||
| commons-lang3   http://jakarta.apache.org/commons/ | ||||
| commons-logging http://jakarta.apache.org/commons/  | ||||
| commons-net http://jakarta.apache.org/commons/ | ||||
| commons-pool    http://jakarta.apache.org/commons/  | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-packaging</artifactId> | ||||
|         <version>25.3.0.20</version> | ||||
|         <version>25.3.0.62</version> | ||||
|     </parent> | ||||
|  | ||||
|     <properties> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo</artifactId> | ||||
|         <version>25.3.0.20</version> | ||||
|         <version>25.3.0.62</version> | ||||
|     </parent> | ||||
|  | ||||
|     <modules> | ||||
|   | ||||
| @@ -6,7 +6,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-packaging</artifactId> | ||||
|         <version>25.3.0.20</version> | ||||
|         <version>25.3.0.62</version> | ||||
|     </parent> | ||||
|  | ||||
|     <modules> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-tests</artifactId> | ||||
|         <version>25.3.0.20</version> | ||||
|         <version>25.3.0.62</version> | ||||
|     </parent> | ||||
|  | ||||
|     <organization> | ||||
|   | ||||
| @@ -5,7 +5,7 @@ import java.util.Date; | ||||
| import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException; | ||||
| import org.apache.chemistry.opencmis.commons.exceptions.CmisPermissionDeniedException; | ||||
| import org.apache.chemistry.opencmis.commons.exceptions.CmisUnauthorizedException; | ||||
| import org.apache.commons.lang.time.DateUtils; | ||||
| import org.apache.commons.lang3.time.DateUtils; | ||||
| import org.testng.annotations.BeforeClass; | ||||
| import org.testng.annotations.Test; | ||||
|  | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-tests</artifactId> | ||||
|         <version>25.3.0.20</version> | ||||
|         <version>25.3.0.62</version> | ||||
|     </parent> | ||||
|  | ||||
|     <developers> | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-tests</artifactId> | ||||
|         <version>25.3.0.20</version> | ||||
|         <version>25.3.0.62</version> | ||||
|     </parent> | ||||
|  | ||||
|     <developers> | ||||
|   | ||||
| @@ -8,7 +8,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-tests</artifactId> | ||||
|         <version>25.3.0.20</version> | ||||
|         <version>25.3.0.62</version> | ||||
|     </parent> | ||||
|  | ||||
|     <properties> | ||||
|   | ||||
| @@ -675,6 +675,11 @@ public class RestWrapper extends DSLWrapper<RestWrapper> | ||||
|         { | ||||
|             returnedResponse = onRequest().get(restRequest.getPath(), restRequest.getPathParams()).andReturn(); | ||||
|         } | ||||
|         else if (HttpMethod.PATCH.equals(httpMethod)) | ||||
|         { | ||||
|             returnedResponse = onRequest().body(restRequest.getBody()) | ||||
|                     .patch(restRequest.getPath(), restRequest.getPathParams()).andReturn(); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             returnedResponse = onRequest().get(restRequest.getPath(), restRequest.getPathParams()).andReturn(); | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-tests</artifactId> | ||||
|         <version>25.3.0.20</version> | ||||
|         <version>25.3.0.62</version> | ||||
|     </parent> | ||||
|  | ||||
|     <developers> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-packaging</artifactId> | ||||
|         <version>25.3.0.20</version> | ||||
|         <version>25.3.0.62</version> | ||||
|     </parent> | ||||
|  | ||||
|     <properties> | ||||
|   | ||||
							
								
								
									
										38
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								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>25.3.0.20</version> | ||||
|     <version>25.3.0.62</version> | ||||
|     <packaging>pom</packaging> | ||||
|     <name>Alfresco Community Repo Parent</name> | ||||
|  | ||||
| @@ -51,14 +51,14 @@ | ||||
|         <dependency.alfresco-server-root.version>7.0.2</dependency.alfresco-server-root.version> | ||||
|         <dependency.activiti-engine.version>5.23.0</dependency.activiti-engine.version> | ||||
|         <dependency.activiti.version>5.23.0</dependency.activiti.version> | ||||
|         <dependency.alfresco-transform-core.version>5.2.0-A.3</dependency.alfresco-transform-core.version> | ||||
|         <dependency.alfresco-transform-service.version>4.2.0</dependency.alfresco-transform-service.version> | ||||
|         <dependency.alfresco-transform-core.version>5.2.2</dependency.alfresco-transform-core.version> | ||||
|         <dependency.alfresco-transform-service.version>4.2.2</dependency.alfresco-transform-service.version> | ||||
|         <dependency.alfresco-greenmail.version>7.1</dependency.alfresco-greenmail.version> | ||||
|         <dependency.acs-event-model.version>1.0.5</dependency.acs-event-model.version> | ||||
|         <dependency.acs-event-model.version>1.0.11</dependency.acs-event-model.version> | ||||
|  | ||||
|         <dependency.aspectj.version>1.9.22.1</dependency.aspectj.version> | ||||
|         <dependency.spring.version>6.2.8</dependency.spring.version> | ||||
|         <dependency.spring-security.version>6.3.9</dependency.spring-security.version> | ||||
|         <dependency.spring.version>6.2.11</dependency.spring.version> | ||||
|         <dependency.spring-security.version>6.4.11</dependency.spring-security.version> | ||||
|         <dependency.antlr.version>3.5.3</dependency.antlr.version> | ||||
|         <dependency.jackson.version>2.17.2</dependency.jackson.version> | ||||
|         <dependency.cxf.version>4.1.2</dependency.cxf.version> | ||||
| @@ -82,7 +82,7 @@ | ||||
|         <dependency.slf4j.version>2.0.16</dependency.slf4j.version> | ||||
|         <dependency.log4j.version>2.25.1</dependency.log4j.version> | ||||
|         <dependency.groovy.version>3.0.25</dependency.groovy.version> | ||||
|         <dependency.tika.version>2.9.2</dependency.tika.version> | ||||
|         <dependency.tika.version>3.2.3</dependency.tika.version> | ||||
|         <dependency.truezip.version>7.7.10</dependency.truezip.version> | ||||
|         <dependency.poi.version>5.4.0</dependency.poi.version> | ||||
|         <dependency.jboss.logging.version>3.5.0.Final</dependency.jboss.logging.version> | ||||
| @@ -115,7 +115,7 @@ | ||||
|         <dependency.jakarta-json-path.version>2.9.0</dependency.jakarta-json-path.version> | ||||
|         <dependency.json-smart.version>2.5.2</dependency.json-smart.version> | ||||
|         <alfresco.googledrive.version>4.1.0</alfresco.googledrive.version> | ||||
|         <alfresco.aos-module.version>3.3.0</alfresco.aos-module.version> | ||||
|         <alfresco.aos-module.version>3.4.0</alfresco.aos-module.version> | ||||
|         <alfresco.api-explorer.version>25.2.0</alfresco.api-explorer.version> <!-- Also in alfresco-enterprise-share --> | ||||
|  | ||||
|         <alfresco.maven-plugin.version>2.2.0</alfresco.maven-plugin.version> | ||||
| @@ -154,7 +154,7 @@ | ||||
|         <connection>scm:git:https://github.com/Alfresco/alfresco-community-repo.git</connection> | ||||
|         <developerConnection>scm:git:https://github.com/Alfresco/alfresco-community-repo.git</developerConnection> | ||||
|         <url>https://github.com/Alfresco/alfresco-community-repo</url> | ||||
|         <tag>25.3.0.20</tag> | ||||
|         <tag>25.3.0.62</tag> | ||||
|     </scm> | ||||
|  | ||||
|     <distributionManagement> | ||||
| @@ -170,6 +170,12 @@ | ||||
|  | ||||
|     <dependencyManagement> | ||||
|         <dependencies> | ||||
|             <!-- v1.10 has 0BSD license it must be consulted with Legal --> | ||||
|             <dependency> | ||||
|                 <groupId>org.tukaani</groupId> | ||||
|                 <artifactId>xz</artifactId> | ||||
|                 <version>1.9</version> | ||||
|             </dependency> | ||||
|             <!-- Jakarta... --> | ||||
|             <dependency> | ||||
|                 <groupId>jakarta.xml.bind</groupId> | ||||
| @@ -422,9 +428,9 @@ | ||||
|                 <version>1.18.0</version> | ||||
|             </dependency> | ||||
|             <dependency> | ||||
|                 <groupId>commons-lang</groupId> | ||||
|                 <artifactId>commons-lang</artifactId> | ||||
|                 <version>2.6</version> | ||||
|                 <groupId>org.apache.commons</groupId> | ||||
|                 <artifactId>commons-lang3</artifactId> | ||||
|                 <version>3.18.0</version> | ||||
|             </dependency> | ||||
|             <dependency> | ||||
|                 <groupId>commons-io</groupId> | ||||
| @@ -1125,16 +1131,10 @@ | ||||
|                                             <exclude>jakarta.xml.soap:jakarta.xml.soap-api:(, 2.0.1)</exclude> | ||||
|                                             <exclude>jakarta.jws:jakarta.jws-api:(, 3.0.0)</exclude> | ||||
| <!--                                            Enforce ban bouncycastle dependencies other than specified under <includes> section--> | ||||
|                                             <exclude>org.bouncycastle</exclude> | ||||
|                                             <exclude>org.bouncycastle:(,1.81)</exclude> | ||||
| <!--                                            Enforce one version of Jaxb--> | ||||
|                                             <exclude>com.sun.xml.bind</exclude> | ||||
|                                         </excludes> | ||||
|                                         <includes> | ||||
|                                             <include>org.bouncycastle:bcprov-jdk18on:[1.78.1,)</include> | ||||
|                                             <include>org.bouncycastle:bcmail-jdk18on:[1.78.1,)</include> | ||||
|                                             <include>org.bouncycastle:bcpkix-jdk18on:[1.78.1,)</include> | ||||
|                                             <include>org.bouncycastle:bcutil-jdk18on:[1.78.1,)</include> | ||||
|                                         </includes> | ||||
|                                     </bannedDependencies> | ||||
|                                 </rules> | ||||
|                                 <fail>true</fail> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo</artifactId> | ||||
|         <version>25.3.0.20</version> | ||||
|         <version>25.3.0.62</version> | ||||
|     </parent> | ||||
|  | ||||
|     <dependencies> | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
|  * #%L | ||||
|  * Alfresco Remote API | ||||
|  * %% | ||||
|  * Copyright (C) 2005 - 2016 Alfresco Software Limited | ||||
|  * Copyright (C) 2005 - 2025 Alfresco Software Limited | ||||
|  * %% | ||||
|  * This file is part of the Alfresco software.  | ||||
|  * If the software was purchased under a paid Alfresco license, the terms of  | ||||
| @@ -31,7 +31,10 @@ import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
|  | ||||
| import org.apache.commons.lang3.StringUtils; | ||||
| import org.json.simple.JSONObject; | ||||
| import org.owasp.html.PolicyFactory; | ||||
| import org.owasp.html.Sanitizers; | ||||
| import org.springframework.extensions.webscripts.Cache; | ||||
| import org.springframework.extensions.webscripts.Status; | ||||
| import org.springframework.extensions.webscripts.WebScriptRequest; | ||||
| @@ -67,6 +70,19 @@ public class CommentsPost extends AbstractCommentsWebScript | ||||
|         // get json object from request | ||||
|         JSONObject json = parseJSON(req); | ||||
|  | ||||
|         // Validating and Sanitizing comment content to prevent XSS | ||||
|         String commentContent = getOrNull(json, "content"); | ||||
|         if (StringUtils.isBlank(commentContent)) | ||||
|         { | ||||
|             throw new IllegalArgumentException("Comment content must not be empty"); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             PolicyFactory policy = Sanitizers.FORMATTING.and(Sanitizers.LINKS); | ||||
|             String safeContent = policy.sanitize(commentContent); | ||||
|             json.replace("content", safeContent); | ||||
|         } | ||||
|  | ||||
|         /* MNT-10231, MNT-9771 fix */ | ||||
|         this.behaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE); | ||||
|  | ||||
|   | ||||
| @@ -51,6 +51,14 @@ function main() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     var contentChanged = false; | ||||
|     if (itemKind === "node") { | ||||
|         contentChanged = metadataExtractAction.isContentChanged(itemId,repoFormData); | ||||
|     } | ||||
|     if(logger.isLoggingEnabled() && contentChanged) { | ||||
|         logger.log("Content has been changed"); | ||||
|     } | ||||
|  | ||||
|     var persistedObject = null; | ||||
|     try | ||||
|     { | ||||
| @@ -83,9 +91,50 @@ function main() | ||||
|         | ||||
|         return; | ||||
|     } | ||||
|     if (itemKind === "node") { | ||||
|         checkAndExtractNodeMetadata(persistedObject, itemId, contentChanged); | ||||
|     } | ||||
|      | ||||
|     model.persistedObject = persistedObject.toString(); | ||||
|     model.message = "Successfully persisted form for item [" + itemKind + "]" + itemId; | ||||
| } | ||||
|  | ||||
| function checkAndExtractNodeMetadata(persistedObject, itemId, isContentChanged) { | ||||
|     var nodeRefStr = toNodeRefString(persistedObject, itemId); | ||||
|     var node = search.findNode(nodeRefStr); | ||||
|  | ||||
|     if (node == null) { | ||||
|         if (logger.isLoggingEnabled()) { | ||||
|             logger.log("Node not found: " + nodeRefStr); | ||||
|         } | ||||
|     } else if(isContentChanged) { | ||||
|         extractMetadata(node, isContentChanged); | ||||
|     } else { | ||||
|         if (logger.isLoggingEnabled()) { | ||||
|             logger.log("Content not changed, skipping metadata extraction for node: " + nodeRefStr); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| function extractMetadata(file, isContentChanged) { | ||||
|     var emAction = metadataExtractAction.create(isContentChanged); | ||||
|     if (emAction) { | ||||
|         // readOnly=false, newTransaction=false | ||||
|         emAction.execute(file, false, false); | ||||
|     } | ||||
| } | ||||
|  | ||||
| function toNodeRefString(persistedObject, itemId) { | ||||
|     // Prefer the NodeRef returned by saveForm (when kind=node). | ||||
|     if (persistedObject instanceof Packages.org.alfresco.service.cmr.repository.NodeRef) { | ||||
|         return persistedObject.toString(); | ||||
|     } | ||||
|     // If the client passed a full noderef, keep it. | ||||
|     if (itemId && itemId.indexOf("://") !== -1) { | ||||
|         return itemId; | ||||
|     } | ||||
|     // Otherwise assume SpacesStore UUID. | ||||
|     return "workspace://SpacesStore/" + itemId; | ||||
| } | ||||
| main(); | ||||
| @@ -2,7 +2,7 @@ function extractMetadata(file) | ||||
| { | ||||
|    // Extract metadata - via repository action for now. | ||||
|    // This should use the MetadataExtracter API to fetch properties, allowing for possible failures. | ||||
|    var emAction = actions.create("extract-metadata"); | ||||
|    var emAction = metadataExtractAction.create(true); | ||||
|    if (emAction != null) | ||||
|    { | ||||
|       // Call using readOnly = false, newTransaction = false | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo</artifactId> | ||||
|         <version>25.3.0.20</version> | ||||
|         <version>25.3.0.62</version> | ||||
|     </parent> | ||||
|  | ||||
|     <dependencies> | ||||
| @@ -94,7 +94,6 @@ | ||||
|         <dependency> | ||||
|             <groupId>org.apache.commons</groupId> | ||||
|             <artifactId>commons-lang3</artifactId> | ||||
|             <version>3.18.0</version> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>commons-codec</groupId> | ||||
| @@ -737,10 +736,6 @@ | ||||
|             <artifactId>reflections</artifactId> | ||||
|             <scope>test</scope> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>commons-lang</groupId> | ||||
|             <artifactId>commons-lang</artifactId> | ||||
|         </dependency> | ||||
|     </dependencies> | ||||
|  | ||||
|     <build> | ||||
|   | ||||
| @@ -0,0 +1,67 @@ | ||||
| /* | ||||
|  * #%L | ||||
|  * Alfresco Repository | ||||
|  * %% | ||||
|  * Copyright (C) 2005 - 2025 Alfresco Software Limited | ||||
|  * %% | ||||
|  * This file is part of the Alfresco software.  | ||||
|  * If the software was purchased under a paid Alfresco license, the terms of  | ||||
|  * the paid license agreement will prevail.  Otherwise, the software is  | ||||
|  * provided under the following open source license terms: | ||||
|  *  | ||||
|  * Alfresco is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Lesser General Public License as published by | ||||
|  * the Free Software Foundation, either version 3 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  *  | ||||
|  * Alfresco is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Lesser General Public License for more details. | ||||
|  *  | ||||
|  * You should have received a copy of the GNU Lesser General Public License | ||||
|  * along with Alfresco. If not, see <http://www.gnu.org/licenses/>. | ||||
|  * #L% | ||||
|  */ | ||||
| package org.alfresco.repo.action.evaluator; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| import org.alfresco.service.cmr.action.ActionCondition; | ||||
| import org.alfresco.service.cmr.action.ParameterDefinition; | ||||
| import org.alfresco.service.cmr.repository.NodeRef; | ||||
|  | ||||
| /** | ||||
|  * Content change condition evaluator implementation. Required only in Scripted Actions to allow determination if content has changed. <br> | ||||
|  * Usage in {@link org.alfresco.repo.jscript.MetaDataExtractAction#create(boolean)} | ||||
|  * | ||||
|  * @author Sayan Bhattacharya | ||||
|  */ | ||||
|  | ||||
| public class CompareContentConditionEvaluator extends ActionConditionEvaluatorAbstractBase | ||||
| { | ||||
|     /** | ||||
|      * Evaluator constants | ||||
|      */ | ||||
|     public static final String NAME = "compare-content"; | ||||
|     public static final String PARAM_IS_CONTENT_CHANGED = "isContentChanged"; | ||||
|  | ||||
|     /** | ||||
|      * @see ActionConditionEvaluatorAbstractBase#evaluateImpl(ActionCondition, NodeRef) | ||||
|      */ | ||||
|     @Override | ||||
|     public boolean evaluateImpl(ActionCondition ruleCondition, NodeRef actionedUponNodeRef) | ||||
|     { | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(List) | ||||
|      */ | ||||
|     @Override | ||||
|     protected void addParameterDefinitions(List<ParameterDefinition> paramList) | ||||
|     { | ||||
|         // No parameters to add | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -51,12 +51,14 @@ import java.util.HashMap; | ||||
| import java.util.HashSet; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.Optional; | ||||
| import java.util.Set; | ||||
|  | ||||
| import org.apache.commons.logging.Log; | ||||
| import org.apache.commons.logging.LogFactory; | ||||
|  | ||||
| import org.alfresco.model.ContentModel; | ||||
| import org.alfresco.repo.action.evaluator.CompareContentConditionEvaluator; | ||||
| import org.alfresco.repo.content.metadata.AbstractMappingMetadataExtracter; | ||||
| import org.alfresco.repo.content.metadata.AsynchronousExtractor; | ||||
| import org.alfresco.repo.content.metadata.MetadataExtracter; | ||||
| @@ -403,6 +405,7 @@ public class ContentMetadataExtracter extends ActionExecuterAbstractBase | ||||
|             ((AbstractMappingMetadataExtracter) extracter).setEnableStringTagging(enableStringTagging); | ||||
|         } | ||||
|  | ||||
|         MetadataExtracter.OverwritePolicy overwritePolicy = determineOverwritePolicy(ruleAction); | ||||
|         // Get all the node's properties | ||||
|         Map<QName, Serializable> nodeProperties = nodeService.getProperties(actionedUponNodeRef); | ||||
|  | ||||
| @@ -415,7 +418,7 @@ public class ContentMetadataExtracter extends ActionExecuterAbstractBase | ||||
|             modifiedProperties = extracter.extract( | ||||
|                     actionedUponNodeRef, | ||||
|                     reader, | ||||
|                     /* OverwritePolicy.PRAGMATIC, */ | ||||
|                     overwritePolicy, | ||||
|                     nodeProperties); | ||||
|         } | ||||
|         catch (Throwable e) | ||||
| @@ -456,6 +459,21 @@ public class ContentMetadataExtracter extends ActionExecuterAbstractBase | ||||
|                 stringTaggingSeparators); | ||||
|     } | ||||
|  | ||||
|     private MetadataExtracter.OverwritePolicy determineOverwritePolicy(Action ruleAction) | ||||
|     { | ||||
|         return Optional.ofNullable(ruleAction.getActionConditions()) | ||||
|                 .flatMap(conditions -> conditions.stream() | ||||
|                         .filter(e -> CompareContentConditionEvaluator.NAME.equals(e.getActionConditionDefinitionName())) | ||||
|                         .findAny() | ||||
|                         .map(e -> { | ||||
|                             Serializable contentChanged = e.getParameterValue(CompareContentConditionEvaluator.PARAM_IS_CONTENT_CHANGED); | ||||
|                             return Boolean.TRUE.equals(contentChanged) | ||||
|                                     ? MetadataExtracter.OverwritePolicy.EAGER | ||||
|                                     : MetadataExtracter.OverwritePolicy.PRAGMATIC; | ||||
|                         })) | ||||
|                 .orElse(MetadataExtracter.OverwritePolicy.PRAGMATIC); | ||||
|     } | ||||
|  | ||||
|     public static void addExtractedMetadataToNode(NodeRef actionedUponNodeRef, Map<QName, Serializable> nodeProperties, | ||||
|             Map<QName, Serializable> modifiedProperties, | ||||
|             NodeService nodeService, DictionaryService dictionaryService, | ||||
|   | ||||
| @@ -70,6 +70,13 @@ public interface AuditComponent | ||||
|      */ | ||||
|     public void setUserAuditFilter(UserAuditFilter userAuditFilter); | ||||
|  | ||||
|     /** | ||||
|      * @param auditRecordReporter | ||||
|      *            AuditRecordReporter | ||||
|      * @since 25.3 | ||||
|      */ | ||||
|     public void setAuditRecordReporter(AuditRecordReporter auditRecordReporter); | ||||
|  | ||||
|     /** | ||||
|      * Get all registered audit applications, whether active or not. | ||||
|      * | ||||
|   | ||||
| @@ -48,7 +48,6 @@ import org.alfresco.repo.audit.model.AuditModelRegistryImpl; | ||||
| import org.alfresco.repo.domain.audit.AuditDAO; | ||||
| import org.alfresco.repo.domain.propval.PropertyValueDAO; | ||||
| import org.alfresco.repo.security.authentication.AuthenticationUtil; | ||||
| import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; | ||||
| import org.alfresco.repo.transaction.AlfrescoTransactionSupport; | ||||
| import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; | ||||
| import org.alfresco.repo.transaction.RetryingTransactionHelper; | ||||
| @@ -73,8 +72,8 @@ public class AuditComponentImpl implements AuditComponent | ||||
| { | ||||
|     private static final String INBOUND_LOGGER = "org.alfresco.repo.audit.inbound"; | ||||
|  | ||||
|     private static Log logger = LogFactory.getLog(AuditComponentImpl.class); | ||||
|     private static Log loggerInbound = LogFactory.getLog(INBOUND_LOGGER); | ||||
|     private static final Log logger = LogFactory.getLog(AuditComponentImpl.class); | ||||
|     private static final Log loggerInbound = LogFactory.getLog(INBOUND_LOGGER); | ||||
|  | ||||
|     private AuditModelRegistryImpl auditModelRegistry; | ||||
|     private PropertyValueDAO propertyValueDAO; | ||||
| @@ -82,6 +81,7 @@ public class AuditComponentImpl implements AuditComponent | ||||
|     private TransactionService transactionService; | ||||
|     private AuditFilter auditFilter; | ||||
|     private UserAuditFilter userAuditFilter; | ||||
|     private AuditRecordReporter auditRecordReporter; | ||||
|  | ||||
|     /** | ||||
|      * Default constructor | ||||
| @@ -140,6 +140,11 @@ public class AuditComponentImpl implements AuditComponent | ||||
|         this.userAuditFilter = userAuditFilter; | ||||
|     } | ||||
|  | ||||
|     public void setAuditRecordReporter(AuditRecordReporter auditRecordReporter) | ||||
|     { | ||||
|         this.auditRecordReporter = auditRecordReporter; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * {@inheritDoc} | ||||
|      * | ||||
| @@ -215,7 +220,7 @@ public class AuditComponentImpl implements AuditComponent | ||||
|     public int deleteAuditEntries(List<Long> auditEntryIds) | ||||
|     { | ||||
|         // Shortcut, if necessary | ||||
|         if (auditEntryIds.size() == 0) | ||||
|         if (auditEntryIds.isEmpty()) | ||||
|         { | ||||
|             return 0; | ||||
|         } | ||||
| @@ -234,7 +239,7 @@ public class AuditComponentImpl implements AuditComponent | ||||
|         { | ||||
|             Long disabledPathsId = application.getDisabledPathsId(); | ||||
|             Set<String> disabledPaths = (Set<String>) propertyValueDAO.getPropertyById(disabledPathsId); | ||||
|             return new HashSet<String>(disabledPaths); | ||||
|             return new HashSet<>(disabledPaths); | ||||
|         } | ||||
|         catch (Throwable e) | ||||
|         { | ||||
| @@ -254,6 +259,16 @@ public class AuditComponentImpl implements AuditComponent | ||||
|         return auditModelRegistry.isAuditEnabled(); | ||||
|     } | ||||
|  | ||||
|     public boolean isAuditingToDatabaseEnabled() | ||||
|     { | ||||
|         return auditModelRegistry.isAuditingToDatabaseEnabled(); | ||||
|     } | ||||
|  | ||||
|     public boolean isAuditingToAuditStorageEnabled() | ||||
|     { | ||||
|         return auditModelRegistry.isAuditingToAuditStorageEnabled(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * {@inheritDoc} | ||||
|      * | ||||
| @@ -309,7 +324,7 @@ public class AuditComponentImpl implements AuditComponent | ||||
|     { | ||||
|         PathMapper pathMapper = auditModelRegistry.getAuditPathMapper(); | ||||
|         Set<String> mappedPaths = pathMapper.getMappedPathsWithPartialMatch(path); | ||||
|         return loggerInbound.isDebugEnabled() || mappedPaths.size() > 0; | ||||
|         return loggerInbound.isDebugEnabled() || !mappedPaths.isEmpty(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -346,7 +361,7 @@ public class AuditComponentImpl implements AuditComponent | ||||
|  | ||||
|         // Check if there are any entries that match or supercede the given path | ||||
|         String disablingPath = null; | ||||
|         ; | ||||
|  | ||||
|         for (String disabledPath : disabledPaths) | ||||
|         { | ||||
|             if (path.startsWith(disabledPath)) | ||||
| @@ -573,7 +588,7 @@ public class AuditComponentImpl implements AuditComponent | ||||
|         } | ||||
|  | ||||
|         // Build the key paths using the session root path | ||||
|         Map<String, Serializable> pathedValues = new HashMap<String, Serializable>(values.size() * 2); | ||||
|         Map<String, Serializable> pathedValues = new HashMap<>(values.size() * 2); | ||||
|         for (Map.Entry<String, Serializable> entry : values.entrySet()) | ||||
|         { | ||||
|             String pathElement = entry.getKey(); | ||||
| @@ -596,12 +611,7 @@ public class AuditComponentImpl implements AuditComponent | ||||
|         case TXN_NONE: | ||||
|         case TXN_READ_ONLY: | ||||
|             // New transaction | ||||
|             RetryingTransactionCallback<Map<String, Serializable>> callback = new RetryingTransactionCallback<Map<String, Serializable>>() { | ||||
|                 public Map<String, Serializable> execute() throws Throwable | ||||
|                 { | ||||
|                     return recordAuditValuesImpl(mappedValues); | ||||
|                 } | ||||
|             }; | ||||
|             RetryingTransactionCallback<Map<String, Serializable>> callback = () -> recordAuditValuesImpl(mappedValues); | ||||
|             RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper(); | ||||
|             txnHelper.setForceWritable(true); | ||||
|             return txnHelper.doInTransaction(callback, false, true); | ||||
| @@ -618,21 +628,16 @@ public class AuditComponentImpl implements AuditComponent | ||||
|     public Map<String, Serializable> recordAuditValuesImpl(Map<String, Serializable> mappedValues) | ||||
|     { | ||||
|         // Group the values by root path | ||||
|         Map<String, Map<String, Serializable>> mappedValuesByRootKey = new HashMap<String, Map<String, Serializable>>(); | ||||
|         Map<String, Map<String, Serializable>> mappedValuesByRootKey = new HashMap<>(); | ||||
|         for (Map.Entry<String, Serializable> entry : mappedValues.entrySet()) | ||||
|         { | ||||
|             String path = entry.getKey(); | ||||
|             String rootKey = AuditApplication.getRootKey(path); | ||||
|             Map<String, Serializable> rootKeyMappedValues = mappedValuesByRootKey.get(rootKey); | ||||
|             if (rootKeyMappedValues == null) | ||||
|             { | ||||
|                 rootKeyMappedValues = new HashMap<String, Serializable>(7); | ||||
|                 mappedValuesByRootKey.put(rootKey, rootKeyMappedValues); | ||||
|             } | ||||
|             Map<String, Serializable> rootKeyMappedValues = mappedValuesByRootKey.computeIfAbsent(rootKey, k -> new HashMap<>(7)); | ||||
|             rootKeyMappedValues.put(path, entry.getValue()); | ||||
|         } | ||||
|  | ||||
|         Map<String, Serializable> allAuditedValues = new HashMap<String, Serializable>(mappedValues.size() * 2 + 1); | ||||
|         Map<String, Serializable> allAuditedValues = new HashMap<>(mappedValues.size() * 2 + 1); | ||||
|         // Now audit for each of the root keys | ||||
|         for (Map.Entry<String, Map<String, Serializable>> entry : mappedValuesByRootKey.entrySet()) | ||||
|         { | ||||
| @@ -694,7 +699,7 @@ public class AuditComponentImpl implements AuditComponent | ||||
|         } | ||||
|  | ||||
|         // Check if there is anything to audit | ||||
|         if (values.size() == 0) | ||||
|         if (values.isEmpty()) | ||||
|         { | ||||
|             if (logger.isDebugEnabled()) | ||||
|             { | ||||
| @@ -727,12 +732,7 @@ public class AuditComponentImpl implements AuditComponent | ||||
|         Map<String, Serializable> auditData = generateData(generators); | ||||
|  | ||||
|         // Now extract values | ||||
|         Map<String, Serializable> extractedData = AuthenticationUtil.runAs(new RunAsWork<Map<String, Serializable>>() { | ||||
|             public Map<String, Serializable> doWork() throws Exception | ||||
|             { | ||||
|                 return extractData(application, values); | ||||
|             } | ||||
|         }, AuthenticationUtil.getSystemUserName()); | ||||
|         Map<String, Serializable> extractedData = AuthenticationUtil.runAs(() -> extractData(application, values), AuthenticationUtil.getSystemUserName()); | ||||
|  | ||||
|         // Combine extracted and generated values (extracted data takes precedence) | ||||
|         auditData.putAll(extractedData); | ||||
| @@ -743,8 +743,8 @@ public class AuditComponentImpl implements AuditComponent | ||||
|         { | ||||
|             String root = value.getKey(); | ||||
|             int index = root.lastIndexOf("/"); | ||||
|             Map<String, Serializable> argc = new HashMap<String, Serializable>(1); | ||||
|             argc.put(root.substring(index, root.length()).substring(1), value.getValue()); | ||||
|             Map<String, Serializable> argc = new HashMap<>(1); | ||||
|             argc.put(root.substring(index).substring(1), value.getValue()); | ||||
|             if (!auditFilter.accept(root.substring(0, index), argc)) | ||||
|             { | ||||
|                 return Collections.emptyMap(); | ||||
| @@ -760,10 +760,15 @@ public class AuditComponentImpl implements AuditComponent | ||||
|         { | ||||
|             // Persist the values (if not just gathering data in a pre call for use in a post call) | ||||
|             boolean justGatherPreCallData = application.isApplicationJustGeneratingPreCallData(); | ||||
|             if (!justGatherPreCallData) | ||||
|             if (!justGatherPreCallData && isAuditingToDatabaseEnabled()) | ||||
|             { | ||||
|                 entryId = auditDAO.createAuditEntry(applicationId, time, username, auditData); | ||||
|             } | ||||
|             if (isAuditingToAuditStorageEnabled()) | ||||
|             { | ||||
|                 auditRecordReporter.reportAuditRecord(createAuditRecord(auditData, true, username, entryId, application.getApplicationName())); | ||||
|             } | ||||
|  | ||||
|             // Done | ||||
|             if (logger.isDebugEnabled()) | ||||
|             { | ||||
| @@ -822,7 +827,7 @@ public class AuditComponentImpl implements AuditComponent | ||||
|             AuditApplication application, | ||||
|             Map<String, Serializable> values) | ||||
|     { | ||||
|         Map<String, Serializable> newData = new HashMap<String, Serializable>(values.size()); | ||||
|         Map<String, Serializable> newData = new HashMap<>(values.size()); | ||||
|  | ||||
|         List<DataExtractorDefinition> extractors = application.getDataExtractors(); | ||||
|         for (DataExtractorDefinition extractorDef : extractors) | ||||
| @@ -900,7 +905,7 @@ public class AuditComponentImpl implements AuditComponent | ||||
|      */ | ||||
|     private Map<String, Serializable> generateData(Map<String, DataGenerator> generators) | ||||
|     { | ||||
|         Map<String, Serializable> newData = new HashMap<String, Serializable>(generators.size() + 5); | ||||
|         Map<String, Serializable> newData = new HashMap<>(generators.size() + 5); | ||||
|         for (Map.Entry<String, DataGenerator> entry : generators.entrySet()) | ||||
|         { | ||||
|             String path = entry.getKey(); | ||||
| @@ -925,6 +930,20 @@ public class AuditComponentImpl implements AuditComponent | ||||
|         return newData; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates an AuditRecord from the provided audit data. | ||||
|      */ | ||||
|     private AuditRecord createAuditRecord(Map<String, Serializable> auditData, boolean inTransaction, String username, Long entryId, String applicationName) | ||||
|     { | ||||
|         int rootSize = applicationName.length() + 2; // Root is constructed like this -> '/' + auditedApplicationName + '/'. | ||||
|         AuditRecord.Builder builder = AuditRecordUtils.generateAuditRecordBuilder(auditData, rootSize); | ||||
|         builder.setAuditRecordType(applicationName); | ||||
|         builder.setInTransaction(inTransaction); | ||||
|         builder.setUsername(username); | ||||
|         builder.setEntryDBId(entryId); | ||||
|         return builder.build(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * {@inheritDoc} | ||||
|      */ | ||||
|   | ||||
| @@ -0,0 +1,130 @@ | ||||
| /* | ||||
|  * #%L | ||||
|  * Alfresco Repository | ||||
|  * %% | ||||
|  * Copyright (C) 2025 Alfresco Software Limited | ||||
|  * %% | ||||
|  * This file is part of the Alfresco software. | ||||
|  * If the software was purchased under a paid Alfresco license, the terms of | ||||
|  * the paid license agreement will prevail.  Otherwise, the software is | ||||
|  * provided under the following open source license terms: | ||||
|  * | ||||
|  * Alfresco is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Lesser General Public License as published by | ||||
|  * the Free Software Foundation, either version 3 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * Alfresco is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public License | ||||
|  * along with Alfresco. If not, see <http://www.gnu.org/licenses/>. | ||||
|  * #L% | ||||
|  */ | ||||
| package org.alfresco.repo.audit; | ||||
|  | ||||
| import java.io.Serializable; | ||||
| import java.time.ZonedDateTime; | ||||
| import java.util.Map; | ||||
|  | ||||
| public class AuditRecord | ||||
| { | ||||
|     private final boolean inTransaction; | ||||
|     private final String auditApplicationId; | ||||
|     private final ZonedDateTime createdAt; | ||||
|     private final String username; | ||||
|     private final Long entryDBId; | ||||
|     private final Map<String, Serializable> auditData; | ||||
|  | ||||
|     public AuditRecord(Builder builder) | ||||
|     { | ||||
|         this.auditApplicationId = builder.auditRecordType; | ||||
|         this.inTransaction = builder.inTransaction; | ||||
|         this.auditData = builder.auditRecordData; | ||||
|         this.createdAt = ZonedDateTime.now(); | ||||
|         this.username = builder.username; | ||||
|         this.entryDBId = builder.entryDBId; | ||||
|     } | ||||
|  | ||||
|     public String getAuditApplicationId() | ||||
|     { | ||||
|         return auditApplicationId; | ||||
|     } | ||||
|  | ||||
|     public boolean isInTransaction() | ||||
|     { | ||||
|         return inTransaction; | ||||
|     } | ||||
|  | ||||
|     public ZonedDateTime getCreatedAt() | ||||
|     { | ||||
|         return createdAt; | ||||
|     } | ||||
|  | ||||
|     public String getUsername() | ||||
|     { | ||||
|         return username; | ||||
|     } | ||||
|  | ||||
|     public Long getEntryDBId() | ||||
|     { | ||||
|         return entryDBId; | ||||
|     } | ||||
|  | ||||
|     public Map<String, Serializable> getAuditData() | ||||
|     { | ||||
|         return auditData; | ||||
|     } | ||||
|  | ||||
|     public static Builder builder() | ||||
|     { | ||||
|         return new Builder(); | ||||
|     } | ||||
|  | ||||
|     public static class Builder | ||||
|     { | ||||
|         private String auditRecordType; | ||||
|         private boolean inTransaction; | ||||
|         private Map<String, Serializable> auditRecordData; | ||||
|         private String username; | ||||
|         private Long entryDBId; | ||||
|  | ||||
|         public Builder setAuditRecordType(String auditRecordType) | ||||
|         { | ||||
|             this.auditRecordType = auditRecordType; | ||||
|             return this; | ||||
|         } | ||||
|  | ||||
|         public Builder setInTransaction(boolean inTransaction) | ||||
|         { | ||||
|             this.inTransaction = inTransaction; | ||||
|             return this; | ||||
|         } | ||||
|  | ||||
|         public Builder setAuditRecordData(Map<String, Serializable> auditRecordData) | ||||
|         { | ||||
|             this.auditRecordData = auditRecordData; | ||||
|             return this; | ||||
|         } | ||||
|  | ||||
|         public Builder setUsername(String username) | ||||
|         { | ||||
|             this.username = username; | ||||
|             return this; | ||||
|         } | ||||
|  | ||||
|         public Builder setEntryDBId(Long entryDBId) | ||||
|         { | ||||
|             this.entryDBId = entryDBId; | ||||
|             return this; | ||||
|         } | ||||
|  | ||||
|         public AuditRecord build() | ||||
|         { | ||||
|             return new AuditRecord(this); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,37 @@ | ||||
| /* | ||||
|  * #%L | ||||
|  * Alfresco Repository | ||||
|  * %% | ||||
|  * Copyright (C) 2025 Alfresco Software Limited | ||||
|  * %% | ||||
|  * This file is part of the Alfresco software. | ||||
|  * If the software was purchased under a paid Alfresco license, the terms of | ||||
|  * the paid license agreement will prevail.  Otherwise, the software is | ||||
|  * provided under the following open source license terms: | ||||
|  * | ||||
|  * Alfresco is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Lesser General Public License as published by | ||||
|  * the Free Software Foundation, either version 3 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * Alfresco is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public License | ||||
|  * along with Alfresco. If not, see <http://www.gnu.org/licenses/>. | ||||
|  * #L% | ||||
|  */ | ||||
| package org.alfresco.repo.audit; | ||||
|  | ||||
| public interface AuditRecordReporter | ||||
| { | ||||
|     /** | ||||
|      * This method will report AuditRecord to Audit Storage using RepoEvent2 | ||||
|      *  | ||||
|      * @param auditRecord | ||||
|      *            represent data that will be reported. | ||||
|      */ | ||||
|     void reportAuditRecord(AuditRecord auditRecord); | ||||
| } | ||||
| @@ -0,0 +1,40 @@ | ||||
| /* | ||||
|  * #%L | ||||
|  * Alfresco Repository | ||||
|  * %% | ||||
|  * Copyright (C) 2025 Alfresco Software Limited | ||||
|  * %% | ||||
|  * This file is part of the Alfresco software. | ||||
|  * If the software was purchased under a paid Alfresco license, the terms of | ||||
|  * the paid license agreement will prevail.  Otherwise, the software is | ||||
|  * provided under the following open source license terms: | ||||
|  * | ||||
|  * Alfresco is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Lesser General Public License as published by | ||||
|  * the Free Software Foundation, either version 3 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * Alfresco is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public License | ||||
|  * along with Alfresco. If not, see <http://www.gnu.org/licenses/>. | ||||
|  * #L% | ||||
|  */ | ||||
| package org.alfresco.repo.audit; | ||||
|  | ||||
| public class AuditRecordReporterImpl implements AuditRecordReporter | ||||
| { | ||||
|     /** | ||||
|      * This method intentionally has an empty implementation. | ||||
|      * <p> | ||||
|      * This class provides a no-op implementation of {@link AuditRecordReporter}. | ||||
|      */ | ||||
|     @Override | ||||
|     public void reportAuditRecord(AuditRecord auditRecord) | ||||
|     { | ||||
|         // No operation performed. | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,104 @@ | ||||
| /* | ||||
|  * #%L | ||||
|  * Alfresco Repository | ||||
|  * %% | ||||
|  * Copyright (C) 2025 Alfresco Software Limited | ||||
|  * %% | ||||
|  * This file is part of the Alfresco software. | ||||
|  * If the software was purchased under a paid Alfresco license, the terms of | ||||
|  * the paid license agreement will prevail.  Otherwise, the software is | ||||
|  * provided under the following open source license terms: | ||||
|  * | ||||
|  * Alfresco is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Lesser General Public License as published by | ||||
|  * the Free Software Foundation, either version 3 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * Alfresco is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public License | ||||
|  * along with Alfresco. If not, see <http://www.gnu.org/licenses/>. | ||||
|  * #L% | ||||
|  */ | ||||
| package org.alfresco.repo.audit; | ||||
|  | ||||
| import java.io.Serializable; | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
|  | ||||
| import org.alfresco.service.cmr.repository.NodeRef; | ||||
|  | ||||
| public final class AuditRecordUtils | ||||
| { | ||||
|     private AuditRecordUtils() | ||||
|     { | ||||
|         // This is a utility class and cannot be instantiated. | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Generates an {@link AuditRecord.Builder} from flat audit data. | ||||
|      * <p> | ||||
|      * This method: | ||||
|      * <ul> | ||||
|      * <li>Translates flat {@code key-value} pairs into a nested JSON structure.</li> | ||||
|      * <li>Preloads the builder with the provided arguments.</li> | ||||
|      * <li>Splits keys by {@code /} to build the nested structure.</li> | ||||
|      * <li>Uses the root key as the application ID.</li> | ||||
|      * <li>Assumes each key starts with the same root, constructed as {@code '/' + auditedApplicationName + '/'}, which is removed before splitting.</li> | ||||
|      * </ul> | ||||
|      * | ||||
|      * @param data | ||||
|      *            a map containing flat audit data as `key-value` pairs | ||||
|      * @param keyRootLength | ||||
|      *            is a length of key root. | ||||
|      * @return a preloaded {@link AuditRecord.Builder} | ||||
|      */ | ||||
|     public static AuditRecord.Builder generateAuditRecordBuilder(Map<String, Serializable> data, int keyRootLength) | ||||
|     { | ||||
|         var auditRecordBuilder = AuditRecord.builder(); | ||||
|  | ||||
|         var rootNode = createRootNode(data, keyRootLength); | ||||
|  | ||||
|         auditRecordBuilder.setAuditRecordData(rootNode); | ||||
|  | ||||
|         return auditRecordBuilder; | ||||
|     } | ||||
|  | ||||
|     @SuppressWarnings("unchecked") | ||||
|     private static HashMap<String, Serializable> createRootNode(Map<String, Serializable> data, int keyRootLength) | ||||
|     { | ||||
|         var rootNode = new HashMap<String, Serializable>(); | ||||
|  | ||||
|         data.forEach((k, v) -> { | ||||
|             var keys = k.substring(keyRootLength).split("/"); | ||||
|  | ||||
|             var current = rootNode; | ||||
|             for (int i = 0; i < keys.length - 1; i++) | ||||
|             { | ||||
|                 current = (HashMap<String, Serializable>) current.computeIfAbsent(keys[i], newMap -> new HashMap<String, Serializable>()); | ||||
|             } | ||||
|             current.put(keys[keys.length - 1], decodeValueByInstance(v)); | ||||
|         }); | ||||
|         return rootNode; | ||||
|     } | ||||
|  | ||||
|     @SuppressWarnings("unchecked") | ||||
|     private static Serializable decodeValueByInstance(Serializable value) | ||||
|     { | ||||
|         if (value instanceof HashMap<?, ?>) | ||||
|         { | ||||
|             return createRootNode((HashMap<String, Serializable>) value, 0); | ||||
|         } | ||||
|         else if (value instanceof NodeRef) | ||||
|         { | ||||
|             return ((NodeRef) value).getId(); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             return value; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -58,6 +58,21 @@ public interface AuditModelRegistry | ||||
|      */ | ||||
|     public boolean isAuditEnabled(); | ||||
|  | ||||
|     /** | ||||
|      * Determines whether audit values should be stored in database. <code>True</code> by default if not changed by property. | ||||
|      * | ||||
|      * @return <code>true</code> if audit is enabled. | ||||
|      */ | ||||
|     boolean isAuditingToDatabaseEnabled(); | ||||
|  | ||||
|     /** | ||||
|      * Determines whether audit values should be stored in audit storage. | ||||
|      *  | ||||
|      * @return <code>true</code> if auditing to Audit Storage is enabled. | ||||
|      * | ||||
|      */ | ||||
|     boolean isAuditingToAuditStorageEnabled(); | ||||
|  | ||||
|     /** | ||||
|      * Get a map of all audit applications key by name | ||||
|      *  | ||||
|   | ||||
| @@ -85,6 +85,9 @@ public class AuditModelRegistryImpl extends AbstractPropertyBackedBean implement | ||||
| { | ||||
|     /** The name of the global enablement property. */ | ||||
|     public static final String PROPERTY_AUDIT_ENABLED = "audit.enabled"; | ||||
|  | ||||
|     private static final String AUDITING_TO_DATABASE = ".auditingToDatabase"; | ||||
|     private static final String AUDITING_TO_AUDIT_STORAGE = ".auditingToAuditStorage"; | ||||
|     /** The name of the strict loading flag. */ | ||||
|     public static final String PROPERTY_AUDIT_CONFIG_STRICT = "audit.config.strict"; | ||||
|     /** The XSD classpath location. */ | ||||
| @@ -249,6 +252,26 @@ public class AuditModelRegistryImpl extends AbstractPropertyBackedBean implement | ||||
|         return value != null && value.equalsIgnoreCase("true"); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * {@inheritDoc} | ||||
|      */ | ||||
|     @Override | ||||
|     public boolean isAuditingToDatabaseEnabled() | ||||
|     { | ||||
|         String value = getProperty(AUDIT_PROPERTY_AUDIT_ENABLED + AUDITING_TO_DATABASE); | ||||
|         return value == null || value.equalsIgnoreCase("true"); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * {@inheritDoc} | ||||
|      */ | ||||
|     @Override | ||||
|     public boolean isAuditingToAuditStorageEnabled() | ||||
|     { | ||||
|         String value = getProperty(AUDIT_PROPERTY_AUDIT_ENABLED + AUDITING_TO_AUDIT_STORAGE); | ||||
|         return value != null && value.equalsIgnoreCase("true"); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Enables audit and registers an audit model at a given URL. Does not register across the cluster and should only be used for unit test purposes. | ||||
|      *  | ||||
| @@ -296,6 +319,8 @@ public class AuditModelRegistryImpl extends AbstractPropertyBackedBean implement | ||||
|  | ||||
|             // Default value for global enabled property | ||||
|             properties.put(AUDIT_PROPERTY_AUDIT_ENABLED, false); | ||||
|             properties.put(AUDIT_PROPERTY_AUDIT_ENABLED + AUDITING_TO_DATABASE, true); | ||||
|             properties.put(AUDIT_PROPERTY_AUDIT_ENABLED + AUDITING_TO_AUDIT_STORAGE, false); | ||||
|  | ||||
|             // Let's search for config files in the appropriate places. The individual applications they contain can still | ||||
|             // be enabled/disabled by the bean properties | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
|  * #%L | ||||
|  * Alfresco Repository | ||||
|  * %% | ||||
|  * Copyright (C) 2005 - 2021 Alfresco Software Limited | ||||
|  * Copyright (C) 2005 - 2025 Alfresco Software Limited | ||||
|  * %% | ||||
|  * This file is part of the Alfresco software.  | ||||
|  * If the software was purchased under a paid Alfresco license, the terms of  | ||||
| @@ -102,6 +102,7 @@ import org.alfresco.service.namespace.QName; | ||||
|  * @author Jesper Steen Møller | ||||
|  * @author Derek Hulley | ||||
|  */ | ||||
| @SuppressWarnings("PMD.CyclomaticComplexity") | ||||
| @AlfrescoPublicApi | ||||
| abstract public class AbstractMappingMetadataExtracter implements MetadataExtracter, MetadataEmbedder, BeanNameAware, ApplicationContextAware | ||||
| { | ||||
| @@ -1118,6 +1119,15 @@ abstract public class AbstractMappingMetadataExtracter implements MetadataExtrac | ||||
|         return extract(nodeRef, reader, overwritePolicy, destination, mapping); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * {@inheritDoc} | ||||
|      */ | ||||
|     @Override | ||||
|     public Map<QName, Serializable> extract(NodeRef nodeRef, ContentReader reader, OverwritePolicy overwritePolicy, Map<QName, Serializable> destination) | ||||
|     { | ||||
|         return extract(nodeRef, reader, overwritePolicy, destination, mapping); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * {@inheritDoc} | ||||
|      */ | ||||
| @@ -1154,7 +1164,7 @@ abstract public class AbstractMappingMetadataExtracter implements MetadataExtrac | ||||
|             // Check that the content has some meat | ||||
|             if (reader.getSize() > 0 && reader.exists()) | ||||
|             { | ||||
|                 rawMetadata = extractRaw(nodeRef, reader, getLimits(reader.getMimetype())); | ||||
|                 rawMetadata = extractRaw(nodeRef, reader, getLimits(reader.getMimetype()), overwritePolicy); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
| @@ -2002,7 +2012,7 @@ abstract public class AbstractMappingMetadataExtracter implements MetadataExtrac | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Exception wrapper to handle exceeded limits imposed by {@link MetadataExtracterLimits} {@link AbstractMappingMetadataExtracter#extractRaw(NodeRef, ContentReader, MetadataExtracterLimits)} | ||||
|      * Exception wrapper to handle exceeded limits imposed by {@link MetadataExtracterLimits} {@link AbstractMappingMetadataExtracter#extractRaw(NodeRef, ContentReader, MetadataExtracterLimits,OverwritePolicy)} | ||||
|      */ | ||||
|     private class LimitExceededException extends Exception | ||||
|     { | ||||
| @@ -2032,7 +2042,7 @@ abstract public class AbstractMappingMetadataExtracter implements MetadataExtrac | ||||
|      *             All exception conditions can be handled. | ||||
|      */ | ||||
|     private Map<String, Serializable> extractRaw(NodeRef nodeRef, | ||||
|             ContentReader reader, MetadataExtracterLimits limits) throws Throwable | ||||
|             ContentReader reader, MetadataExtracterLimits limits, OverwritePolicy overwritePolicy) throws Throwable | ||||
|     { | ||||
|         if (reader.getSize() > limits.getMaxDocumentSizeMB() * MEGABYTE_SIZE) | ||||
|         { | ||||
| @@ -2059,6 +2069,12 @@ abstract public class AbstractMappingMetadataExtracter implements MetadataExtrac | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return extractRawInThread(nodeRef, reader, limits, overwritePolicy); | ||||
|     } | ||||
|  | ||||
|     protected Map<String, Serializable> extractRawInThread(NodeRef nodeRef, ContentReader reader, MetadataExtracterLimits limits, OverwritePolicy policy) | ||||
|             throws Throwable | ||||
|     { | ||||
|         return extractRawInThread(nodeRef, reader, limits); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -93,6 +93,9 @@ public class AsynchronousExtractor extends AbstractMappingMetadataExtracter | ||||
|     private static final String METADATA = "metadata"; | ||||
|     private static final Map<String, Serializable> EMPTY_METADATA = Collections.emptyMap(); | ||||
|  | ||||
|     private static final OverwritePolicy DEFAULT_OVERWRITE_POLICY = OverwritePolicy.PRAGMATIC; | ||||
|     private OverwritePolicy extractOverwritePolicy = DEFAULT_OVERWRITE_POLICY; | ||||
|  | ||||
|     private final ObjectMapper jsonObjectMapper = new ObjectMapper(); | ||||
|  | ||||
|     private NodeService nodeService; | ||||
| @@ -260,9 +263,9 @@ public class AsynchronousExtractor extends AbstractMappingMetadataExtracter | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected Map<String, Serializable> extractRawInThread(NodeRef nodeRef, ContentReader reader, MetadataExtracterLimits limits) | ||||
|             throws Throwable | ||||
|     protected Map<String, Serializable> extractRawInThread(NodeRef nodeRef, ContentReader reader, MetadataExtracterLimits limits, OverwritePolicy overwritePolicy) throws Throwable | ||||
|     { | ||||
|         this.extractOverwritePolicy = overwritePolicy != null ? overwritePolicy : DEFAULT_OVERWRITE_POLICY; | ||||
|         Map<String, String> options = getExtractOptions(nodeRef, reader, limits); | ||||
|         transformInBackground(nodeRef, reader, MIMETYPE_METADATA_EXTRACT, EXTRACT, options); | ||||
|         return EMPTY_METADATA; | ||||
| @@ -461,7 +464,7 @@ public class AsynchronousExtractor extends AbstractMappingMetadataExtracter | ||||
|         } | ||||
|  | ||||
|         // Remove well know entries from the map that drive how the real metadata is applied. | ||||
|         OverwritePolicy overwritePolicy = removeOverwritePolicy(metadata, "sys:overwritePolicy", OverwritePolicy.PRAGMATIC); | ||||
|         OverwritePolicy overwritePolicy = removeOverwritePolicy(metadata, "sys:overwritePolicy", extractOverwritePolicy); | ||||
|         Boolean enableStringTagging = removeBoolean(metadata, "sys:enableStringTagging", false); | ||||
|         Boolean carryAspectProperties = removeBoolean(metadata, "sys:carryAspectProperties", true); | ||||
|         List<String> stringTaggingSeparators = removeTaggingSeparators(metadata, "sys:stringTaggingSeparators", | ||||
|   | ||||
| @@ -404,6 +404,24 @@ public interface MetadataExtracter extends ContentWorker | ||||
|         return extract(reader, destination); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Identical to {@link #extract(ContentReader, OverwritePolicy ,Map)} but with the addition of the {@code NodeRef} being acted on. By default, the method without the {@code NodeRef} is called. | ||||
|      * | ||||
|      * @param nodeRef | ||||
|      *            the node being acted on. | ||||
|      * @param reader | ||||
|      *            the source of the content | ||||
|      * @param destination | ||||
|      *            the map of properties to populate (essentially a return value) | ||||
|      * @return Returns a map of all properties on the destination map that were added or modified. If the return map is empty, then no properties were modified. | ||||
|      * @throws ContentIOException | ||||
|      *             if a detectable error occurs | ||||
|      */ | ||||
|     default Map<QName, Serializable> extract(NodeRef nodeRef, ContentReader reader, OverwritePolicy overwritePolicy, Map<QName, Serializable> destination) | ||||
|     { | ||||
|         return extract(reader, overwritePolicy, destination); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Identical to {@link #extract(ContentReader, OverwritePolicy, Map, Map)} but with the addition of the {@code NodeRef} being acted on. By default, the method without the {@code NodeRef} is called. | ||||
|      * | ||||
|   | ||||
| @@ -2066,7 +2066,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO | ||||
|  | ||||
|         Node node = getNodeNotNull(nodeId, false); | ||||
|         // Handle sys:referenceable | ||||
|         ReferenceablePropertiesEntity.addReferenceableProperties(node, props); | ||||
|         ReferenceablePropertiesEntity.addReferenceableProperties(node.getId(), node.getNodeRef(), props); | ||||
|         // Handle sys:localized | ||||
|         LocalizedPropertiesEntity.addLocalizedProperties(localeDAO, node, props); | ||||
|         // Handle cm:auditable | ||||
|   | ||||
| @@ -86,10 +86,8 @@ public class ReferenceablePropertiesEntity | ||||
|     /** | ||||
|      * Adds all {@link ContentModel#ASPECT_REFERENCEABLE referencable} properties. | ||||
|      */ | ||||
|     public static void addReferenceableProperties(Node node, Map<QName, Serializable> properties) | ||||
|     public static void addReferenceableProperties(Long nodeId, NodeRef nodeRef, Map<QName, Serializable> properties) | ||||
|     { | ||||
|         Long nodeId = node.getId(); | ||||
|         NodeRef nodeRef = node.getNodeRef(); | ||||
|         properties.put(ContentModel.PROP_STORE_PROTOCOL, nodeRef.getStoreRef().getProtocol()); | ||||
|         properties.put(ContentModel.PROP_STORE_IDENTIFIER, nodeRef.getStoreRef().getIdentifier()); | ||||
|         properties.put(ContentModel.PROP_NODE_UUID, nodeRef.getId()); | ||||
|   | ||||
| @@ -116,7 +116,7 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl | ||||
|     private static final String SELECT_NODE_MAX_ID = "alfresco.node.select_NodeMaxId"; | ||||
|     private static final String SELECT_NODE_INTERVAL_BY_TYPE = "alfresco.node.select_MinMaxNodeIdForNodeType"; | ||||
|     private static final String SELECT_NODES_WITH_ASPECT_IDS = "alfresco.node.select_NodesWithAspectIds"; | ||||
|     private static final String SELECT_NODES_WITH_ASPECT_IDS_LIMITED = "alfresco.node.select_NodesWithAspectIds_Limited"; | ||||
|     private static final String SELECT_NODES_WITH_ASPECT_IDS_LIMITED = "alfresco.node.select.select_NodesWithAspectIds_Limited"; | ||||
|     private static final String INSERT_NODE_ASSOC = "alfresco.node.insert.insert_NodeAssoc"; | ||||
|     private static final String UPDATE_NODE_ASSOC = "alfresco.node.update_NodeAssoc"; | ||||
|     private static final String DELETE_NODE_ASSOC = "alfresco.node.delete_NodeAssoc"; | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
|  * #%L | ||||
|  * Alfresco Repository | ||||
|  * %% | ||||
|  * Copyright (C) 2005 - 2023 Alfresco Software Limited | ||||
|  * Copyright (C) 2005 - 2025 Alfresco Software Limited | ||||
|  * %% | ||||
|  * This file is part of the Alfresco software. | ||||
|  * If the software was purchased under a paid Alfresco license, the terms of | ||||
| @@ -97,19 +97,26 @@ public abstract class EventConsolidator<REF extends EntityRef, RES extends Resou | ||||
|      * @return the {@link RepoEvent} instance | ||||
|      */ | ||||
|     public RepoEvent<DataAttributes<RES>> getRepoEvent(EventInfo eventInfo) | ||||
|     { | ||||
|         final RepoEvent.Builder<DataAttributes<RES>> builder = RepoEvent.builder(); | ||||
|  | ||||
|         configureRepoEventBuilder(builder, eventInfo); | ||||
|  | ||||
|         return builder.build(); | ||||
|     } | ||||
|  | ||||
|     protected void configureRepoEventBuilder(RepoEvent.Builder<DataAttributes<RES>> builder, EventInfo eventInfo) | ||||
|     { | ||||
|         EventType eventType = getDerivedEvent(); | ||||
|  | ||||
|         DataAttributes<RES> eventData = buildEventData(eventInfo, resource, eventType); | ||||
|  | ||||
|         return RepoEvent.<DataAttributes<RES>> builder() | ||||
|                 .setId(eventInfo.getId()) | ||||
|         builder.setId(eventInfo.getId()) | ||||
|                 .setSource(eventInfo.getSource()) | ||||
|                 .setTime(eventInfo.getTimestamp()) | ||||
|                 .setType(eventType.getType()) | ||||
|                 .setData(eventData) | ||||
|                 .setDataschema(EventJSONSchema.getSchemaV1(eventType)) | ||||
|                 .build(); | ||||
|                 .setDataschema(EventJSONSchema.getSchemaV1(eventType)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
|  * #%L | ||||
|  * Alfresco Repository | ||||
|  * %% | ||||
|  * Copyright (C) 2005 - 2020 Alfresco Software Limited | ||||
|  * Copyright (C) 2005 - 2025 Alfresco Software Limited | ||||
|  * %% | ||||
|  * This file is part of the Alfresco software. | ||||
|  * If the software was purchased under a paid Alfresco license, the terms of | ||||
| @@ -38,7 +38,7 @@ import org.alfresco.util.Pair; | ||||
|  */ | ||||
| public enum EventJSONSchema | ||||
| { | ||||
|     NODE_CREATED_V1("nodeCreated", 1, EventType.NODE_CREATED), NODE_UPDATED_V1("nodeUpdated", 1, EventType.NODE_UPDATED), NODE_DELETED_V1("nodeDeleted", 1, EventType.NODE_DELETED), CHILD_ASSOC_CREATED_V1("childAssocCreated", 1, EventType.CHILD_ASSOC_CREATED), CHILD_ASSOC_DELETED_V1("childAssocDeleted", 1, EventType.CHILD_ASSOC_DELETED), PEER_ASSOC_CREATED_V1("peerAssocCreated", 1, EventType.PEER_ASSOC_CREATED), PEER_ASSOC_DELETED_V1("peerAssocDeleted", 1, EventType.PEER_ASSOC_DELETED), PERMISSION_UPDATED_V1("permissionUpdated", 1, EventType.PERMISSION_UPDATED); | ||||
|     NODE_CREATED_V1("nodeCreated", 1, EventType.NODE_CREATED), NODE_UPDATED_V1("nodeUpdated", 1, EventType.NODE_UPDATED), NODE_DELETED_V1("nodeDeleted", 1, EventType.NODE_DELETED), CHILD_ASSOC_CREATED_V1("childAssocCreated", 1, EventType.CHILD_ASSOC_CREATED), CHILD_ASSOC_DELETED_V1("childAssocDeleted", 1, EventType.CHILD_ASSOC_DELETED), PEER_ASSOC_CREATED_V1("peerAssocCreated", 1, EventType.PEER_ASSOC_CREATED), PEER_ASSOC_DELETED_V1("peerAssocDeleted", 1, EventType.PEER_ASSOC_DELETED), PERMISSION_UPDATED_V1("permissionUpdated", 1, EventType.PERMISSION_UPDATED), AUDIT_ENTRY_CREATED_V1("auditEntryCreated", 1, EventType.AUDIT_ENTRY_CREATED); | ||||
|  | ||||
|     private static final String PREFIX = "https://api.alfresco.com/schema/event/repo/v"; | ||||
|  | ||||
|   | ||||
| @@ -4,21 +4,21 @@ | ||||
|  * %% | ||||
|  * Copyright (C) 2005 - 2016 Alfresco Software Limited | ||||
|  * %% | ||||
|  * This file is part of the Alfresco software.  | ||||
|  * If the software was purchased under a paid Alfresco license, the terms of  | ||||
|  * the paid license agreement will prevail.  Otherwise, the software is  | ||||
|  * This file is part of the Alfresco software. | ||||
|  * If the software was purchased under a paid Alfresco license, the terms of | ||||
|  * the paid license agreement will prevail.  Otherwise, the software is | ||||
|  * provided under the following open source license terms: | ||||
|  *  | ||||
|  * | ||||
|  * Alfresco is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Lesser General Public License as published by | ||||
|  * the Free Software Foundation, either version 3 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  *  | ||||
|  * | ||||
|  * Alfresco is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Lesser General Public License for more details. | ||||
|  *  | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public License | ||||
|  * along with Alfresco. If not, see <http://www.gnu.org/licenses/>. | ||||
|  * #L% | ||||
| @@ -34,7 +34,7 @@ import org.alfresco.service.cmr.action.ActionService; | ||||
|  | ||||
| /** | ||||
|  * Scripted Action service for describing and executing actions against Nodes. | ||||
|  *  | ||||
|  * | ||||
|  * @author davidc | ||||
|  */ | ||||
| public final class Actions extends BaseScopableProcessorExtension | ||||
| @@ -44,7 +44,7 @@ public final class Actions extends BaseScopableProcessorExtension | ||||
|  | ||||
|     /** | ||||
|      * Set the service registry | ||||
|      *  | ||||
|      * | ||||
|      * @param serviceRegistry | ||||
|      *            the service registry | ||||
|      */ | ||||
| @@ -55,7 +55,7 @@ public final class Actions extends BaseScopableProcessorExtension | ||||
|  | ||||
|     /** | ||||
|      * Gets the list of registered action names | ||||
|      *  | ||||
|      * | ||||
|      * @return the registered action names | ||||
|      */ | ||||
|     public String[] getRegistered() | ||||
| @@ -73,7 +73,7 @@ public final class Actions extends BaseScopableProcessorExtension | ||||
|  | ||||
|     /** | ||||
|      * Create an Action | ||||
|      *  | ||||
|      * | ||||
|      * @param actionName | ||||
|      *            the action name | ||||
|      * @return the action | ||||
|   | ||||
| @@ -0,0 +1,171 @@ | ||||
| /* | ||||
|  * #%L | ||||
|  * Alfresco Repository | ||||
|  * %% | ||||
|  * Copyright (C) 2005 - 2020 Alfresco Software Limited | ||||
|  * %% | ||||
|  * This file is part of the Alfresco software. | ||||
|  * If the software was purchased under a paid Alfresco license, the terms of | ||||
|  * the paid license agreement will prevail.  Otherwise, the software is | ||||
|  * provided under the following open source license terms: | ||||
|  * | ||||
|  * Alfresco is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Lesser General Public License as published by | ||||
|  * the Free Software Foundation, either version 3 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * Alfresco is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public License | ||||
|  * along with Alfresco. If not, see <http://www.gnu.org/licenses/>. | ||||
|  * #L% | ||||
|  */ | ||||
| /* | ||||
|  * Copyright (C) 2005 - 2025 Alfresco Software Limited | ||||
|  * | ||||
|  * This file is part of Alfresco | ||||
|  * | ||||
|  * Alfresco is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Lesser General Public License as published by | ||||
|  * the Free Software Foundation, either version 3 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * Alfresco is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public License | ||||
|  * along with Alfresco. If not, see <http://www.gnu.org/licenses/>. | ||||
|  */ | ||||
| package org.alfresco.repo.jscript; | ||||
|  | ||||
| import org.apache.commons.lang3.Strings; | ||||
| import org.apache.commons.logging.Log; | ||||
| import org.apache.commons.logging.LogFactory; | ||||
|  | ||||
| import org.alfresco.model.ContentModel; | ||||
| import org.alfresco.repo.action.evaluator.CompareContentConditionEvaluator; | ||||
| import org.alfresco.repo.forms.FormData; | ||||
| import org.alfresco.service.ServiceRegistry; | ||||
| import org.alfresco.service.cmr.action.Action; | ||||
| import org.alfresco.service.cmr.action.ActionCondition; | ||||
| import org.alfresco.service.cmr.action.ActionDefinition; | ||||
| import org.alfresco.service.cmr.action.ActionService; | ||||
| import org.alfresco.service.cmr.repository.ContentReader; | ||||
| import org.alfresco.service.cmr.repository.ContentService; | ||||
| import org.alfresco.service.cmr.repository.NodeRef; | ||||
|  | ||||
| /** | ||||
|  * JavaScript wrapper for the "extract-metadata" action. | ||||
|  * <p> | ||||
|  * This class provides a scriptable interface to trigger metadata extraction actions within the Alfresco repository.</br> | ||||
|  * It is similar to {@link Actions} class but is dedicated to metadata extraction functionality. | ||||
|  * | ||||
|  * </br> | ||||
|  * | ||||
|  * @author Sayan Bhattacharya | ||||
|  */ | ||||
| public final class MetaDataExtractAction extends BaseScopableProcessorExtension | ||||
| { | ||||
|     private static final Log LOG = LogFactory.getLog(MetaDataExtractAction.class); | ||||
|  | ||||
|     private final static String ACTION_NAME = "extract-metadata"; | ||||
|  | ||||
|     private ContentService contentService; | ||||
|  | ||||
|     private ServiceRegistry services; | ||||
|  | ||||
|     /** | ||||
|      * Set the service registry | ||||
|      * | ||||
|      * @param serviceRegistry | ||||
|      *            the service registry | ||||
|      */ | ||||
|     public void setServiceRegistry(ServiceRegistry serviceRegistry) | ||||
|     { | ||||
|         this.services = serviceRegistry; | ||||
|     } | ||||
|  | ||||
|     public void setContentService(ContentService contentService) | ||||
|     { | ||||
|         this.contentService = contentService; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Create a new metadata extraction action instance | ||||
|      * | ||||
|      * @param setActionContext | ||||
|      *            if true, sets the action context to "scriptaction". | ||||
|      * @return the newly created action | ||||
|      */ | ||||
|  | ||||
|     public ScriptAction create(boolean isContentChanged) | ||||
|     { | ||||
|         ScriptAction scriptAction = null; | ||||
|         ActionService actionService = services.getActionService(); | ||||
|         ActionDefinition actionDef = actionService.getActionDefinition(ACTION_NAME); | ||||
|         if (actionDef != null) | ||||
|         { | ||||
|             Action action = actionService.createAction(ACTION_NAME); | ||||
|  | ||||
|             ActionCondition actionCondition = actionService.createActionCondition(CompareContentConditionEvaluator.NAME); | ||||
|             actionCondition.setParameterValue(CompareContentConditionEvaluator.PARAM_IS_CONTENT_CHANGED, isContentChanged); | ||||
|             action.addActionCondition(actionCondition); | ||||
|  | ||||
|             scriptAction = new ScriptAction(this.services, action, actionDef); | ||||
|             scriptAction.setScope(getScope()); | ||||
|         } | ||||
|         return scriptAction; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Check if the content has been updated in the form data compared to the existing content of the node. | ||||
|      * | ||||
|      * @param itemId | ||||
|      * @param formData | ||||
|      * @return true if content has changed, false otherwise | ||||
|      */ | ||||
|     public boolean isContentChanged(String itemId, FormData formData) | ||||
|     { | ||||
|  | ||||
|         try | ||||
|         { | ||||
|             NodeRef nodeRef = NodeRef.isNodeRef(itemId) ? new NodeRef(itemId) : parseNodeRef(itemId); | ||||
|             if (nodeRef == null) | ||||
|             { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             ContentReader reader = contentService.getReader(nodeRef, ContentModel.PROP_CONTENT); | ||||
|             String contentString = reader.getContentString(); | ||||
|             FormData.FieldData fieldData = formData.getFieldData("prop_cm_content"); | ||||
|  | ||||
|             if (fieldData == null || fieldData.getValue() == null) | ||||
|             { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             String propCmContent = String.valueOf(fieldData.getValue()); | ||||
|             return !Strings.CS.equals(contentString, propCmContent); | ||||
|         } | ||||
|         catch (Exception e) | ||||
|         { | ||||
|             if (LOG.isDebugEnabled()) | ||||
|             { | ||||
|                 LOG.debug("Unable to determine if content has changed for node: " + itemId, e); | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private NodeRef parseNodeRef(String itemId) | ||||
|     { | ||||
|         String[] parts = itemId.split("/"); | ||||
|         return (parts.length == 3) ? new NodeRef(parts[0], parts[1], parts[2]) : null; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -2,7 +2,7 @@ | ||||
|  * #%L | ||||
|  * Alfresco Repository | ||||
|  * %% | ||||
|  * Copyright (C) 2005 - 2016 Alfresco Software Limited | ||||
|  * Copyright (C) 2005 - 2025 Alfresco Software Limited | ||||
|  * %% | ||||
|  * This file is part of the Alfresco software.  | ||||
|  * If the software was purchased under a paid Alfresco license, the terms of  | ||||
| @@ -124,6 +124,9 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess | ||||
|     /** Number of (bytecode) instructions that will trigger the observer */ | ||||
|     private int observerInstructionCount = 100; | ||||
|  | ||||
|     /** Flag to enable or disable scope cleaning at the end of each script execution */ | ||||
|     private boolean cleanScope = true; | ||||
|  | ||||
|     /** Custom context factory */ | ||||
|     public static AlfrescoContextFactory contextFactory; | ||||
|  | ||||
| @@ -210,6 +213,15 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess | ||||
|         this.observerInstructionCount = observerInstructionCount; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param cleanScope | ||||
|      *            true to enable scope cleaning at the end of each script execution - set to false to disable this feature. | ||||
|      */ | ||||
|     public void setCleanScope(boolean cleanScope) | ||||
|     { | ||||
|         this.cleanScope = cleanScope; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @see org.alfresco.service.cmr.repository.ScriptProcessor#reset() | ||||
|      */ | ||||
| @@ -619,7 +631,7 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             if (!secure) | ||||
|             if (!secure && cleanScope) | ||||
|             { | ||||
|                 unsetScope(model, scope); | ||||
|             } | ||||
|   | ||||
| @@ -28,8 +28,10 @@ package org.alfresco.repo.node.getchildren; | ||||
| import java.util.List; | ||||
| import java.util.Set; | ||||
|  | ||||
| import org.alfresco.repo.domain.node.NodeEntity; | ||||
| import org.alfresco.repo.domain.node.AuditablePropertiesEntity; | ||||
| import org.alfresco.repo.domain.node.NodePropertyEntity; | ||||
| import org.alfresco.service.cmr.repository.NodeRef; | ||||
| import org.alfresco.service.cmr.repository.StoreRef; | ||||
|  | ||||
| /** | ||||
|  * Filterable/Sortable Node Entity | ||||
| @@ -42,12 +44,17 @@ import org.alfresco.repo.domain.node.NodePropertyEntity; | ||||
| public class FilterSortNodeEntity | ||||
| { | ||||
|     private Long id; // node id | ||||
|     private String nodeUuid; | ||||
|     private Long typeQNameId; | ||||
|  | ||||
|     private NodeEntity node; | ||||
|     private AuditablePropertiesEntity auditablePropertiesEntity; | ||||
|     private NodePropertyEntity prop1; | ||||
|     private NodePropertyEntity prop2; | ||||
|     private NodePropertyEntity prop3; | ||||
|  | ||||
|     private String storeProtocol; | ||||
|     private String storeIdentifier; | ||||
|  | ||||
|     // Supplemental query-related parameters | ||||
|     private Long parentNodeId; | ||||
|     private Long prop1qnameId; | ||||
| @@ -80,6 +87,26 @@ public class FilterSortNodeEntity | ||||
|         this.id = id; | ||||
|     } | ||||
|  | ||||
|     public String getNodeUuid() | ||||
|     { | ||||
|         return nodeUuid; | ||||
|     } | ||||
|  | ||||
|     public void setNodeUuid(String nodeUuid) | ||||
|     { | ||||
|         this.nodeUuid = nodeUuid; | ||||
|     } | ||||
|  | ||||
|     public Long getTypeQNameId() | ||||
|     { | ||||
|         return typeQNameId; | ||||
|     } | ||||
|  | ||||
|     public void setTypeQNameId(Long typeQNameId) | ||||
|     { | ||||
|         this.typeQNameId = typeQNameId; | ||||
|     } | ||||
|  | ||||
|     public String getPattern() | ||||
|     { | ||||
|         return pattern; | ||||
| @@ -136,6 +163,16 @@ public class FilterSortNodeEntity | ||||
|         this.namePropertyQNameId = namePropertyQNameId; | ||||
|     } | ||||
|  | ||||
|     public AuditablePropertiesEntity getAuditablePropertiesEntity() | ||||
|     { | ||||
|         return auditablePropertiesEntity; | ||||
|     } | ||||
|  | ||||
|     public void setAuditablePropertiesEntity(AuditablePropertiesEntity auditablePropertiesEntity) | ||||
|     { | ||||
|         this.auditablePropertiesEntity = auditablePropertiesEntity; | ||||
|     } | ||||
|  | ||||
|     public NodePropertyEntity getProp1() | ||||
|     { | ||||
|         return prop1; | ||||
| @@ -166,14 +203,24 @@ public class FilterSortNodeEntity | ||||
|         this.prop3 = prop3; | ||||
|     } | ||||
|  | ||||
|     public NodeEntity getNode() | ||||
|     public String getStoreProtocol() | ||||
|     { | ||||
|         return node; | ||||
|         return storeProtocol; | ||||
|     } | ||||
|  | ||||
|     public void setNode(NodeEntity childNode) | ||||
|     public void setStoreProtocol(String storeProtocol) | ||||
|     { | ||||
|         this.node = childNode; | ||||
|         this.storeProtocol = storeProtocol; | ||||
|     } | ||||
|  | ||||
|     public String getStoreIdentifier() | ||||
|     { | ||||
|         return storeIdentifier; | ||||
|     } | ||||
|  | ||||
|     public void setStoreIdentifier(String storeIdentifier) | ||||
|     { | ||||
|         this.storeIdentifier = storeIdentifier; | ||||
|     } | ||||
|  | ||||
|     // Supplemental query-related parameters | ||||
| @@ -257,4 +304,9 @@ public class FilterSortNodeEntity | ||||
|     { | ||||
|         this.isPrimary = isPrimary; | ||||
|     } | ||||
|  | ||||
|     public NodeRef createNodeRef() | ||||
|     { | ||||
|         return new NodeRef(new StoreRef(storeProtocol, storeIdentifier), nodeUuid); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -48,7 +48,6 @@ import org.alfresco.query.CannedQueryParameters; | ||||
| import org.alfresco.query.CannedQuerySortDetails; | ||||
| import org.alfresco.query.CannedQuerySortDetails.SortOrder; | ||||
| import org.alfresco.repo.domain.node.AuditablePropertiesEntity; | ||||
| import org.alfresco.repo.domain.node.Node; | ||||
| import org.alfresco.repo.domain.node.NodeDAO; | ||||
| import org.alfresco.repo.domain.node.NodeEntity; | ||||
| import org.alfresco.repo.domain.node.NodePropertyEntity; | ||||
| @@ -775,7 +774,8 @@ public class GetChildrenCannedQuery extends AbstractCannedQueryPermissions<NodeR | ||||
|             if (results.size() >= BATCH_SIZE) | ||||
|             { | ||||
|                 // batch | ||||
|                 preloadFilterSort(); | ||||
|                 preloadNodes(); | ||||
|                 filterSort(); | ||||
|             } | ||||
|  | ||||
|             results.add(result); | ||||
| @@ -788,24 +788,27 @@ public class GetChildrenCannedQuery extends AbstractCannedQueryPermissions<NodeR | ||||
|             if (results.size() >= 0) | ||||
|             { | ||||
|                 // finish batch | ||||
|                 preloadFilterSort(); | ||||
|                 preloadNodes(); | ||||
|                 filterSort(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private void preloadFilterSort() | ||||
|         private void preloadNodes() | ||||
|         { | ||||
|             List<NodeRef> nodeRefs = new ArrayList<>(results.size()); | ||||
|             for (FilterSortNodeEntity result : results) | ||||
|             { | ||||
|                 nodeRefs.add(result.getNode().getNodeRef()); | ||||
|                 nodeRefs.add(result.createNodeRef()); | ||||
|             } | ||||
|  | ||||
|             preload(nodeRefs); | ||||
|         } | ||||
|  | ||||
|         private void filterSort() | ||||
|         { | ||||
|             for (FilterSortNodeEntity result : results) | ||||
|             { | ||||
|                 Node node = result.getNode(); | ||||
|                 NodeRef nodeRef = node.getNodeRef(); | ||||
|                 NodeRef nodeRef = result.createNodeRef(); | ||||
|  | ||||
|                 Map<NodePropertyKey, NodePropertyValue> propertyValues = new HashMap<NodePropertyKey, NodePropertyValue>(3); | ||||
|  | ||||
| @@ -830,7 +833,7 @@ public class GetChildrenCannedQuery extends AbstractCannedQueryPermissions<NodeR | ||||
|                 Map<QName, Serializable> propVals = nodePropertyHelper.convertToPublicProperties(propertyValues); | ||||
|  | ||||
|                 // Add referenceable / spoofed properties (including spoofed name if null) | ||||
|                 ReferenceablePropertiesEntity.addReferenceableProperties(node, propVals); | ||||
|                 ReferenceablePropertiesEntity.addReferenceableProperties(result.getId(), nodeRef, propVals); | ||||
|  | ||||
|                 // special cases | ||||
|  | ||||
| @@ -852,7 +855,7 @@ public class GetChildrenCannedQuery extends AbstractCannedQueryPermissions<NodeR | ||||
|                 } | ||||
|  | ||||
|                 // Auditable props (eg. cm:creator, cm:created, cm:modifier, cm:modified, ...) | ||||
|                 AuditablePropertiesEntity auditableProps = node.getAuditableProperties(); | ||||
|                 AuditablePropertiesEntity auditableProps = result.getAuditablePropertiesEntity(); | ||||
|                 if (auditableProps != null) | ||||
|                 { | ||||
|                     for (Map.Entry<QName, Serializable> entry : auditableProps.getAuditableProperties().entrySet()) | ||||
| @@ -862,7 +865,7 @@ public class GetChildrenCannedQuery extends AbstractCannedQueryPermissions<NodeR | ||||
|                 } | ||||
|  | ||||
|                 // Node type | ||||
|                 Long nodeTypeQNameId = node.getTypeQNameId(); | ||||
|                 Long nodeTypeQNameId = result.getTypeQNameId(); | ||||
|                 if (nodeTypeQNameId != null) | ||||
|                 { | ||||
|                     Pair<Long, QName> pair = qnameDAO.getQName(nodeTypeQNameId); | ||||
|   | ||||
| @@ -81,11 +81,19 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea | ||||
|  | ||||
|     public static final QName DEFAULT_RENDITION_CONTENT_PROP = ContentModel.PROP_CONTENT; | ||||
|     public static final String DEFAULT_MIMETYPE = MimetypeMap.MIMETYPE_TEXT_PLAIN; | ||||
|     public static final String MIMETYPE_METADATA_EXTRACT = "alfresco-metadata-extract"; | ||||
|     public static final String MIMETYPE_METADATA_EMBED = "alfresco-metadata-embed"; | ||||
|     public static final String DEFAULT_ENCODING = "UTF-8"; | ||||
|  | ||||
|     public static final int SOURCE_HAS_NO_CONTENT = -1; | ||||
|     public static final int RENDITION2_DOES_NOT_EXIST = -2; | ||||
|  | ||||
|     // Allowed mimetypes to support text or metadata extract transforms when thumbnails are disabled. | ||||
|     private static final Set<String> ALLOWED_MIMETYPES = Set.of( | ||||
|             MimetypeMap.MIMETYPE_TEXT_PLAIN, | ||||
|             MIMETYPE_METADATA_EXTRACT, | ||||
|             MIMETYPE_METADATA_EMBED); | ||||
|  | ||||
|     private static Log logger = LogFactory.getLog(RenditionService2Impl.class); | ||||
|  | ||||
|     // As Async transforms and renditions are so similar, this class provides a way to provide the code that is different. | ||||
| @@ -288,7 +296,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             if (!isEnabled()) | ||||
|             if (!isAsyncAllowed(renderOrTransform)) | ||||
|             { | ||||
|                 throw new RenditionService2Exception("Async transforms and renditions are disabled " + | ||||
|                         "(system.thumbnail.generate=false or renditionService2.enabled=false)."); | ||||
| @@ -967,4 +975,24 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Checks if the given transform callback is a text extract transform for content indexing or metadata extract/embed. | ||||
|     private boolean isTextOrMetadataExtractTransform(RenderOrTransformCallBack renderOrTransform) | ||||
|     { | ||||
|         RenditionDefinition2 renditionDefinition = renderOrTransform.getRenditionDefinition(); | ||||
|         return renditionDefinition != null && ALLOWED_MIMETYPES.contains(renditionDefinition.getTargetMimetype()); | ||||
|     } | ||||
|  | ||||
|     private boolean isAsyncAllowed(RenderOrTransformCallBack renderOrTransform) | ||||
|     { | ||||
|         // If enabled is false, all async transforms/renditions must be blocked | ||||
|         if (!enabled) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         // If thumbnails are disabled, allow only text extract or metadata extract/embed transforms | ||||
|         return thumbnailsEnabled || isTextOrMetadataExtractTransform(renderOrTransform); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -74,7 +74,7 @@ import com.nimbusds.oauth2.sdk.id.Identifier; | ||||
| import com.nimbusds.oauth2.sdk.id.Issuer; | ||||
| import com.nimbusds.openid.connect.sdk.claims.PersonClaims; | ||||
| import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata; | ||||
| import org.apache.commons.lang.StringUtils; | ||||
| import org.apache.commons.lang3.StringUtils; | ||||
| import org.apache.commons.logging.Log; | ||||
| import org.apache.commons.logging.LogFactory; | ||||
| import org.apache.hc.client5.http.classic.HttpClient; | ||||
|   | ||||
| @@ -42,7 +42,7 @@ import jakarta.servlet.http.HttpServletResponse; | ||||
| import com.nimbusds.oauth2.sdk.Scope; | ||||
| import com.nimbusds.oauth2.sdk.id.Identifier; | ||||
| import com.nimbusds.oauth2.sdk.id.State; | ||||
| import org.apache.commons.lang.StringUtils; | ||||
| import org.apache.commons.lang3.StringUtils; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.security.oauth2.client.registration.ClientRegistration; | ||||
|   | ||||
| @@ -237,6 +237,12 @@ | ||||
|     <bean id="no-condition" class="org.alfresco.repo.action.evaluator.NoConditionEvaluator" parent="action-condition-evaluator"> | ||||
|     </bean> | ||||
|  | ||||
|     <bean id="compare-content" class="org.alfresco.repo.action.evaluator.CompareContentConditionEvaluator" parent="action-condition-evaluator"> | ||||
|         <property name="publicCondition"> | ||||
|             <value>false</value> | ||||
|         </property> | ||||
|     </bean> | ||||
|  | ||||
|     <bean id="compare-property-value" class="org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator" parent="action-condition-evaluator"> | ||||
|         <property name="nodeService"> | ||||
|             <ref bean="nodeService" /> | ||||
|   | ||||
| @@ -37,8 +37,9 @@ | ||||
|                 <property name="properties" ref="global-properties" /> | ||||
|             </bean> | ||||
|         </property> | ||||
|         <property name="auditRecordReporter" ref="auditRecordReporter"/> | ||||
|     </bean> | ||||
|      | ||||
|  | ||||
|     <!-- User Audit Filter --> | ||||
|      | ||||
|     <bean id="userAuditFilter" class="org.alfresco.repo.audit.UserAuditFilter"> | ||||
| @@ -108,5 +109,8 @@ | ||||
|     | ||||
|     <!-- Reference in the audit registry managed bean --> | ||||
|     <alias name="Audit" alias="auditModel.modelRegistry"/> | ||||
|        | ||||
| </beans> | ||||
|  | ||||
|     <!-- Audit Record Reported --> | ||||
|     <bean id="auditRecordReporter" class="org.alfresco.repo.audit.AuditRecordReporterImpl"/> | ||||
|  | ||||
| </beans> | ||||
|   | ||||
| @@ -133,7 +133,15 @@ | ||||
|     <resultMap id="result_FilterSortNode" type="FilterSortNode"> | ||||
|          | ||||
|         <id property="id" column="id" jdbcType="BIGINT" javaType="java.lang.Long"/> | ||||
|          | ||||
|         <result property="nodeUuid" column="uuid" jdbcType="VARCHAR" javaType="java.lang.String"/> | ||||
|         <result property="typeQNameId" column="type_qname_id" jdbcType="BIGINT" javaType="java.lang.Long"/> | ||||
|  | ||||
|         <result property="auditablePropertiesEntity.auditCreator" column="audit_creator" jdbcType="VARCHAR" javaType="java.lang.String"/> | ||||
|         <result property="auditablePropertiesEntity.auditCreated" column="audit_created" jdbcType="VARCHAR" javaType="java.lang.String"/> | ||||
|         <result property="auditablePropertiesEntity.auditModifier" column="audit_modifier" jdbcType="VARCHAR" javaType="java.lang.String"/> | ||||
|         <result property="auditablePropertiesEntity.auditModified" column="audit_modified" jdbcType="VARCHAR" javaType="java.lang.String"/> | ||||
|         <result property="auditablePropertiesEntity.auditAccessed" column="audit_accessed" jdbcType="VARCHAR" javaType="java.lang.String"/> | ||||
|  | ||||
|         <result property="prop1.nodeId" column="prop1_node_id" jdbcType="BIGINT" javaType="java.lang.Long"/> | ||||
|         <result property="prop1.key.qnameId" column="prop1_qname_id" jdbcType="BIGINT" javaType="java.lang.Long"/> | ||||
|         <result property="prop1.key.localeId" column="prop1_locale_id" jdbcType="BIGINT" javaType="java.lang.Long"/> | ||||
| @@ -169,9 +177,9 @@ | ||||
|         <result property="prop3.value.floatValue" column="prop3_float_value" jdbcType="FLOAT" javaType="java.lang.Float"/> | ||||
|         <result property="prop3.value.doubleValue" column="prop3_double_value" jdbcType="FLOAT" javaType="java.lang.Double"/> | ||||
|         <result property="prop3.value.stringValue" column="prop3_string_value" jdbcType="VARCHAR" javaType="java.lang.String"/> | ||||
|          | ||||
|         <association property="node" resultMap="alfresco.node.result_Node"/> | ||||
|          | ||||
|  | ||||
|         <result property="storeProtocol" column="protocol" jdbcType="VARCHAR" javaType="java.lang.String"/> | ||||
|         <result property="storeIdentifier" column="identifier" jdbcType="VARCHAR" javaType="java.lang.String"/> | ||||
|     </resultMap> | ||||
|      | ||||
|     <resultMap id="result_ArchivedNodes" type="ArchivedNodes"> | ||||
| @@ -782,25 +790,6 @@ | ||||
|         <if test="ordered == true">order by node.id ASC</if> | ||||
|     </select> | ||||
|  | ||||
|     <select id="select_NodesWithAspectIds_Limited" parameterType="Ids" resultMap="result_NodeRef" > | ||||
|         select | ||||
|             node.id             as id, | ||||
|             store.protocol      as protocol, | ||||
|             store.identifier    as identifier, | ||||
|             node.uuid           as uuid | ||||
|         from | ||||
|             alf_node_aspects na | ||||
|             join alf_node node on (na.node_id = node.id) | ||||
|             left join alf_store store on (store.id = node.store_id) | ||||
|         where | ||||
|             <![CDATA[na.node_id >= #{idOne}]]> | ||||
|             <if test="idTwo != null"><![CDATA[and na.node_id < #{idTwo}]]></if> | ||||
|             and na.qname_id in | ||||
|                 <foreach item="item" index="i" collection="ids" open="(" separator="," close=")">#{item}</foreach> | ||||
|         <if test="ordered == true">order by node.id ASC</if> | ||||
|         <if test="maxResults != null"><![CDATA[limit #{maxResults}]]></if> | ||||
|     </select> | ||||
|  | ||||
|     <!-- Common results for result_NodeAssoc --> | ||||
|     <sql id="select_NodeAssoc_Results"> | ||||
|         select | ||||
| @@ -991,8 +980,8 @@ | ||||
|     </select> | ||||
|  | ||||
|     <!-- GetChildren - with explicit prop filtering and/or sorting --> | ||||
|     <select id="select_GetChildrenCannedQueryWithProps" parameterType="FilterSortNode" resultMap="result_FilterSortNode"> | ||||
|        select | ||||
|     <select id="select_GetChildrenCannedQueryWithProps" parameterType="FilterSortNode" resultMap="result_FilterSortNode" flushCache="true"> | ||||
|        select distinct | ||||
|             childNode.id             as id, | ||||
|             childNode.version        as version, | ||||
|             childStore.id            as store_id, | ||||
| @@ -1008,7 +997,7 @@ | ||||
|             childNode.audit_created  as audit_created, | ||||
|             childNode.audit_modifier as audit_modifier, | ||||
|             childNode.audit_modified as audit_modified, | ||||
|             childNode.audit_accessed  as audit_accessed | ||||
|             childNode.audit_accessed as audit_accessed | ||||
|             <if test="prop1qnameId != null"> | ||||
|           , prop1.node_id            as prop1_node_id, | ||||
|             prop1.qname_id           as prop1_qname_id, | ||||
| @@ -1086,9 +1075,6 @@ | ||||
|                     #{item} | ||||
|                 </foreach> | ||||
|             </if> | ||||
|         <if test="prop1qnameId == null and auditableProps == false"> | ||||
|             <include refid="alfresco.node.select_ChildAssoc_OrderBy"/> | ||||
|         </if> | ||||
|     </select> | ||||
|      | ||||
|     <!-- GetChildren - with no explicit sorting (or prop filtering) - note: still filtered by child type (and optionally primary or secondary) --> | ||||
| @@ -1566,4 +1552,4 @@ | ||||
|         </foreach> | ||||
|     </delete> | ||||
|          | ||||
| </mapper> | ||||
| </mapper> | ||||
|   | ||||
| @@ -30,4 +30,23 @@ | ||||
|       <![CDATA[and commit_time_ms <= #{maxCommitTime}]]> | ||||
|     </select> | ||||
|  | ||||
| </mapper> | ||||
|     <select id="select_NodesWithAspectIds_Limited" parameterType="Ids" resultMap="alfresco.node.result_NodeRef" > | ||||
|         select | ||||
|             node.id             as id, | ||||
|             store.protocol      as protocol, | ||||
|             store.identifier    as identifier, | ||||
|             node.uuid           as uuid | ||||
|         from | ||||
|             alf_node_aspects na | ||||
|             join alf_node node on (na.node_id = node.id) | ||||
|             left join alf_store store on (store.id = node.store_id) | ||||
|         where | ||||
|             <![CDATA[na.node_id >= #{idOne}]]> | ||||
|             <if test="idTwo != null"><![CDATA[and na.node_id < #{idTwo}]]></if> | ||||
|             and na.qname_id in | ||||
|                 <foreach item="item" index="i" collection="ids" open="(" separator="," close=")">#{item}</foreach> | ||||
|         <if test="ordered == true">order by node.id ASC</if> | ||||
|         <if test="maxResults != null"><![CDATA[limit #{maxResults}]]></if> | ||||
|     </select> | ||||
|  | ||||
| </mapper> | ||||
|   | ||||
| @@ -30,4 +30,23 @@ | ||||
|       <![CDATA[and commit_time_ms <= #{maxCommitTime}]]> | ||||
|     </select> | ||||
|  | ||||
| </mapper> | ||||
|     <select id="select_NodesWithAspectIds_Limited" parameterType="Ids" resultMap="alfresco.node.result_NodeRef" > | ||||
|         select | ||||
|             node.id             as id, | ||||
|             store.protocol      as protocol, | ||||
|             store.identifier    as identifier, | ||||
|             node.uuid           as uuid | ||||
|         from | ||||
|             alf_node_aspects na | ||||
|             join alf_node node on (na.node_id = node.id) | ||||
|             left join alf_store store on (store.id = node.store_id) | ||||
|         where | ||||
|             <![CDATA[na.node_id >= #{idOne}]]> | ||||
|             <if test="idTwo != null"><![CDATA[and na.node_id < #{idTwo}]]></if> | ||||
|             and na.qname_id in | ||||
|                 <foreach item="item" index="i" collection="ids" open="(" separator="," close=")">#{item}</foreach> | ||||
|         <if test="ordered == true">order by node.id ASC</if> | ||||
|         <if test="maxResults != null"><![CDATA[limit #{maxResults}]]></if> | ||||
|     </select> | ||||
|  | ||||
| </mapper> | ||||
|   | ||||
| @@ -286,6 +286,8 @@ audit.alfresco-access.enabled=false | ||||
| audit.alfresco-access.sub-actions.enabled=false | ||||
| audit.cmischangelog.enabled=false | ||||
| audit.dod5015.enabled=false | ||||
| audit.enabled.auditingToAuditStorage=false | ||||
| audit.enabled.auditingToDatabase=true | ||||
| # Setting this flag to true will force startup failure when invalid audit configurations are detected | ||||
| audit.config.strict=false | ||||
| # Audit map filter for AccessAuditor - restricts recorded events to user driven events  | ||||
| @@ -1394,6 +1396,9 @@ scripts.execution.maxMemoryUsedInBytes=-1 | ||||
| # Number of instructions that will trigger the observer | ||||
| scripts.execution.observerInstructionCount=5000 | ||||
|  | ||||
| # Flag to control if the scope is cleaned at the end of script execution | ||||
| scripts.execution.clean.scope=true | ||||
|  | ||||
| # Default value being used in POST/size-details endpoint to partition a huge folder into smaller chunks | ||||
| # so that we can compute more efficiently and consolidate all sizes into a single unit. | ||||
| default.async.folder.items=1000 | ||||
|   | ||||
| @@ -60,6 +60,9 @@ | ||||
|         <property name="observerInstructionCount"> | ||||
|             <value>${scripts.execution.observerInstructionCount}</value> | ||||
|         </property> | ||||
|         <property name="cleanScope"> | ||||
|             <value>${scripts.execution.clean.scope}</value> | ||||
|         </property> | ||||
|     </bean> | ||||
|  | ||||
|     <!-- base config implementation that script extension beans extend from - for auto registration | ||||
| @@ -101,6 +104,17 @@ | ||||
|         </property> | ||||
|     </bean> | ||||
|  | ||||
|     <bean id="metadataExtractServiceScript" parent="baseJavaScriptExtension" | ||||
|           class="org.alfresco.repo.jscript.MetaDataExtractAction"> | ||||
|         <property name="extensionName"> | ||||
|             <value>metadataExtractAction</value> | ||||
|         </property> | ||||
|         <property name="contentService" ref="ContentService" /> | ||||
|         <property name="serviceRegistry"> | ||||
|             <ref bean="ServiceRegistry"/> | ||||
|         </property> | ||||
|     </bean> | ||||
|  | ||||
|     <bean id="imapScript" parent="baseJavaScriptExtension" class="org.alfresco.repo.jscript.Imap"> | ||||
|         <property name="extensionName"> | ||||
|             <value>imap</value> | ||||
|   | ||||
| @@ -48,6 +48,7 @@ import org.alfresco.util.testing.category.NonBuildTests; | ||||
|         org.alfresco.repo.audit.UserAuditFilterTest.class, | ||||
|         org.alfresco.repo.audit.AuditMethodInterceptorTest.class, | ||||
|         org.alfresco.repo.audit.access.AccessAuditorTest.class, | ||||
|         org.alfresco.repo.audit.AuditRecordUtilsTest.class, | ||||
|  | ||||
|         // the following test will lock up the DB if run in the applicationContext_01 test suite | ||||
|         org.alfresco.repo.activities.feed.FeedNotifierTest.class, | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
|  * #%L | ||||
|  * Alfresco Repository | ||||
|  * %% | ||||
|  * Copyright (C) 2005 - 2017 Alfresco Software Limited | ||||
|  * Copyright (C) 2005 - 2025 Alfresco Software Limited | ||||
|  * %% | ||||
|  * This file is part of the Alfresco software.  | ||||
|  * If the software was purchased under a paid Alfresco license, the terms of  | ||||
| @@ -66,6 +66,7 @@ import org.alfresco.util.testing.category.NonBuildTests; | ||||
|         org.alfresco.repo.importer.FileImporterTest.class, | ||||
|         org.alfresco.repo.importer.ImporterComponentTest.class, | ||||
|         org.alfresco.repo.jscript.PeopleTest.class, | ||||
|         org.alfresco.repo.jscript.MetaDataExtractActionTest.class, | ||||
|         org.alfresco.repo.jscript.RhinoScriptTest.class, | ||||
|  | ||||
|         // needs a clean DB to run | ||||
|   | ||||
| @@ -28,6 +28,7 @@ package org.alfresco.repo.action.executer; | ||||
| import static org.awaitility.Awaitility.await; | ||||
|  | ||||
| import java.io.Serializable; | ||||
| import java.time.Duration; | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
| import java.util.Objects; | ||||
| @@ -46,6 +47,8 @@ import org.alfresco.repo.content.MimetypeMap; | ||||
| import org.alfresco.repo.content.metadata.AbstractMappingMetadataExtracter; | ||||
| import org.alfresco.repo.content.metadata.MetadataExtracterRegistry; | ||||
| import org.alfresco.repo.content.transform.AbstractContentTransformerTest; | ||||
| import org.alfresco.repo.jscript.MetaDataExtractAction; | ||||
| import org.alfresco.repo.jscript.ScriptAction; | ||||
| import org.alfresco.repo.security.authentication.AuthenticationComponent; | ||||
| import org.alfresco.repo.transaction.RetryingTransactionHelper; | ||||
| import org.alfresco.service.cmr.repository.ContentReader; | ||||
| @@ -74,6 +77,10 @@ public class ContentMetadataExtracterTest extends BaseSpringTest | ||||
|     protected static final String QUICK_DESCRIPTION = "Pangram, fox, dog, Gym class featuring a brown fox and lazy dog"; | ||||
|     protected static final String QUICK_CREATOR = "Nevin Nollop"; | ||||
|  | ||||
|     protected static final String QUICK_UPDATED_TITLE = "The hot dog is eaten by the city fox"; | ||||
|     protected static final String QUICK_UPDATED_DESCRIPTION = "Pangram, fox, dog, Gym class featuring only brown fox"; | ||||
|     protected static final String QUICK_UPDATED_CREATOR = "Friday"; | ||||
|  | ||||
|     private NodeService nodeService; | ||||
|     private ContentService contentService; | ||||
|     private MetadataExtracterRegistry registry; | ||||
| @@ -84,6 +91,8 @@ public class ContentMetadataExtracterTest extends BaseSpringTest | ||||
|  | ||||
|     private ContentMetadataExtracter executer; | ||||
|  | ||||
|     private MetaDataExtractAction extractAction; | ||||
|  | ||||
|     private final static String ID = GUID.generate(); | ||||
|  | ||||
|     @Before | ||||
| @@ -116,6 +125,9 @@ public class ContentMetadataExtracterTest extends BaseSpringTest | ||||
|  | ||||
|         // Get the executer instance | ||||
|         this.executer = (ContentMetadataExtracter) this.applicationContext.getBean("extract-metadata"); | ||||
|  | ||||
|         // get the js script action | ||||
|         this.extractAction = (MetaDataExtractAction) this.applicationContext.getBean("metadataExtractServiceScript"); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -351,4 +363,45 @@ public class ContentMetadataExtracterTest extends BaseSpringTest | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     public void testUsingScriptAction_WhenContentChanged() throws Exception | ||||
|     { | ||||
|  | ||||
|         // update the content | ||||
|         ContentWriter cw = this.contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); | ||||
|         cw.setMimetype(MimetypeMap.MIMETYPE_PDF); | ||||
|         cw.putContent(AbstractContentTransformerTest.loadNamedQuickTestFile("quickupdated.pdf")); | ||||
|  | ||||
|         // Make the nodeRef visible to other transactions as it will need to be in async requests | ||||
|         TestTransaction.flagForCommit(); | ||||
|         TestTransaction.end(); | ||||
|  | ||||
|         // Execute the action | ||||
|         transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>() { | ||||
|             public Void execute() throws Throwable | ||||
|             { | ||||
|                 ScriptAction action = extractAction.create(true); | ||||
|                 action.execute(nodeRef, false, false); | ||||
|                 return null; | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         // Need to wait for the async extract | ||||
|         await().pollInSameThread() | ||||
|                 .atMost(Duration.ofSeconds(100)) | ||||
|                 .until(() -> nodeService.getProperty(nodeRef, ContentModel.PROP_DESCRIPTION), Objects::nonNull); | ||||
|  | ||||
|         // Check that the properties have been preserved, but that description has been set | ||||
|         transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>() { | ||||
|             public Void execute() throws Throwable | ||||
|             { | ||||
|                 assertEquals(QUICK_UPDATED_TITLE, nodeService.getProperty(nodeRef, ContentModel.PROP_TITLE)); | ||||
|                 assertEquals(QUICK_UPDATED_CREATOR, nodeService.getProperty(nodeRef, ContentModel.PROP_AUTHOR)); | ||||
|  | ||||
|                 assertEquals(QUICK_UPDATED_DESCRIPTION, nodeService.getProperty(nodeRef, ContentModel.PROP_DESCRIPTION)); | ||||
|                 return null; | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -877,6 +877,7 @@ public class AuditComponentTest extends TestCase | ||||
|         auditModelRegistry.loadAuditModels(); | ||||
|  | ||||
|         auditModelRegistry.setProperty("audit.enabled", "true"); | ||||
|         auditModelRegistry.setProperty("audit.enabled.auditingToDatabase", "true"); | ||||
|  | ||||
|         auditModelRegistry.setProperty("audit.app1.enabled", "true"); | ||||
|         auditModelRegistry.setProperty("audit.filter.app1.default.enabled", "true"); | ||||
|   | ||||
| @@ -0,0 +1,123 @@ | ||||
| /* | ||||
|  * #%L | ||||
|  * Alfresco Repository | ||||
|  * %% | ||||
|  * Copyright (C) 2025 Alfresco Software Limited | ||||
|  * %% | ||||
|  * This file is part of the Alfresco software. | ||||
|  * If the software was purchased under a paid Alfresco license, the terms of | ||||
|  * the paid license agreement will prevail.  Otherwise, the software is | ||||
|  * provided under the following open source license terms: | ||||
|  * | ||||
|  * Alfresco is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Lesser General Public License as published by | ||||
|  * the Free Software Foundation, either version 3 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * Alfresco is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public License | ||||
|  * along with Alfresco. If not, see <http://www.gnu.org/licenses/>. | ||||
|  * #L% | ||||
|  */ | ||||
|  | ||||
| package org.alfresco.repo.audit; | ||||
|  | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertNotNull; | ||||
|  | ||||
| import java.io.Serializable; | ||||
| import java.time.Instant; | ||||
| import java.util.Date; | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
|  | ||||
| import org.junit.Test; | ||||
|  | ||||
| import org.alfresco.service.cmr.repository.NodeRef; | ||||
| import org.alfresco.service.namespace.QName; | ||||
|  | ||||
| public class AuditRecordUtilsTest | ||||
| { | ||||
|     @SuppressWarnings("unchecked") | ||||
|     @Test | ||||
|     public void testGenerateAuditRecordBuilderTest() | ||||
|     { | ||||
|         var testData = new HashMap<String, Serializable>(); | ||||
|  | ||||
|         testData.put("/alfresco-access/transaction/path", "/app:company_home"); | ||||
|         testData.put("/alfresco-access/transaction/user", "admin"); | ||||
|         testData.put("/alfresco-access/transaction/sub-actions", "updateNodeProperties"); | ||||
|         var now = Instant.now(); | ||||
|         testData.put("/alfresco-access/transaction/properties/from", (Serializable) Map.of(QName.createQName("modified"), Date.from(now))); | ||||
|         testData.put("/alfresco-access/transaction/properties/to", (Serializable) Map.of(QName.createQName("modified"), Date.from(now))); | ||||
|  | ||||
|         var builder = AuditRecordUtils.generateAuditRecordBuilder(testData, "/alfresco-access/".length()); | ||||
|         builder.setAuditRecordType("alfresco-access"); | ||||
|         var auditRecord = builder.build(); | ||||
|  | ||||
|         assertNotNull(auditRecord); | ||||
|         assertEquals("alfresco-access", auditRecord.getAuditApplicationId()); | ||||
|  | ||||
|         var auditData = auditRecord.getAuditData(); | ||||
|         assertEquals(1, auditData.size()); | ||||
|  | ||||
|         var transaction = (HashMap<String, ?>) auditData.get("transaction"); | ||||
|         assertNotNull(transaction); | ||||
|         assertEquals(4, transaction.size()); | ||||
|         assertEquals(testData.get("/alfresco-access/transaction/path"), transaction.get("path")); | ||||
|         assertEquals(testData.get("/alfresco-access/transaction/user"), transaction.get("user")); | ||||
|         assertEquals(testData.get("/alfresco-access/transaction/sub-actions"), transaction.get("sub-actions")); | ||||
|  | ||||
|         var properties = (HashMap<String, Object>) transaction.get("properties"); | ||||
|         assertNotNull(properties); | ||||
|         assertEquals(2, properties.size()); | ||||
|         assertEquals(testData.get("/alfresco-access/transaction/properties/from"), properties.get("from")); | ||||
|         assertEquals(testData.get("/alfresco-access/transaction/properties/to"), properties.get("to")); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @SuppressWarnings("unchecked") | ||||
|     @Test | ||||
|     public void testGenerateAuditRecordBuilderTestNodeRef() | ||||
|     { | ||||
|         var testData = new HashMap<String, Serializable>(); | ||||
|         var expectedValue = new HashMap<String, Serializable>(); | ||||
|  | ||||
|         expectedValue.put("nodeRef", new NodeRef("workspace://SpacesStore/bfa612e6-1a02-46a0-a612-e61a02e6a036")); | ||||
|         expectedValue.put("objectId", "bfa612e6-1a02-46a0-a612-e61a02e6a036;1.0"); | ||||
|  | ||||
|         testData.put("/CMISChangeLog/CREATED/result/value", expectedValue); | ||||
|  | ||||
|         var builder = AuditRecordUtils.generateAuditRecordBuilder(testData, "/CMISChangeLog/".length()); | ||||
|         builder.setAuditRecordType("CMISChangeLog"); | ||||
|         var auditRecord = builder.build(); | ||||
|  | ||||
|         assertNotNull(auditRecord); | ||||
|  | ||||
|         assertEquals("CMISChangeLog", auditRecord.getAuditApplicationId()); | ||||
|  | ||||
|         var auditData = auditRecord.getAuditData(); | ||||
|         assertEquals(1, auditData.size()); | ||||
|  | ||||
|         var created = (HashMap<String, ?>) auditData.get("CREATED"); | ||||
|         assertNotNull(created); | ||||
|  | ||||
|         assertEquals(1, created.size()); | ||||
|         var result = (HashMap<String, Object>) created.get("result"); | ||||
|         assertNotNull(result); | ||||
|         assertEquals(1, result.size()); | ||||
|  | ||||
|         var resultValue = (HashMap<String, Object>) result.get("value"); | ||||
|         assertNotNull(resultValue); | ||||
|         assertEquals(2, resultValue.size()); | ||||
|  | ||||
|         var expectedNodeRef = (NodeRef) expectedValue.get("nodeRef"); | ||||
|         assertEquals(expectedNodeRef.getId(), resultValue.get("nodeRef")); | ||||
|         assertEquals(expectedValue.get("objectId"), resultValue.get("objectId")); | ||||
|  | ||||
|     } | ||||
| } | ||||
| @@ -50,6 +50,7 @@ public class AuditTestSuite extends TestSuite | ||||
|         suite.addTestSuite(UserAuditFilterTest.class); | ||||
|         suite.addTestSuite(AuditMethodInterceptorTest.class); | ||||
|  | ||||
|         suite.addTest(new JUnit4TestAdapter(AuditRecordUtilsTest.class)); | ||||
|         suite.addTest(new JUnit4TestAdapter(PropertyAuditFilterTest.class)); | ||||
|         suite.addTest(new JUnit4TestAdapter(AccessAuditorTest.class)); | ||||
|  | ||||
|   | ||||
| @@ -0,0 +1,128 @@ | ||||
| /* | ||||
|  * #%L | ||||
|  * Alfresco Repository | ||||
|  * %% | ||||
|  * Copyright (C) 2005 - 2025 Alfresco Software Limited | ||||
|  * %% | ||||
|  * This file is part of the Alfresco software. | ||||
|  * If the software was purchased under a paid Alfresco license, the terms of | ||||
|  * the paid license agreement will prevail.  Otherwise, the software is | ||||
|  * provided under the following open source license terms: | ||||
|  * | ||||
|  * Alfresco is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Lesser General Public License as published by | ||||
|  * the Free Software Foundation, either version 3 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * Alfresco is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public License | ||||
|  * along with Alfresco. If not, see <http://www.gnu.org/licenses/>. | ||||
|  * #L% | ||||
|  */ | ||||
| /* | ||||
|  * Copyright (C) 2005 Jesper Steen Møller | ||||
|  * | ||||
|  * This file is part of Alfresco | ||||
|  * | ||||
|  * Alfresco is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Lesser General Public License as published by | ||||
|  * the Free Software Foundation, either version 3 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * Alfresco is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Lesser General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Lesser General Public License | ||||
|  * along with Alfresco. If not, see <http://www.gnu.org/licenses/>. | ||||
|  */ | ||||
|  | ||||
| package org.alfresco.repo.jscript; | ||||
|  | ||||
| import static org.junit.Assert.*; | ||||
|  | ||||
| import org.junit.Test; | ||||
| import org.mockito.Mockito; | ||||
|  | ||||
| import org.alfresco.repo.forms.FormData; | ||||
| import org.alfresco.service.ServiceRegistry; | ||||
| import org.alfresco.service.cmr.action.Action; | ||||
| import org.alfresco.service.cmr.action.ActionCondition; | ||||
| import org.alfresco.service.cmr.action.ActionDefinition; | ||||
| import org.alfresco.service.cmr.action.ActionService; | ||||
| import org.alfresco.service.cmr.repository.ContentReader; | ||||
| import org.alfresco.service.cmr.repository.ContentService; | ||||
|  | ||||
| public class MetaDataExtractActionTest | ||||
| { | ||||
|  | ||||
|     @Test | ||||
|     public void testIsContentChangedReturnsTrue() | ||||
|     { | ||||
|         MetaDataExtractAction action = new MetaDataExtractAction(); | ||||
|         ContentService contentService = Mockito.mock(ContentService.class); | ||||
|         ContentReader reader = Mockito.mock(ContentReader.class); | ||||
|         FormData formData = Mockito.mock(FormData.class); | ||||
|         FormData.FieldData fieldData = Mockito.mock(FormData.FieldData.class); | ||||
|  | ||||
|         String nodeRefStr = "workspace://SpacesStore/abc/def"; | ||||
|         Mockito.when(contentService.getReader(Mockito.any(), Mockito.any())).thenReturn(reader); | ||||
|         Mockito.when(reader.getContentString()).thenReturn("oldContent"); | ||||
|         Mockito.when(formData.getFieldData("prop_cm_content")).thenReturn(fieldData); | ||||
|         Mockito.when(fieldData.getValue()).thenReturn("newContent"); | ||||
|  | ||||
|         action.setContentService(contentService); | ||||
|  | ||||
|         boolean result = action.isContentChanged(nodeRefStr, formData); | ||||
|         assertTrue(result); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     public void testIsContentChangedReturnsFalse() | ||||
|     { | ||||
|         MetaDataExtractAction action = new MetaDataExtractAction(); | ||||
|         ContentService contentService = Mockito.mock(ContentService.class); | ||||
|         ContentReader reader = Mockito.mock(ContentReader.class); | ||||
|         FormData formData = Mockito.mock(FormData.class); | ||||
|         FormData.FieldData fieldData = Mockito.mock(FormData.FieldData.class); | ||||
|  | ||||
|         String nodeRefStr = "workspace://SpacesStore/abc/def"; | ||||
|         Mockito.when(contentService.getReader(Mockito.any(), Mockito.any())).thenReturn(reader); | ||||
|         Mockito.when(reader.getContentString()).thenReturn("sameContent"); | ||||
|         Mockito.when(formData.getFieldData("prop_cm_content")).thenReturn(fieldData); | ||||
|         Mockito.when(fieldData.getValue()).thenReturn("sameContent"); | ||||
|  | ||||
|         action.setContentService(contentService); | ||||
|  | ||||
|         boolean result = action.isContentChanged(nodeRefStr, formData); | ||||
|         assertFalse(result); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     public void testCreateWhenContentChangedReturnsScriptAction() | ||||
|     { | ||||
|         MetaDataExtractAction action = new MetaDataExtractAction(); | ||||
|  | ||||
|         ServiceRegistry serviceRegistry = Mockito.mock(ServiceRegistry.class); | ||||
|         ActionService actionService = Mockito.mock(ActionService.class); | ||||
|         ActionDefinition actionDefinition = Mockito.mock(ActionDefinition.class); | ||||
|         Action alfrescoAction = Mockito.mock(Action.class); | ||||
|         ActionCondition actionCondition = Mockito.mock(ActionCondition.class); | ||||
|  | ||||
|         Mockito.when(serviceRegistry.getActionService()).thenReturn(actionService); | ||||
|         Mockito.when(actionService.getActionDefinition(Mockito.anyString())).thenReturn(actionDefinition); | ||||
|         Mockito.when(actionService.createAction(Mockito.anyString())).thenReturn(alfrescoAction); | ||||
|         Mockito.when(actionService.createActionCondition(Mockito.anyString())).thenReturn(actionCondition); | ||||
|  | ||||
|         action.setServiceRegistry(serviceRegistry); | ||||
|  | ||||
|         ScriptAction result = action.create(true); | ||||
|  | ||||
|         assertNotNull("ScriptAction should not be null when content has changed", result); | ||||
|     } | ||||
| } | ||||
| @@ -39,6 +39,7 @@ import org.junit.Test; | ||||
|  | ||||
| import org.alfresco.model.ContentModel; | ||||
| import org.alfresco.model.RenditionModel; | ||||
| import org.alfresco.repo.content.MimetypeMap; | ||||
| import org.alfresco.repo.security.authentication.AuthenticationUtil; | ||||
| import org.alfresco.repo.security.permissions.AccessDeniedException; | ||||
| import org.alfresco.service.cmr.repository.ChildAssociationRef; | ||||
| @@ -776,4 +777,58 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     public void testTextExtractTransformAllowedWhenThumbnailDisabled() | ||||
|     { | ||||
|         // create a source node | ||||
|         NodeRef sourceNodeRef = createSource(ADMIN, "quick.pdf"); | ||||
|         assertNotNull("Node not generated", sourceNodeRef); | ||||
|         String replyQueue = "org.test.queue"; | ||||
|         String targetMimetype = MimetypeMap.MIMETYPE_TEXT_PLAIN; | ||||
|  | ||||
|         TransformDefinition textExtractTransform = new TransformDefinition( | ||||
|                 targetMimetype, | ||||
|                 java.util.Collections.emptyMap(), | ||||
|                 "clientData", | ||||
|                 replyQueue, | ||||
|                 "requestId"); | ||||
|  | ||||
|         renditionService2.setThumbnailsEnabled(false); | ||||
|         try | ||||
|         { | ||||
|             // Should NOT throw, as this is a text extract transform | ||||
|             AuthenticationUtil.runAs(() -> { | ||||
|                 transactionService.getRetryingTransactionHelper().doInTransaction(() -> { | ||||
|                     renditionService2.transform(sourceNodeRef, textExtractTransform); | ||||
|                     return null; | ||||
|                 }); | ||||
|                 return null; | ||||
|             }, ADMIN); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             renditionService2.setThumbnailsEnabled(true); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     public void testMetadataExtractTransformAllowedWhenThumbnailDisabled() | ||||
|     { | ||||
|         // create a source node | ||||
|         NodeRef sourceNodeRef = createSource(ADMIN, "quick.pdf"); | ||||
|         assertNotNull("Node not generated", sourceNodeRef); | ||||
|         renditionService2.setThumbnailsEnabled(false); | ||||
|         try | ||||
|         { | ||||
|             // Should NOT throw, as this is a metadata extract transform | ||||
|             extract(ADMIN, sourceNodeRef); | ||||
|             waitForExtract(ADMIN, sourceNodeRef, true); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             renditionService2.setThumbnailsEnabled(true); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								repository/src/test/resources/quick/quickupdated.pdf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								repository/src/test/resources/quick/quickupdated.pdf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
		Reference in New Issue
	
	Block a user