mirror of
				https://github.com/Alfresco/alfresco-community-repo.git
				synced 2025-10-29 15:21:53 +00:00 
			
		
		
		
	Compare commits
	
		
			322 Commits
		
	
	
		
			23.3.0.2
			...
			fix/checki
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					06d500311c | ||
| 
						 | 
					8d95ffc9ff | ||
| 
						 | 
					6e05d5d157 | ||
| 
						 | 
					dfff72849d | ||
| 
						 | 
					d60172e860 | ||
| 
						 | 
					3cac4e8206 | ||
| 
						 | 
					b35a64d14f | ||
| 
						 | 
					10e78191dd | ||
| 
						 | 
					6290c46d62 | ||
| 
						 | 
					8a61badabc | ||
| 
						 | 
					f9946827c4 | ||
| 
						 | 
					b812c7856e | ||
| 
						 | 
					a3f6e13a7c | ||
| 
						 | 
					afffc7e870 | ||
| 
						 | 
					fe5a01e2bd | ||
| 
						 | 
					03625565e9 | ||
| 
						 | 
					8d1d2b4f1b | ||
| 
						 | 
					1342c6a7bb | ||
| 
						 | 
					17152b69fc | ||
| 
						 | 
					1a7027327e | ||
| 
						 | 
					7921969222 | ||
| 
						 | 
					9cc93de7b2 | ||
| 
						 | 
					55c9cf3407 | ||
| 
						 | 
					120f45ba92 | ||
| 
						 | 
					eacdbd3770 | ||
| 
						 | 
					93d4701d80 | ||
| 
						 | 
					64baf03818 | ||
| 
						 | 
					e39606aec5 | ||
| 
						 | 
					7581e07c3c | ||
| 
						 | 
					a01d375e6f | ||
| 
						 | 
					a98f44803a | ||
| 
						 | 
					cabc38b386 | ||
| 
						 | 
					9ed29967b7 | ||
| 
						 | 
					b63a3eae9b | ||
| 
						 | 
					1ce46c2039 | ||
| 
						 | 
					278aa59302 | ||
| 
						 | 
					51a51ecd6b | ||
| 
						 | 
					10f4b10ae8 | ||
| 
						 | 
					24575c436e | ||
| 
						 | 
					25c4b677de | ||
| 
						 | 
					6f13f36c5a | ||
| 
						 | 
					db8b353fb1 | ||
| 
						 | 
					2ccdee122a | ||
| 
						 | 
					14c1b91a9b | ||
| 
						 | 
					5b562edad1 | ||
| 
						 | 
					19e577383a | ||
| 
						 | 
					115997d367 | ||
| 
						 | 
					de2effcefb | ||
| 
						 | 
					79b78448e0 | ||
| 
						 | 
					05c4f2282e | ||
| 
						 | 
					228944c59c | ||
| 
						 | 
					ec279bcd5d | ||
| 
						 | 
					97770fd831 | ||
| 
						 | 
					1a99c54074 | ||
| 
						 | 
					b30b7bd252 | ||
| 
						 | 
					7ce9183360 | ||
| 
						 | 
					06af664a16 | ||
| 
						 | 
					7adf58d35f | ||
| 
						 | 
					08e42ed877 | ||
| 
						 | 
					aa2fb35b41 | ||
| 
						 | 
					ab1e762a65 | ||
| 
						 | 
					c6201fa2fa | ||
| 
						 | 
					0ab31fcc93 | ||
| 
						 | 
					7616781ce9 | ||
| 
						 | 
					a7c83b9acc | ||
| 
						 | 
					a09649c40a | ||
| 
						 | 
					0bb0e5b5b0 | ||
| 
						 | 
					0f1ef341f6 | ||
| 
						 | 
					48e4d8a5a6 | ||
| 
						 | 
					22bd945003 | ||
| 
						 | 
					12b986373e | ||
| 
						 | 
					fbc5b738ab | ||
| 
						 | 
					92c1b2fb9d | ||
| 
						 | 
					ca3a7663a0 | ||
| 
						 | 
					ae88784bdb | ||
| 
						 | 
					4f03115b69 | ||
| 
						 | 
					8fc2da1ce9 | ||
| 
						 | 
					5f0c65b551 | ||
| 
						 | 
					b3f5cf9f76 | ||
| 
						 | 
					cb3934eb96 | ||
| 
						 | 
					df00db7cb7 | ||
| 
						 | 
					af4e0c0a01 | ||
| 
						 | 
					b6a56cb0f8 | ||
| 
						 | 
					aac9be6887 | ||
| 
						 | 
					c831a5e23d | ||
| 
						 | 
					0e9633fa5b | ||
| 
						 | 
					971f8c9b46 | ||
| 
						 | 
					cb89ed65fb | ||
| 
						 | 
					34357a83d8 | ||
| 
						 | 
					b29afa2248 | ||
| 
						 | 
					c89871c4fa | ||
| 
						 | 
					2a07b65e25 | ||
| 
						 | 
					36286e76e9 | ||
| 
						 | 
					267a776620 | ||
| 
						 | 
					5da314f03a | ||
| 
						 | 
					02b43f42ef | ||
| 
						 | 
					1bcde87d42 | ||
| 
						 | 
					c06b210056 | ||
| 
						 | 
					f4a89937ef | ||
| 
						 | 
					fc8c37b1e2 | ||
| 
						 | 
					fcc4bc9ac4 | ||
| 
						 | 
					8ee8be1696 | ||
| 
						 | 
					3aeb049d6b | ||
| 
						 | 
					2311665003 | ||
| 
						 | 
					90fad5d4e8 | ||
| 
						 | 
					dee3674494 | ||
| 
						 | 
					91b91e3dee | ||
| 
						 | 
					c085d42531 | ||
| 
						 | 
					d0323836ad | ||
| 
						 | 
					628d299980 | ||
| 
						 | 
					b532ba9d54 | ||
| 
						 | 
					195f5b1a8c | ||
| 
						 | 
					c8718ca722 | ||
| 
						 | 
					9a48b09f32 | ||
| 
						 | 
					3421bcb532 | ||
| 
						 | 
					e3175e9a98 | ||
| 
						 | 
					f5c71e6098 | ||
| 
						 | 
					725ef34ccb | ||
| 
						 | 
					b7224b3d20 | ||
| 
						 | 
					24c6abedb5 | ||
| 
						 | 
					bf2f123aa7 | ||
| 
						 | 
					0e90fe9710 | ||
| 
						 | 
					638058629d | ||
| 
						 | 
					0e7660be83 | ||
| 
						 | 
					35b4ad5dbe | ||
| 
						 | 
					90b91b87ef | ||
| 
						 | 
					bfce7cf3ad | ||
| 
						 | 
					03a261c4c5 | ||
| 
						 | 
					4b3558ff12 | ||
| 
						 | 
					4355add778 | ||
| 
						 | 
					92fbaf29d4 | ||
| 
						 | 
					8e15dba3eb | ||
| 
						 | 
					2206479e06 | ||
| 
						 | 
					abeaff52e8 | ||
| 
						 | 
					76be8d2bec | ||
| 
						 | 
					ce27304eae | ||
| 
						 | 
					060cf3aa3c | ||
| 
						 | 
					f1fdf72c5b | ||
| 
						 | 
					b580a52459 | ||
| 
						 | 
					d13da0cdfa | ||
| 
						 | 
					ca236d9814 | ||
| 
						 | 
					8398a5dca0 | ||
| 
						 | 
					52731727c6 | ||
| 
						 | 
					2f45231149 | ||
| 
						 | 
					447d0c4f56 | ||
| 
						 | 
					5e920054f4 | ||
| 
						 | 
					2f66f068a6 | ||
| 
						 | 
					2461b75f61 | ||
| 
						 | 
					96231517bd | ||
| 
						 | 
					b73b8df892 | ||
| 
						 | 
					08e49c98dd | ||
| 
						 | 
					ba50617181 | ||
| 
						 | 
					0d621badd7 | ||
| 
						 | 
					661ddce806 | ||
| 
						 | 
					aa8b0256e1 | ||
| 
						 | 
					8990581cc1 | ||
| 
						 | 
					04d76a182a | ||
| 
						 | 
					27fbf633df | ||
| 
						 | 
					358ca8dc9e | ||
| 
						 | 
					9a25862cc9 | ||
| 
						 | 
					ac7e58ddc5 | ||
| 
						 | 
					6bcaa1be04 | ||
| 
						 | 
					a1bf0f5480 | ||
| 
						 | 
					2c3d5dd297 | ||
| 
						 | 
					6b32d96f08 | ||
| 
						 | 
					69dd388133 | ||
| 
						 | 
					29106d307f | ||
| 
						 | 
					ef8b3578df | ||
| 
						 | 
					dc5128b447 | ||
| 
						 | 
					741ac97c3b | ||
| 
						 | 
					3f5cb68250 | ||
| 
						 | 
					fdb4f9e338 | ||
| 
						 | 
					f3f3a1db4b | ||
| 
						 | 
					eb922e1c95 | ||
| 
						 | 
					46da34fabd | ||
| 
						 | 
					7dee8314fb | ||
| 
						 | 
					f888f8244e | ||
| 
						 | 
					8e3eeb9dd7 | ||
| 
						 | 
					012d2f37c7 | ||
| 
						 | 
					f5f73162bd | ||
| 
						 | 
					e7a83f2641 | ||
| 
						 | 
					2a300f9b7c | ||
| 
						 | 
					ac0ed124aa | ||
| 
						 | 
					cee2da9700 | ||
| 
						 | 
					2bc914b649 | ||
| 
						 | 
					dcd381923e | ||
| 
						 | 
					bcf5d1b2c0 | ||
| 
						 | 
					89dd6f7b35 | ||
| 
						 | 
					c82e8d652a | ||
| 
						 | 
					a9a911e77f | ||
| 
						 | 
					e59bb3261c | ||
| 
						 | 
					dbcb54369a | ||
| 
						 | 
					b50a022e48 | ||
| 
						 | 
					799db5f212 | ||
| 
						 | 
					975976db18 | ||
| 
						 | 
					809fa28023 | ||
| 
						 | 
					d28359c168 | ||
| 
						 | 
					d999558b33 | ||
| 
						 | 
					897bfa8410 | ||
| 
						 | 
					a3b13ed8de | ||
| 
						 | 
					ea66b9e4f3 | ||
| 
						 | 
					0a5827e37c | ||
| 
						 | 
					c01d1f8c3c | ||
| 
						 | 
					3895ce6584 | ||
| 
						 | 
					d01f9eb27b | ||
| 
						 | 
					325dfd285b | ||
| 
						 | 
					d907461972 | ||
| 
						 | 
					01a2587555 | ||
| 
						 | 
					8b45d19b00 | ||
| 
						 | 
					0b4b1dac44 | ||
| 
						 | 
					f8becd3523 | ||
| 
						 | 
					781b56f06d | ||
| 
						 | 
					92656a68a9 | ||
| 
						 | 
					99838a73e3 | ||
| 
						 | 
					723156a86a | ||
| 
						 | 
					3f6692a63b | ||
| 
						 | 
					32e72f204a | ||
| 
						 | 
					f5868e7f45 | ||
| 
						 | 
					fd015de2c8 | ||
| 
						 | 
					ec2494f2b5 | ||
| 
						 | 
					afc7e73352 | ||
| 
						 | 
					3519fd6a71 | ||
| 
						 | 
					5e43f3d4ab | ||
| 
						 | 
					337d0b8f8f | ||
| 
						 | 
					3c7593265d | ||
| 
						 | 
					5dcecb7f19 | ||
| 
						 | 
					0617a441d3 | ||
| 
						 | 
					d9abdc23d5 | ||
| 
						 | 
					f132ec0a91 | ||
| 
						 | 
					1797781ce8 | ||
| 
						 | 
					88c1c849ed | ||
| 
						 | 
					c4ac7621c2 | ||
| 
						 | 
					bf7432deb1 | ||
| 
						 | 
					6f74667a5c | ||
| 
						 | 
					1e1508b74e | ||
| 
						 | 
					da8ffa08a0 | ||
| 
						 | 
					277dbd1054 | ||
| 
						 | 
					43cfa0d872 | ||
| 
						 | 
					1325ec5718 | ||
| 
						 | 
					ecde2b5b60 | ||
| 
						 | 
					a9f75638c6 | ||
| 
						 | 
					a80b996f7e | ||
| 
						 | 
					05aa6b4cf6 | ||
| 
						 | 
					50b1d3a52e | ||
| 
						 | 
					38b763c635 | ||
| 
						 | 
					762b6ce607 | ||
| 
						 | 
					0d89010ae6 | ||
| 
						 | 
					b744f267c1 | ||
| 
						 | 
					e840726a7d | ||
| 
						 | 
					c53683df85 | ||
| 
						 | 
					d9e58483ff | ||
| 
						 | 
					9e81472d06 | ||
| 
						 | 
					3e9cdc6b77 | ||
| 
						 | 
					e8ac8c2602 | ||
| 
						 | 
					eea58f4ba3 | ||
| 
						 | 
					a95fa4a83d | ||
| 
						 | 
					0b74d283e2 | ||
| 
						 | 
					052e21e62d | ||
| 
						 | 
					26f49e80e9 | ||
| 
						 | 
					c31158a113 | ||
| 
						 | 
					2b4fc52203 | ||
| 
						 | 
					6222e1dfde | ||
| 
						 | 
					077c48dea9 | ||
| 
						 | 
					1a0b8d8dee | ||
| 
						 | 
					66d007e703 | ||
| 
						 | 
					81a4c5bac0 | ||
| 
						 | 
					018157c808 | ||
| 
						 | 
					8db97184f2 | ||
| 
						 | 
					84948e051a | ||
| 
						 | 
					8c20a3271e | ||
| 
						 | 
					48c7abcfbe | ||
| 
						 | 
					53d77f8d71 | ||
| 
						 | 
					e8e747347a | ||
| 
						 | 
					fee244cb08 | ||
| 
						 | 
					2bec2bd1c4 | ||
| 
						 | 
					1cd8098f52 | ||
| 
						 | 
					30afcaa033 | ||
| 
						 | 
					bdf1a57630 | ||
| 
						 | 
					2723817832 | ||
| 
						 | 
					22acb2abe7 | ||
| 
						 | 
					3c616152a1 | ||
| 
						 | 
					8cfdc613cb | ||
| 
						 | 
					f1919934b2 | ||
| 
						 | 
					e9da60dac3 | ||
| 
						 | 
					0597b0997f | ||
| 
						 | 
					6dbe30e8f7 | ||
| 
						 | 
					d978b1cd68 | ||
| 
						 | 
					dce356fe74 | ||
| 
						 | 
					f965165894 | ||
| 
						 | 
					2ac44c24a8 | ||
| 
						 | 
					a0dc5a0d70 | ||
| 
						 | 
					af94063bbb | ||
| 
						 | 
					87b91b6cae | ||
| 
						 | 
					6bdcaa9b10 | ||
| 
						 | 
					86070d881c | ||
| 
						 | 
					a82199967c | ||
| 
						 | 
					f09266c081 | ||
| 
						 | 
					563f65825f | ||
| 
						 | 
					8bedeedfd5 | ||
| 
						 | 
					a73cf6a71d | ||
| 
						 | 
					1bdd6c022c | ||
| 
						 | 
					1b553dbcaf | ||
| 
						 | 
					5a3b4e1a0d | ||
| 
						 | 
					1a0156b1e5 | ||
| 
						 | 
					6ac9248262 | ||
| 
						 | 
					70290c8f23 | ||
| 
						 | 
					7d135b9356 | ||
| 
						 | 
					df4629b801 | ||
| 
						 | 
					df6f656b95 | ||
| 
						 | 
					f27718c43b | ||
| 
						 | 
					cbf0aaaaa4 | ||
| 
						 | 
					2da78a94ad | ||
| 
						 | 
					3325e08d57 | ||
| 
						 | 
					985205e78e | ||
| 
						 | 
					5fd5e75bd2 | ||
| 
						 | 
					279bc15aac | ||
| 
						 | 
					a3283b4521 | ||
| 
						 | 
					e09c9118b2 | ||
| 
						 | 
					330256438b | ||
| 
						 | 
					2214f16e6e | ||
| 
						 | 
					2237a45e76 | ||
| 
						 | 
					440e31fcdb | 
							
								
								
									
										4
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							@@ -52,3 +52,7 @@ updates:
 | 
			
		||||
    interval: "daily"
 | 
			
		||||
    time: "22:00"
 | 
			
		||||
    timezone: Africa/Abidjan
 | 
			
		||||
- package-ecosystem: "github-actions"
 | 
			
		||||
  directory: "/"
 | 
			
		||||
  schedule:
 | 
			
		||||
    interval: "monthly"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										712
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										712
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -29,6 +29,9 @@ env:
 | 
			
		||||
  AUTH0_CLIENT_ID: ${{ secrets.AUTH0_OIDC_ADMIN_CLIENT_ID }}
 | 
			
		||||
  AUTH0_CLIENT_SECRET: ${{ secrets.AUTH0_OIDC_CLIENT_SECRET }}
 | 
			
		||||
  AUTH0_ADMIN_PASSWORD: ${{ secrets.AUTH0_OIDC_ADMIN_PASSWORD }}
 | 
			
		||||
  # Report Portal settings
 | 
			
		||||
  RP_LAUNCH_PREFIX: "${{ github.workflow }} - ${{ github.job }}"
 | 
			
		||||
  RP_PROJECT: alfresco-backend
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  prepare:
 | 
			
		||||
@@ -38,7 +41,7 @@ jobs:
 | 
			
		||||
      !contains(github.event.head_commit.message, '[skip tests]') &&
 | 
			
		||||
      !contains(github.event.head_commit.message, '[force')
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.35.2
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v1.35.2      
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v1.35.2
 | 
			
		||||
@@ -58,7 +61,7 @@ jobs:
 | 
			
		||||
      !contains(github.event.head_commit.message, '[skip tests]') &&
 | 
			
		||||
      !contains(github.event.head_commit.message, '[force')
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.35.2
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v1.35.2      
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v1.35.2
 | 
			
		||||
@@ -81,7 +84,7 @@ jobs:
 | 
			
		||||
      !contains(github.event.head_commit.message, '[skip tests]') &&
 | 
			
		||||
      !contains(github.event.head_commit.message, '[force')
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.35.2
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v1.35.2
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v1.35.2
 | 
			
		||||
@@ -97,7 +100,7 @@ jobs:
 | 
			
		||||
          bash ./scripts/ci/init.sh
 | 
			
		||||
          bash ./scripts/ci/build.sh
 | 
			
		||||
      - name: "Run SAST Scan"
 | 
			
		||||
        uses: veracode/Veracode-pipeline-scan-action@v1.0.10
 | 
			
		||||
        uses: veracode/Veracode-pipeline-scan-action@v1.0.16
 | 
			
		||||
        with:
 | 
			
		||||
          vid: ${{ secrets.VERACODE_API_ID }}
 | 
			
		||||
          vkey: ${{ secrets.VERACODE_API_KEY }}
 | 
			
		||||
@@ -135,29 +138,87 @@ jobs:
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.35.2
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v1.35.2
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v1.35.2
 | 
			
		||||
      - uses: Alfresco/ya-pmd-scan@v3.0.0
 | 
			
		||||
      - uses: Alfresco/ya-pmd-scan@v4.0.0
 | 
			
		||||
        with:
 | 
			
		||||
          classpath-build-command: "mvn test-compile -ntp -Pags -pl \"-:alfresco-community-repo-docker\""
 | 
			
		||||
 | 
			
		||||
  all_unit_tests_suite:
 | 
			
		||||
    name: "Core, Data-Model, Repository - AllUnitTestsSuite - Build and test"
 | 
			
		||||
    name: ${{ matrix.testName }} - AllUnitTestsSuite - Build and test
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs: [prepare]
 | 
			
		||||
    if: >
 | 
			
		||||
      !contains(github.event.head_commit.message, '[skip repo]') &&
 | 
			
		||||
      !contains(github.event.head_commit.message, '[skip tests]') &&
 | 
			
		||||
      !contains(github.event.head_commit.message, '[force')
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
        include:
 | 
			
		||||
          - testName: Core
 | 
			
		||||
            testModule: core
 | 
			
		||||
            testAttributes: "-Dtest=AllCoreUnitTestSuite"
 | 
			
		||||
          - testName: Data-Model
 | 
			
		||||
            testModule: data-model
 | 
			
		||||
            testAttributes: "-Dtest=AllDataModelUnitTestSuite"
 | 
			
		||||
          - testName: Repository
 | 
			
		||||
            testModule: repository
 | 
			
		||||
            testAttributes: "-Dtest=AllUnitTestsSuite"
 | 
			
		||||
          - testName: Mmt
 | 
			
		||||
            testModule: mmt
 | 
			
		||||
            testAttributes: "-Dtest=AllMmtUnitTestSuite"
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.35.2
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v1.35.2      
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v1.35.2
 | 
			
		||||
      - name: "Init"
 | 
			
		||||
        run: bash ./scripts/ci/init.sh
 | 
			
		||||
      - name: "Prepare Report Portal"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v5.1.0
 | 
			
		||||
        id: rp-prepare
 | 
			
		||||
        with:
 | 
			
		||||
          rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} - ${{ matrix.testModule }}
 | 
			
		||||
          rp-token: ${{ secrets.REPORT_PORTAL_TOKEN }}
 | 
			
		||||
          rp-project: ${{ env.RP_PROJECT }}
 | 
			
		||||
          rp-use-static-launch-name: true
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Add GitHub Step Summary"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        env:
 | 
			
		||||
          RP_ENABLED: ${{ steps.rp-prepare.outputs.enabled }}
 | 
			
		||||
          RP_KEY: ${{ steps.rp-prepare.outputs.key }}
 | 
			
		||||
          RP_URL: ${{ steps.rp-prepare.outputs.url }}
 | 
			
		||||
        run: bash scripts/ci/add_step_summary.sh
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Run tests"
 | 
			
		||||
        id: run-tests
 | 
			
		||||
        env:
 | 
			
		||||
          RP_OPTS: ${{ github.ref_name == 'master' && steps.rp-prepare.outputs.mvn-opts || '' }}
 | 
			
		||||
        run: |
 | 
			
		||||
          mvn -B test -pl core,data-model -am -DfailIfNoTests=false
 | 
			
		||||
          mvn -B test -pl "repository,mmt" -am "-Dtest=AllUnitTestsSuite,AllMmtUnitTestSuite" -DfailIfNoTests=false
 | 
			
		||||
          eval "args=($RP_OPTS)"
 | 
			
		||||
          mvn -B test -pl ${{ matrix.testModule }} -am ${{ matrix.testAttributes }} -DfailIfNoTests=false "${args[@]}"
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Update GitHub Step Summary"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "#### ⏱ After Tests: $(date -u +'%Y-%m-%d %H:%M:%S%:z')" >> $GITHUB_STEP_SUMMARY
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Summarize Report Portal"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v5.1.0
 | 
			
		||||
        id: rp-summarize
 | 
			
		||||
        with:
 | 
			
		||||
          tests-outcome: ${{ steps.run-tests.outcome }}
 | 
			
		||||
          rp-launch-key: ${{ steps.rp-prepare.outputs.key }}
 | 
			
		||||
          rp-project: ${{ env.RP_PROJECT }}
 | 
			
		||||
          rp-token: ${{ secrets.REPORT_PORTAL_TOKEN }}
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Exit on failure"
 | 
			
		||||
        if: steps.run-tests.outcome != 'success'
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::error title=run-tests::Tests failed: re-throwing on error."
 | 
			
		||||
          exit 1
 | 
			
		||||
      - name: "Clean Maven cache"
 | 
			
		||||
        run: bash ./scripts/ci/cleanup_cache.sh
 | 
			
		||||
 | 
			
		||||
@@ -186,7 +247,7 @@ jobs:
 | 
			
		||||
    env:
 | 
			
		||||
      REQUIRES_INSTALLED_ARTIFACTS: true
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.35.2
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v1.35.2      
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v1.35.2
 | 
			
		||||
@@ -198,9 +259,53 @@ jobs:
 | 
			
		||||
      - name: "Set transformers tag"
 | 
			
		||||
        run: echo "TRANSFORMERS_TAG=$(mvn help:evaluate -Dexpression=dependency.alfresco-transform-core.version -q -DforceStdout)" >> $GITHUB_ENV
 | 
			
		||||
      - name: "Set up the environment"
 | 
			
		||||
        run: docker-compose -f ./scripts/ci/docker-compose/docker-compose.yaml --profile ${{ matrix.compose-profile }} up -d
 | 
			
		||||
        run: docker compose -f ./scripts/ci/docker-compose/docker-compose.yaml --profile ${{ matrix.compose-profile }} up -d
 | 
			
		||||
      - name: "Prepare Report Portal"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v5.1.0
 | 
			
		||||
        id: rp-prepare
 | 
			
		||||
        with:
 | 
			
		||||
          rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} - ${{ matrix.testSuite }}
 | 
			
		||||
          rp-token: ${{ secrets.REPORT_PORTAL_TOKEN }}
 | 
			
		||||
          rp-project: ${{ env.RP_PROJECT }}
 | 
			
		||||
          rp-use-static-launch-name: true
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Add GitHub Step Summary"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        env:
 | 
			
		||||
          RP_ENABLED: ${{ steps.rp-prepare.outputs.enabled }}
 | 
			
		||||
          RP_KEY: ${{ steps.rp-prepare.outputs.key }}
 | 
			
		||||
          RP_URL: ${{ steps.rp-prepare.outputs.url }}
 | 
			
		||||
        run: bash scripts/ci/add_step_summary.sh
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Run tests"
 | 
			
		||||
        run: mvn -B test -pl remote-api -Dtest=${{ matrix.testSuite }} -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco
 | 
			
		||||
        id: run-tests
 | 
			
		||||
        env:
 | 
			
		||||
          RP_OPTS: ${{ github.ref_name == 'master' && steps.rp-prepare.outputs.mvn-opts || '' }}
 | 
			
		||||
        run: |
 | 
			
		||||
          eval "args=($RP_OPTS)"  
 | 
			
		||||
          mvn -B test -pl remote-api -Dtest=${{ matrix.testSuite }} -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco "${args[@]}"
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Update GitHub Step Summary"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        run: |
 | 
			
		||||
            echo "#### ⏱ After Tests: $(date -u +'%Y-%m-%d %H:%M:%S%:z')" >> $GITHUB_STEP_SUMMARY
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Summarize Report Portal"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v5.1.0
 | 
			
		||||
        id: rp-summarize
 | 
			
		||||
        with:
 | 
			
		||||
          tests-outcome: ${{ steps.run-tests.outcome }}
 | 
			
		||||
          rp-launch-key: ${{ steps.rp-prepare.outputs.key }}
 | 
			
		||||
          rp-project: ${{ env.RP_PROJECT }}
 | 
			
		||||
          rp-token: ${{ secrets.REPORT_PORTAL_TOKEN }}
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Exit on failure"
 | 
			
		||||
        if: steps.run-tests.outcome != 'success'
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::error title=run-tests::Tests failed: re-throwing on error."
 | 
			
		||||
          exit 1
 | 
			
		||||
      - name: "Clean Maven cache"
 | 
			
		||||
        run: bash ./scripts/ci/cleanup_cache.sh
 | 
			
		||||
 | 
			
		||||
@@ -220,18 +325,62 @@ jobs:
 | 
			
		||||
      matrix:
 | 
			
		||||
        version: ['10.2.18', '10.4', '10.5']
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.35.2
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v1.35.2      
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v1.35.2
 | 
			
		||||
      - name: "Init"
 | 
			
		||||
        run: bash ./scripts/ci/init.sh
 | 
			
		||||
      - name: Run MariaDB ${{ matrix.version }} database
 | 
			
		||||
        run: docker-compose -f ./scripts/ci/docker-compose/docker-compose-db.yaml --profile mariadb up -d
 | 
			
		||||
        run: docker compose -f ./scripts/ci/docker-compose/docker-compose-db.yaml --profile mariadb up -d
 | 
			
		||||
        env:
 | 
			
		||||
          MARIADB_VERSION: ${{ matrix.version }}
 | 
			
		||||
      - name: "Prepare Report Portal"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v5.1.0
 | 
			
		||||
        id: rp-prepare
 | 
			
		||||
        with:
 | 
			
		||||
          rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} - ${{ matrix.version }}
 | 
			
		||||
          rp-token: ${{ secrets.REPORT_PORTAL_TOKEN }}
 | 
			
		||||
          rp-project: ${{ env.RP_PROJECT }}
 | 
			
		||||
          rp-use-static-launch-name: true
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Add GitHub Step Summary"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        env:
 | 
			
		||||
          RP_ENABLED: ${{ steps.rp-prepare.outputs.enabled }}
 | 
			
		||||
          RP_KEY: ${{ steps.rp-prepare.outputs.key }}
 | 
			
		||||
          RP_URL: ${{ steps.rp-prepare.outputs.url }}
 | 
			
		||||
        run: bash scripts/ci/add_step_summary.sh
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Run tests"
 | 
			
		||||
        run: mvn -B test -pl repository -am -Dtest=AllDBTestsTestSuite -DfailIfNoTests=false -Ddb.name=alfresco -Ddb.url=jdbc:mariadb://localhost:3307/alfresco?useUnicode=yes\&characterEncoding=UTF-8 -Ddb.username=alfresco -Ddb.password=alfresco -Ddb.driver=org.mariadb.jdbc.Driver
 | 
			
		||||
        id: run-tests
 | 
			
		||||
        env:
 | 
			
		||||
          RP_OPTS: ${{ github.ref_name == 'master' && steps.rp-prepare.outputs.mvn-opts || '' }}
 | 
			
		||||
        run: |
 | 
			
		||||
          eval "args=($RP_OPTS)"
 | 
			
		||||
          mvn -B test -pl repository -am -Dtest=AllDBTestsTestSuite -DfailIfNoTests=false -Ddb.name=alfresco -Ddb.url=jdbc:mariadb://localhost:3307/alfresco?useUnicode=yes\&characterEncoding=UTF-8 -Ddb.username=alfresco -Ddb.password=alfresco -Ddb.driver=org.mariadb.jdbc.Driver "${args[@]}"
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Update GitHub Step Summary"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "#### ⏱ After Tests: $(date -u +'%Y-%m-%d %H:%M:%S%:z')" >> $GITHUB_STEP_SUMMARY
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Summarize Report Portal"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v5.1.0
 | 
			
		||||
        id: rp-summarize
 | 
			
		||||
        with:
 | 
			
		||||
          tests-outcome: ${{ steps.run-tests.outcome }}
 | 
			
		||||
          rp-launch-key: ${{ steps.rp-prepare.outputs.key }}
 | 
			
		||||
          rp-project: ${{ env.RP_PROJECT }}
 | 
			
		||||
          rp-token: ${{ secrets.REPORT_PORTAL_TOKEN }}
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Exit on failure"
 | 
			
		||||
        if: steps.run-tests.outcome != 'success'
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::error title=run-tests::Tests failed: re-throwing on error."
 | 
			
		||||
          exit 1
 | 
			
		||||
      - name: "Clean Maven cache"
 | 
			
		||||
        run: bash ./scripts/ci/cleanup_cache.sh
 | 
			
		||||
 | 
			
		||||
@@ -247,18 +396,62 @@ jobs:
 | 
			
		||||
      !contains(github.event.head_commit.message, '[skip tests]') &&
 | 
			
		||||
      !contains(github.event.head_commit.message, '[force')
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.35.2
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v1.35.2      
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v1.35.2
 | 
			
		||||
      - name: "Init"
 | 
			
		||||
        run: bash ./scripts/ci/init.sh
 | 
			
		||||
      - name: "Run MariaDB 10.6 database"
 | 
			
		||||
        run: docker-compose -f ./scripts/ci/docker-compose/docker-compose-db.yaml --profile mariadb up -d
 | 
			
		||||
        run: docker compose -f ./scripts/ci/docker-compose/docker-compose-db.yaml --profile mariadb up -d
 | 
			
		||||
        env:
 | 
			
		||||
          MARIADB_VERSION: 10.6
 | 
			
		||||
      - name: "Prepare Report Portal"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v5.1.0
 | 
			
		||||
        id: rp-prepare
 | 
			
		||||
        with:
 | 
			
		||||
          rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }}
 | 
			
		||||
          rp-token: ${{ secrets.REPORT_PORTAL_TOKEN }}
 | 
			
		||||
          rp-project: ${{ env.RP_PROJECT }}
 | 
			
		||||
          rp-use-static-launch-name: true
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Add GitHub Step Summary"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        env:
 | 
			
		||||
          RP_ENABLED: ${{ steps.rp-prepare.outputs.enabled }}
 | 
			
		||||
          RP_KEY: ${{ steps.rp-prepare.outputs.key }}
 | 
			
		||||
          RP_URL: ${{ steps.rp-prepare.outputs.url }}
 | 
			
		||||
        run: bash scripts/ci/add_step_summary.sh
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Run tests"
 | 
			
		||||
        run: mvn -B test -pl repository -am -Dtest=AllDBTestsTestSuite -DfailIfNoTests=false -Ddb.name=alfresco -Ddb.url=jdbc:mariadb://localhost:3307/alfresco?useUnicode=yes\&characterEncoding=UTF-8 -Ddb.username=alfresco -Ddb.password=alfresco -Ddb.driver=org.mariadb.jdbc.Driver
 | 
			
		||||
        id: run-tests
 | 
			
		||||
        env:
 | 
			
		||||
          RP_OPTS: ${{ github.ref_name == 'master' && steps.rp-prepare.outputs.mvn-opts || '' }}
 | 
			
		||||
        run: |
 | 
			
		||||
          eval "args=($RP_OPTS)"
 | 
			
		||||
          mvn -B test -pl repository -am -Dtest=AllDBTestsTestSuite -DfailIfNoTests=false -Ddb.name=alfresco -Ddb.url=jdbc:mariadb://localhost:3307/alfresco?useUnicode=yes\&characterEncoding=UTF-8 -Ddb.username=alfresco -Ddb.password=alfresco -Ddb.driver=org.mariadb.jdbc.Driver "${args[@]}"
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Update GitHub Step Summary"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "#### ⏱ After Tests: $(date -u +'%Y-%m-%d %H:%M:%S%:z')" >> $GITHUB_STEP_SUMMARY
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Summarize Report Portal"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v5.1.0
 | 
			
		||||
        id: rp-summarize
 | 
			
		||||
        with:
 | 
			
		||||
          tests-outcome: ${{ steps.run-tests.outcome }}
 | 
			
		||||
          rp-launch-key: ${{ steps.rp-prepare.outputs.key }}
 | 
			
		||||
          rp-project: ${{ env.RP_PROJECT }}
 | 
			
		||||
          rp-token: ${{ secrets.REPORT_PORTAL_TOKEN }}
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Exit on failure"
 | 
			
		||||
        if: steps.run-tests.outcome != 'success'
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::error title=run-tests::Tests failed: re-throwing on error."
 | 
			
		||||
          exit 1
 | 
			
		||||
      - name: "Clean Maven cache"
 | 
			
		||||
        run: bash ./scripts/ci/cleanup_cache.sh
 | 
			
		||||
 | 
			
		||||
@@ -274,18 +467,62 @@ jobs:
 | 
			
		||||
      !contains(github.event.head_commit.message, '[skip tests]') &&
 | 
			
		||||
      !contains(github.event.head_commit.message, '[force')
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.35.2
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v1.35.2      
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v1.35.2
 | 
			
		||||
      - name: "Init"
 | 
			
		||||
        run: bash ./scripts/ci/init.sh
 | 
			
		||||
      - name: "Run MySQL 8 database"
 | 
			
		||||
        run: docker-compose -f ./scripts/ci/docker-compose/docker-compose-db.yaml --profile mysql up -d
 | 
			
		||||
        run: docker compose -f ./scripts/ci/docker-compose/docker-compose-db.yaml --profile mysql up -d
 | 
			
		||||
        env:
 | 
			
		||||
          MYSQL_VERSION: 8
 | 
			
		||||
      - name: "Prepare Report Portal"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v5.1.0
 | 
			
		||||
        id: rp-prepare
 | 
			
		||||
        with:
 | 
			
		||||
          rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }}
 | 
			
		||||
          rp-token: ${{ secrets.REPORT_PORTAL_TOKEN }}
 | 
			
		||||
          rp-project: ${{ env.RP_PROJECT }}
 | 
			
		||||
          rp-use-static-launch-name: true
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Add GitHub Step Summary"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        env:
 | 
			
		||||
          RP_ENABLED: ${{ steps.rp-prepare.outputs.enabled }}
 | 
			
		||||
          RP_KEY: ${{ steps.rp-prepare.outputs.key }}
 | 
			
		||||
          RP_URL: ${{ steps.rp-prepare.outputs.url }}
 | 
			
		||||
        run: bash scripts/ci/add_step_summary.sh
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Run tests"
 | 
			
		||||
        run: mvn -B test -pl repository -am -Dtest=AllDBTestsTestSuite -DfailIfNoTests=false -Ddb.driver=com.mysql.jdbc.Driver -Ddb.name=alfresco -Ddb.url=jdbc:mysql://localhost:3307/alfresco -Ddb.username=alfresco -Ddb.password=alfresco
 | 
			
		||||
        id: run-tests
 | 
			
		||||
        env:
 | 
			
		||||
          RP_OPTS: ${{ github.ref_name == 'master' && steps.rp-prepare.outputs.mvn-opts || '' }}
 | 
			
		||||
        run: |
 | 
			
		||||
          eval "args=($RP_OPTS)" 
 | 
			
		||||
          mvn -B test -pl repository -am -Dtest=AllDBTestsTestSuite -DfailIfNoTests=false -Ddb.driver=com.mysql.jdbc.Driver -Ddb.name=alfresco -Ddb.url=jdbc:mysql://localhost:3307/alfresco -Ddb.username=alfresco -Ddb.password=alfresco "${args[@]}"
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Update GitHub Step Summary"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "#### ⏱ After Tests: $(date -u +'%Y-%m-%d %H:%M:%S%:z')" >> $GITHUB_STEP_SUMMARY
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Summarize Report Portal"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v5.1.0
 | 
			
		||||
        id: rp-summarize
 | 
			
		||||
        with:
 | 
			
		||||
          tests-outcome: ${{ steps.run-tests.outcome }}
 | 
			
		||||
          rp-launch-key: ${{ steps.rp-prepare.outputs.key }}
 | 
			
		||||
          rp-project: ${{ env.RP_PROJECT }}
 | 
			
		||||
          rp-token: ${{ secrets.REPORT_PORTAL_TOKEN }}
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Exit on failure"
 | 
			
		||||
        if: steps.run-tests.outcome != 'success'
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::error title=run-tests::Tests failed: re-throwing on error."
 | 
			
		||||
          exit 1
 | 
			
		||||
      - name: "Clean Maven cache"
 | 
			
		||||
        run: bash ./scripts/ci/cleanup_cache.sh
 | 
			
		||||
 | 
			
		||||
@@ -300,18 +537,62 @@ jobs:
 | 
			
		||||
      !contains(github.event.head_commit.message, '[skip tests]') &&
 | 
			
		||||
      !contains(github.event.head_commit.message, '[force')
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.35.2
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v1.35.2      
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v1.35.2
 | 
			
		||||
      - name: "Init"
 | 
			
		||||
        run: bash ./scripts/ci/init.sh
 | 
			
		||||
      - name: "Run PostgreSQL 13.12 database"
 | 
			
		||||
        run: docker-compose -f ./scripts/ci/docker-compose/docker-compose-db.yaml --profile postgres up -d
 | 
			
		||||
        run: docker compose -f ./scripts/ci/docker-compose/docker-compose-db.yaml --profile postgres up -d
 | 
			
		||||
        env:
 | 
			
		||||
          POSTGRES_VERSION: 13.12
 | 
			
		||||
      - name: "Prepare Report Portal"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v5.1.0
 | 
			
		||||
        id: rp-prepare
 | 
			
		||||
        with:
 | 
			
		||||
          rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }}
 | 
			
		||||
          rp-token: ${{ secrets.REPORT_PORTAL_TOKEN }}
 | 
			
		||||
          rp-project: ${{ env.RP_PROJECT }}
 | 
			
		||||
          rp-use-static-launch-name: true
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Add GitHub Step Summary"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        env:
 | 
			
		||||
          RP_ENABLED: ${{ steps.rp-prepare.outputs.enabled }}
 | 
			
		||||
          RP_KEY: ${{ steps.rp-prepare.outputs.key }}
 | 
			
		||||
          RP_URL: ${{ steps.rp-prepare.outputs.url }}
 | 
			
		||||
        run: bash scripts/ci/add_step_summary.sh
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Run tests"
 | 
			
		||||
        run: mvn -B test -pl repository -am -Dtest=AllDBTestsTestSuite -DfailIfNoTests=false -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco
 | 
			
		||||
        id: run-tests
 | 
			
		||||
        env:
 | 
			
		||||
          RP_OPTS: ${{ github.ref_name == 'master' && steps.rp-prepare.outputs.mvn-opts || '' }}
 | 
			
		||||
        run: |
 | 
			
		||||
          eval "args=($RP_OPTS)" 
 | 
			
		||||
          mvn -B test -pl repository -am -Dtest=AllDBTestsTestSuite -DfailIfNoTests=false -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco "${args[@]}"
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Update GitHub Step Summary"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "#### ⏱ After Tests: $(date -u +'%Y-%m-%d %H:%M:%S%:z')" >> $GITHUB_STEP_SUMMARY
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Summarize Report Portal"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v5.1.0
 | 
			
		||||
        id: rp-summarize
 | 
			
		||||
        with:
 | 
			
		||||
          tests-outcome: ${{ steps.run-tests.outcome }}
 | 
			
		||||
          rp-launch-key: ${{ steps.rp-prepare.outputs.key }}
 | 
			
		||||
          rp-project: ${{ env.RP_PROJECT }}
 | 
			
		||||
          rp-token: ${{ secrets.REPORT_PORTAL_TOKEN }}
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Exit on failure"
 | 
			
		||||
        if: steps.run-tests.outcome != 'success'
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::error title=run-tests::Tests failed: re-throwing on error."
 | 
			
		||||
          exit 1
 | 
			
		||||
      - name: "Clean Maven cache"
 | 
			
		||||
        run: bash ./scripts/ci/cleanup_cache.sh
 | 
			
		||||
 | 
			
		||||
@@ -326,18 +607,62 @@ jobs:
 | 
			
		||||
      !contains(github.event.head_commit.message, '[skip tests]') &&
 | 
			
		||||
      !contains(github.event.head_commit.message, '[force')
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.35.2
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v1.35.2      
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v1.35.2
 | 
			
		||||
      - name: "Init"
 | 
			
		||||
        run: bash ./scripts/ci/init.sh
 | 
			
		||||
      - name: "Run PostgreSQL 14.9 database"
 | 
			
		||||
        run: docker-compose -f ./scripts/ci/docker-compose/docker-compose-db.yaml --profile postgres up -d
 | 
			
		||||
        run: docker compose -f ./scripts/ci/docker-compose/docker-compose-db.yaml --profile postgres up -d
 | 
			
		||||
        env:
 | 
			
		||||
          POSTGRES_VERSION: 14.9
 | 
			
		||||
      - name: "Prepare Report Portal"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v5.1.0
 | 
			
		||||
        id: rp-prepare
 | 
			
		||||
        with:
 | 
			
		||||
          rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }}
 | 
			
		||||
          rp-token: ${{ secrets.REPORT_PORTAL_TOKEN }}
 | 
			
		||||
          rp-project: ${{ env.RP_PROJECT }}
 | 
			
		||||
          rp-use-static-launch-name: true
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Add GitHub Step Summary"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        env:
 | 
			
		||||
          RP_ENABLED: ${{ steps.rp-prepare.outputs.enabled }}
 | 
			
		||||
          RP_KEY: ${{ steps.rp-prepare.outputs.key }}
 | 
			
		||||
          RP_URL: ${{ steps.rp-prepare.outputs.url }}
 | 
			
		||||
        run: bash scripts/ci/add_step_summary.sh
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Run tests"
 | 
			
		||||
        run: mvn -B test -pl repository -am -Dtest=AllDBTestsTestSuite -DfailIfNoTests=false -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco
 | 
			
		||||
        id: run-tests
 | 
			
		||||
        env:
 | 
			
		||||
          RP_OPTS: ${{ github.ref_name == 'master' && steps.rp-prepare.outputs.mvn-opts || '' }}
 | 
			
		||||
        run: |
 | 
			
		||||
          eval "args=($RP_OPTS)" 
 | 
			
		||||
          mvn -B test -pl repository -am -Dtest=AllDBTestsTestSuite -DfailIfNoTests=false -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco "${args[@]}"
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Update GitHub Step Summary"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "#### ⏱ After Tests: $(date -u +'%Y-%m-%d %H:%M:%S%:z')" >> $GITHUB_STEP_SUMMARY
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Summarize Report Portal"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v5.1.0
 | 
			
		||||
        id: rp-summarize
 | 
			
		||||
        with:
 | 
			
		||||
          tests-outcome: ${{ steps.run-tests.outcome }}
 | 
			
		||||
          rp-launch-key: ${{ steps.rp-prepare.outputs.key }}
 | 
			
		||||
          rp-project: ${{ env.RP_PROJECT }}
 | 
			
		||||
          rp-token: ${{ secrets.REPORT_PORTAL_TOKEN }}
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Exit on failure"
 | 
			
		||||
        if: steps.run-tests.outcome != 'success'
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::error title=run-tests::Tests failed: re-throwing on error."
 | 
			
		||||
          exit 1
 | 
			
		||||
      - name: "Clean Maven cache"
 | 
			
		||||
        run: bash ./scripts/ci/cleanup_cache.sh
 | 
			
		||||
 | 
			
		||||
@@ -352,18 +677,62 @@ jobs:
 | 
			
		||||
      !contains(github.event.head_commit.message, '[skip tests]') &&
 | 
			
		||||
      !contains(github.event.head_commit.message, '[force')
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.35.2
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v1.35.2      
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v1.35.2
 | 
			
		||||
      - name: "Init"
 | 
			
		||||
        run: bash ./scripts/ci/init.sh
 | 
			
		||||
      - name: "Run PostgreSQL 15.4 database"
 | 
			
		||||
        run: docker-compose -f ./scripts/ci/docker-compose/docker-compose-db.yaml --profile postgres up -d
 | 
			
		||||
        run: docker compose -f ./scripts/ci/docker-compose/docker-compose-db.yaml --profile postgres up -d
 | 
			
		||||
        env:
 | 
			
		||||
          POSTGRES_VERSION: 15.4
 | 
			
		||||
      - name: "Prepare Report Portal"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v5.1.0
 | 
			
		||||
        id: rp-prepare
 | 
			
		||||
        with:
 | 
			
		||||
          rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }}
 | 
			
		||||
          rp-token: ${{ secrets.REPORT_PORTAL_TOKEN }}
 | 
			
		||||
          rp-project: ${{ env.RP_PROJECT }}
 | 
			
		||||
          rp-use-static-launch-name: true
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Add GitHub Step Summary"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        env:
 | 
			
		||||
          RP_ENABLED: ${{ steps.rp-prepare.outputs.enabled }}
 | 
			
		||||
          RP_KEY: ${{ steps.rp-prepare.outputs.key }}
 | 
			
		||||
          RP_URL: ${{ steps.rp-prepare.outputs.url }}
 | 
			
		||||
        run: bash scripts/ci/add_step_summary.sh
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Run tests"
 | 
			
		||||
        run: mvn -B test -pl repository -am -Dtest=AllDBTestsTestSuite -DfailIfNoTests=false -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco
 | 
			
		||||
        id: run-tests
 | 
			
		||||
        env:
 | 
			
		||||
          RP_OPTS: ${{ github.ref_name == 'master' && steps.rp-prepare.outputs.mvn-opts || '' }}
 | 
			
		||||
        run: |
 | 
			
		||||
          eval "args=($RP_OPTS)" 
 | 
			
		||||
          mvn -B test -pl repository -am -Dtest=AllDBTestsTestSuite -DfailIfNoTests=false -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco "${args[@]}"
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Update GitHub Step Summary"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "#### ⏱ After Tests: $(date -u +'%Y-%m-%d %H:%M:%S%:z')" >> $GITHUB_STEP_SUMMARY
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Summarize Report Portal"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v5.1.0
 | 
			
		||||
        id: rp-summarize
 | 
			
		||||
        with:
 | 
			
		||||
          tests-outcome: ${{ steps.run-tests.outcome }}
 | 
			
		||||
          rp-launch-key: ${{ steps.rp-prepare.outputs.key }}
 | 
			
		||||
          rp-project: ${{ env.RP_PROJECT }}
 | 
			
		||||
          rp-token: ${{ secrets.REPORT_PORTAL_TOKEN }}
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Exit on failure"
 | 
			
		||||
        if: steps.run-tests.outcome != 'success'
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::error title=run-tests::Tests failed: re-throwing on error."
 | 
			
		||||
          exit 1
 | 
			
		||||
      - name: "Clean Maven cache"
 | 
			
		||||
        run: bash ./scripts/ci/cleanup_cache.sh
 | 
			
		||||
 | 
			
		||||
@@ -376,16 +745,60 @@ jobs:
 | 
			
		||||
      !contains(github.event.head_commit.message, '[skip tests]') &&
 | 
			
		||||
      !contains(github.event.head_commit.message, '[force')
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.35.2
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v1.35.2      
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v1.35.2
 | 
			
		||||
      - name: "Init"
 | 
			
		||||
        run: bash ./scripts/ci/init.sh
 | 
			
		||||
      - name: "Run ActiveMQ"
 | 
			
		||||
        run: docker-compose -f ./scripts/ci/docker-compose/docker-compose.yaml --profile activemq up -d
 | 
			
		||||
        run: docker compose -f ./scripts/ci/docker-compose/docker-compose.yaml --profile activemq up -d
 | 
			
		||||
      - name: "Prepare Report Portal"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v5.13.1
 | 
			
		||||
        id: rp-prepare
 | 
			
		||||
        with:
 | 
			
		||||
          rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }}
 | 
			
		||||
          rp-token: ${{ secrets.REPORT_PORTAL_TOKEN }}
 | 
			
		||||
          rp-project: ${{ env.RP_PROJECT }}
 | 
			
		||||
          rp-use-static-launch-name: true
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Add GitHub Step Summary"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        env:
 | 
			
		||||
          RP_ENABLED: ${{ steps.rp-prepare.outputs.enabled }}
 | 
			
		||||
          RP_KEY: ${{ steps.rp-prepare.outputs.key }}
 | 
			
		||||
          RP_URL: ${{ steps.rp-prepare.outputs.url }}
 | 
			
		||||
        run: bash scripts/ci/add_step_summary.sh
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Run tests"
 | 
			
		||||
        run: mvn -B test -pl repository -am -Dtest=CamelRoutesTest,CamelComponentsTest -DfailIfNoTests=false
 | 
			
		||||
        id: run-tests
 | 
			
		||||
        env:
 | 
			
		||||
          RP_OPTS: ${{ github.ref_name == 'master' && steps.rp-prepare.outputs.mvn-opts || '' }}
 | 
			
		||||
        run: |
 | 
			
		||||
          eval "args=($RP_OPTS)"
 | 
			
		||||
          mvn -B test -pl repository -am -Dtest=MessagingUnitTestSuite -DfailIfNoTests=false "${args[@]}"
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Update GitHub Step Summary"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "#### ⏱ After Tests: $(date -u +'%Y-%m-%d %H:%M:%S%:z')" >> $GITHUB_STEP_SUMMARY
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Summarize Report Portal"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v5.13.1
 | 
			
		||||
        id: rp-summarize
 | 
			
		||||
        with:
 | 
			
		||||
          tests-outcome: ${{ steps.run-tests.outcome }}
 | 
			
		||||
          rp-launch-key: ${{ steps.rp-prepare.outputs.key }}
 | 
			
		||||
          rp-project: ${{ env.RP_PROJECT }}
 | 
			
		||||
          rp-token: ${{ secrets.REPORT_PORTAL_TOKEN }}
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Exit on failure"
 | 
			
		||||
        if: steps.run-tests.outcome != 'success'
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::error title=run-tests::Tests failed: re-throwing on error."
 | 
			
		||||
          exit 1
 | 
			
		||||
      - name: "Clean Maven cache"
 | 
			
		||||
        run: bash ./scripts/ci/cleanup_cache.sh
 | 
			
		||||
 | 
			
		||||
@@ -432,7 +845,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@v3
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.35.2
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v1.35.2      
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v1.35.2
 | 
			
		||||
@@ -454,9 +867,53 @@ jobs:
 | 
			
		||||
            echo "HOSTNAME_VERIFICATION_DISABLED=false" >> "$GITHUB_ENV"
 | 
			
		||||
          fi
 | 
			
		||||
      - name: "Set up the environment"
 | 
			
		||||
        run: docker-compose -f ./scripts/ci/docker-compose/docker-compose.yaml --profile ${{ matrix.compose-profile }} up -d
 | 
			
		||||
        run: docker compose -f ./scripts/ci/docker-compose/docker-compose.yaml --profile ${{ matrix.compose-profile }} up -d
 | 
			
		||||
      - name: "Prepare Report Portal"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v5.1.0
 | 
			
		||||
        id: rp-prepare
 | 
			
		||||
        with:
 | 
			
		||||
          rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} - ${{ matrix.testSuite }} ${{ matrix.idp }}
 | 
			
		||||
          rp-token: ${{ secrets.REPORT_PORTAL_TOKEN }}
 | 
			
		||||
          rp-project: ${{ env.RP_PROJECT }}
 | 
			
		||||
          rp-use-static-launch-name: true
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Add GitHub Step Summary"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        env:
 | 
			
		||||
          RP_ENABLED: ${{ steps.rp-prepare.outputs.enabled }}
 | 
			
		||||
          RP_KEY: ${{ steps.rp-prepare.outputs.key }}
 | 
			
		||||
          RP_URL: ${{ steps.rp-prepare.outputs.url }}
 | 
			
		||||
        run: bash scripts/ci/add_step_summary.sh
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Run tests"
 | 
			
		||||
        run: mvn -B test -pl repository -am -Dtest=${{ matrix.testSuite }} -DfailIfNoTests=false -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco ${{ matrix.mvn-options }}
 | 
			
		||||
        id: run-tests
 | 
			
		||||
        env:
 | 
			
		||||
          RP_OPTS: ${{ github.ref_name == 'master' && steps.rp-prepare.outputs.mvn-opts || '' }}
 | 
			
		||||
        run: |
 | 
			
		||||
          eval "args=($RP_OPTS)"
 | 
			
		||||
          mvn -B test -pl repository -am -Dtest=${{ matrix.testSuite }} -DfailIfNoTests=false -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco ${{ matrix.mvn-options }} "${args[@]}"
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Update GitHub Step Summary"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "#### ⏱ After Tests: $(date -u +'%Y-%m-%d %H:%M:%S%:z')" >> $GITHUB_STEP_SUMMARY
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Summarize Report Portal"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v5.1.0
 | 
			
		||||
        id: rp-summarize
 | 
			
		||||
        with:
 | 
			
		||||
          tests-outcome: ${{ steps.run-tests.outcome }}
 | 
			
		||||
          rp-launch-key: ${{ steps.rp-prepare.outputs.key }}
 | 
			
		||||
          rp-project: ${{ env.RP_PROJECT }}
 | 
			
		||||
          rp-token: ${{ secrets.REPORT_PORTAL_TOKEN }}
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Exit on failure"
 | 
			
		||||
        if: steps.run-tests.outcome != 'success'
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::error title=run-tests::Tests failed: re-throwing on error."
 | 
			
		||||
          exit 1
 | 
			
		||||
      - name: "Clean Maven cache"
 | 
			
		||||
        run: bash ./scripts/ci/cleanup_cache.sh
 | 
			
		||||
 | 
			
		||||
@@ -501,7 +958,7 @@ jobs:
 | 
			
		||||
    env:
 | 
			
		||||
      REQUIRES_LOCAL_IMAGES: true
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.35.2
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v1.35.2      
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v1.35.2
 | 
			
		||||
@@ -517,16 +974,59 @@ jobs:
 | 
			
		||||
      - name: "Build TAS integration tests"
 | 
			
		||||
        if: ${{ matrix.test-name }} == 'Integration TAS tests'
 | 
			
		||||
        run: mvn install -pl :alfresco-community-repo-integration-test -am -DskipTests -Pall-tas-tests
 | 
			
		||||
      - name: "Prepare Report Portal"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v5.1.0
 | 
			
		||||
        id: rp-prepare
 | 
			
		||||
        with:
 | 
			
		||||
          rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} - ${{ matrix.test-name }}
 | 
			
		||||
          rp-token: ${{ secrets.REPORT_PORTAL_TOKEN }}
 | 
			
		||||
          rp-project: ${{ env.RP_PROJECT }}
 | 
			
		||||
          rp-use-static-launch-name: true
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Add GitHub Step Summary"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        env:
 | 
			
		||||
          RP_ENABLED: ${{ steps.rp-prepare.outputs.enabled }}
 | 
			
		||||
          RP_KEY: ${{ steps.rp-prepare.outputs.key }}
 | 
			
		||||
          RP_URL: ${{ steps.rp-prepare.outputs.url }}
 | 
			
		||||
        run: bash scripts/ci/add_step_summary.sh
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Run tests"
 | 
			
		||||
        id: tests
 | 
			
		||||
        env:
 | 
			
		||||
          RP_OPTS: ${{ github.ref_name == 'master' && steps.rp-prepare.outputs.mvn-opts || '' }}
 | 
			
		||||
        timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
 | 
			
		||||
        run: mvn -B verify -f packaging/tests/${{ matrix.pom-dir }}/pom.xml -Pall-tas-tests,${{ matrix.test-profile }} -Denvironment=default -DrunBugs=false
 | 
			
		||||
        run: |
 | 
			
		||||
          eval "args=($RP_OPTS)"
 | 
			
		||||
          mvn -B verify -f packaging/tests/${{ matrix.pom-dir }}/pom.xml -Pall-tas-tests,${{ matrix.test-profile }} -Denvironment=default -DrunBugs=false "${args[@]}"
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Print output after success"
 | 
			
		||||
        if: ${{ always() && steps.tests.outcome == 'success' }}
 | 
			
		||||
        run: ${TAS_SCRIPTS}/output_tests_run.sh "packaging/tests/${{ matrix.pom-dir }}"
 | 
			
		||||
      - name: "Print output after failure"
 | 
			
		||||
        if: ${{ always() && steps.tests.outcome == 'failure' }}
 | 
			
		||||
        run: ${TAS_SCRIPTS}/output_logs_for_failures.sh "packaging/tests/${{ matrix.pom-dir }}"
 | 
			
		||||
      - name: "Update GitHub Step Summary"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "#### ⏱ After Tests: $(date -u +'%Y-%m-%d %H:%M:%S%:z')" >> $GITHUB_STEP_SUMMARY
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Summarize Report Portal"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v5.1.0
 | 
			
		||||
        id: rp-summarize
 | 
			
		||||
        with:
 | 
			
		||||
          tests-outcome: ${{ steps.tests.outcome }}
 | 
			
		||||
          rp-launch-key: ${{ steps.rp-prepare.outputs.key }}
 | 
			
		||||
          rp-project: ${{ env.RP_PROJECT }}
 | 
			
		||||
          rp-token: ${{ secrets.REPORT_PORTAL_TOKEN }}
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Exit on failure"
 | 
			
		||||
        if: steps.tests.outcome != 'success'
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::error title=tests::Tests failed: re-throwing on error."
 | 
			
		||||
          exit 1
 | 
			
		||||
      - name: "Clean Maven cache"
 | 
			
		||||
        run: bash ./scripts/ci/cleanup_cache.sh
 | 
			
		||||
 | 
			
		||||
@@ -539,16 +1039,60 @@ jobs:
 | 
			
		||||
      !contains(github.event.head_commit.message, '[skip tests]') &&
 | 
			
		||||
      !contains(github.event.head_commit.message, '[force')
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.35.2
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v1.35.2      
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v1.35.2
 | 
			
		||||
      - name: "Init"
 | 
			
		||||
        run: bash ./scripts/ci/init.sh
 | 
			
		||||
      - name: "Run Postgres 15.4 database"
 | 
			
		||||
        run: docker-compose -f ./scripts/ci/docker-compose/docker-compose.yaml --profile postgres up -d
 | 
			
		||||
        run: docker compose -f ./scripts/ci/docker-compose/docker-compose.yaml --profile postgres up -d
 | 
			
		||||
      - name: "Prepare Report Portal"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v5.1.0
 | 
			
		||||
        id: rp-prepare
 | 
			
		||||
        with:
 | 
			
		||||
          rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }}
 | 
			
		||||
          rp-token: ${{ secrets.REPORT_PORTAL_TOKEN }}
 | 
			
		||||
          rp-project: ${{ env.RP_PROJECT }}
 | 
			
		||||
          rp-use-static-launch-name: true
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Add GitHub Step Summary"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        env:
 | 
			
		||||
          RP_ENABLED: ${{ steps.rp-prepare.outputs.enabled }}
 | 
			
		||||
          RP_KEY: ${{ steps.rp-prepare.outputs.key }}
 | 
			
		||||
          RP_URL: ${{ steps.rp-prepare.outputs.url }}
 | 
			
		||||
        run: bash scripts/ci/add_step_summary.sh
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Run tests"
 | 
			
		||||
        run: mvn -B test -pl :alfresco-share-services -am -Dtest=ShareServicesTestSuite -DfailIfNoTests=false -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco
 | 
			
		||||
        id: run-tests
 | 
			
		||||
        env:
 | 
			
		||||
          RP_OPTS: ${{ github.ref_name == 'master' && steps.rp-prepare.outputs.mvn-opts || '' }}
 | 
			
		||||
        run: |
 | 
			
		||||
          eval "args=($RP_OPTS)"
 | 
			
		||||
          mvn -B test -pl :alfresco-share-services -am -Dtest=ShareServicesTestSuite -DfailIfNoTests=false -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco "${args[@]}"
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Update GitHub Step Summary"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "#### ⏱ After Tests: $(date -u +'%Y-%m-%d %H:%M:%S%:z')" >> $GITHUB_STEP_SUMMARY
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Summarize Report Portal"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v5.1.0
 | 
			
		||||
        id: rp-summarize
 | 
			
		||||
        with:
 | 
			
		||||
          tests-outcome: ${{ steps.run-tests.outcome }}
 | 
			
		||||
          rp-launch-key: ${{ steps.rp-prepare.outputs.key }}
 | 
			
		||||
          rp-project: ${{ env.RP_PROJECT }}
 | 
			
		||||
          rp-token: ${{ secrets.REPORT_PORTAL_TOKEN }}
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Exit on failure"
 | 
			
		||||
        if: steps.run-tests.outcome != 'success'
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::error title=run-tests::Tests failed: re-throwing on error."
 | 
			
		||||
          exit 1
 | 
			
		||||
      - name: "Clean Maven cache"
 | 
			
		||||
        run: bash ./scripts/ci/cleanup_cache.sh
 | 
			
		||||
 | 
			
		||||
@@ -569,7 +1113,7 @@ jobs:
 | 
			
		||||
    env:
 | 
			
		||||
      REQUIRES_INSTALLED_ARTIFACTS: true
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.35.2
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v1.35.2      
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v1.35.2
 | 
			
		||||
@@ -578,9 +1122,23 @@ jobs:
 | 
			
		||||
        run: |
 | 
			
		||||
          bash ./scripts/ci/init.sh
 | 
			
		||||
          bash ./scripts/ci/build.sh
 | 
			
		||||
      - name: "Prepare Report Portal"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v5.1.0
 | 
			
		||||
        id: rp-prepare
 | 
			
		||||
        with:
 | 
			
		||||
          rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} 0${{ matrix.part }} - (PostgreSQL) ${{ matrix.test-name }}
 | 
			
		||||
          rp-token: ${{ secrets.REPORT_PORTAL_TOKEN }}
 | 
			
		||||
          rp-project: ${{ env.RP_PROJECT }}
 | 
			
		||||
          rp-use-static-launch-name: true
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Verify"
 | 
			
		||||
        timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
 | 
			
		||||
        run: mvn --file amps/ags/pom.xml -B verify -Dmaven.javadoc.skip=true -Dmaven.source.skip=true -Pags -Pstart-postgres -PagsAllTestSuitePt${{ matrix.part }} ${{ env.LOG_WARN }}
 | 
			
		||||
        env:
 | 
			
		||||
          RP_OPTS: ${{ github.ref_name == 'master' && steps.rp-prepare.outputs.mvn-opts || '' }}
 | 
			
		||||
        run: |
 | 
			
		||||
          eval "args=($RP_OPTS)"
 | 
			
		||||
          mvn --file amps/ags/pom.xml -B verify -Dmaven.javadoc.skip=true -Dmaven.source.skip=true -Pags -Pstart-postgres -PagsAllTestSuitePt${{ matrix.part }} ${{ env.LOG_WARN }} "${args[@]}"
 | 
			
		||||
      - name: "Clean Maven cache"
 | 
			
		||||
        run: bash ./scripts/ci/cleanup_cache.sh
 | 
			
		||||
 | 
			
		||||
@@ -601,7 +1159,7 @@ jobs:
 | 
			
		||||
    env:
 | 
			
		||||
      REQUIRES_INSTALLED_ARTIFACTS: true
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.35.2
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v1.35.2      
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v1.35.2
 | 
			
		||||
@@ -610,9 +1168,23 @@ jobs:
 | 
			
		||||
        run: |
 | 
			
		||||
          bash ./scripts/ci/init.sh
 | 
			
		||||
          bash ./scripts/ci/build.sh
 | 
			
		||||
      - name: "Prepare Report Portal"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v5.1.0
 | 
			
		||||
        id: rp-prepare
 | 
			
		||||
        with:
 | 
			
		||||
          rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} 0${{ matrix.part }} - (MySQL) ${{ matrix.test-name }}
 | 
			
		||||
          rp-token: ${{ secrets.REPORT_PORTAL_TOKEN }}
 | 
			
		||||
          rp-project: ${{ env.RP_PROJECT }}
 | 
			
		||||
          rp-use-static-launch-name: true
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Verify"
 | 
			
		||||
        timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
 | 
			
		||||
        run: mvn --file amps/ags/pom.xml -B verify -Dmaven.javadoc.skip=true -Dmaven.source.skip=true -Pags -Pstart-mysql -PagsAllTestSuitePt${{ matrix.part }} ${{ env.LOG_WARN }}
 | 
			
		||||
        env:
 | 
			
		||||
          RP_OPTS: ${{ github.ref_name == 'master' && steps.rp-prepare.outputs.mvn-opts || '' }}
 | 
			
		||||
        run: |
 | 
			
		||||
          eval "args=($RP_OPTS)"
 | 
			
		||||
          mvn --file amps/ags/pom.xml -B verify -Dmaven.javadoc.skip=true -Dmaven.source.skip=true -Pags -Pstart-mysql -PagsAllTestSuitePt${{ matrix.part }} ${{ env.LOG_WARN }} "${args[@]}"
 | 
			
		||||
      - name: "Clean Maven cache"
 | 
			
		||||
        run: bash ./scripts/ci/cleanup_cache.sh
 | 
			
		||||
 | 
			
		||||
@@ -629,7 +1201,7 @@ jobs:
 | 
			
		||||
    env:
 | 
			
		||||
      REQUIRES_LOCAL_IMAGES: true
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.35.2
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v1.35.2      
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v1.35.2
 | 
			
		||||
@@ -644,9 +1216,53 @@ jobs:
 | 
			
		||||
          ${{ env.TAS_SCRIPTS }}/start-compose.sh ./amps/ags/rm-community/rm-community-repo/docker-compose.yml
 | 
			
		||||
          ${{ env.TAS_SCRIPTS }}/wait-for-alfresco-start.sh "http://localhost:8080/alfresco"
 | 
			
		||||
          mvn -B install -pl :alfresco-governance-services-automation-community-rest-api -am -Pags -Pall-tas-tests -DskipTests
 | 
			
		||||
      - name: "Prepare Report Portal"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v5.1.0
 | 
			
		||||
        id: rp-prepare
 | 
			
		||||
        with:
 | 
			
		||||
          rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }}
 | 
			
		||||
          rp-token: ${{ secrets.REPORT_PORTAL_TOKEN }}
 | 
			
		||||
          rp-project: ${{ env.RP_PROJECT }}
 | 
			
		||||
          rp-use-static-launch-name: true
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Add GitHub Step Summary"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        env:
 | 
			
		||||
          RP_ENABLED: ${{ steps.rp-prepare.outputs.enabled }}
 | 
			
		||||
          RP_KEY: ${{ steps.rp-prepare.outputs.key }}
 | 
			
		||||
          RP_URL: ${{ steps.rp-prepare.outputs.url }}
 | 
			
		||||
        run: bash scripts/ci/add_step_summary.sh
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Test"
 | 
			
		||||
        id: run-tests
 | 
			
		||||
        timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
 | 
			
		||||
        run: mvn -B test -pl :alfresco-governance-services-automation-community-rest-api -Dskip.automationtests=false -Pags -Pall-tas-tests
 | 
			
		||||
        env:
 | 
			
		||||
          RP_OPTS: ${{ github.ref_name == 'master' && steps.rp-prepare.outputs.mvn-opts || '' }}
 | 
			
		||||
        run: |
 | 
			
		||||
          eval "args=($RP_OPTS)"
 | 
			
		||||
          mvn -B test -pl :alfresco-governance-services-automation-community-rest-api -Dskip.automationtests=false -Pags -Pall-tas-tests "${args[@]}"
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Update GitHub Step Summary"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "#### ⏱ After Tests: $(date -u +'%Y-%m-%d %H:%M:%S%:z')" >> $GITHUB_STEP_SUMMARY
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Summarize Report Portal"
 | 
			
		||||
        if: github.ref_name == 'master'
 | 
			
		||||
        uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v5.1.0
 | 
			
		||||
        id: rp-summarize
 | 
			
		||||
        with:
 | 
			
		||||
          tests-outcome: ${{ steps.run-tests.outcome }}
 | 
			
		||||
          rp-launch-key: ${{ steps.rp-prepare.outputs.key }}
 | 
			
		||||
          rp-project: ${{ env.RP_PROJECT }}
 | 
			
		||||
          rp-token: ${{ secrets.REPORT_PORTAL_TOKEN }}
 | 
			
		||||
        continue-on-error: true
 | 
			
		||||
      - name: "Exit on failure"
 | 
			
		||||
        if: steps.run-tests.outcome != 'success'
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "::error title=run-tests::Tests failed: re-throwing on error."
 | 
			
		||||
          exit 1
 | 
			
		||||
      - name: "Configure AWS credentials"
 | 
			
		||||
        if: ${{ always() }}
 | 
			
		||||
        uses: aws-actions/configure-aws-credentials@v1
 | 
			
		||||
@@ -675,7 +1291,7 @@ jobs:
 | 
			
		||||
      !contains(github.event.head_commit.message, '[skip tests]') &&
 | 
			
		||||
      !contains(github.event.head_commit.message, '[force]')
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.35.2
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v1.35.2      
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v1.35.2
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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@v3
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
        with:
 | 
			
		||||
          persist-credentials: false
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.35.2
 | 
			
		||||
@@ -60,7 +60,7 @@ jobs:
 | 
			
		||||
      !contains(github.event.head_commit.message, '[no downstream]') &&
 | 
			
		||||
      github.event_name != 'pull_request'
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
        with:
 | 
			
		||||
          persist-credentials: false
 | 
			
		||||
      - uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.35.2
 | 
			
		||||
 
 | 
			
		||||
@@ -71,7 +71,7 @@ the _alfresco-internal_ repository:
 | 
			
		||||
   </snapshots>
 | 
			
		||||
</repository>
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
For additional instructions you can check the official Maven documentation:
 | 
			
		||||
* [setting up repositories](https://maven.apache.org/guides/mini/guide-multiple-repositories.html)
 | 
			
		||||
@@ -227,7 +227,7 @@ If only the Community or Enterprise images need to be built than the same comman
 | 
			
		||||
 | 
			
		||||
The Docker images of the Repo can be started independently from Share running the following command in the rm-repo-enterprise or rm-repo-community folder which contains the Docker-compose.yml file:
 | 
			
		||||
```
 | 
			
		||||
docker-compose up
 | 
			
		||||
docker compose up
 | 
			
		||||
```
 | 
			
		||||
> Be aware of the fact that the Share images can not be started independently from Repo
 | 
			
		||||
 | 
			
		||||
@@ -237,5 +237,5 @@ e.g. In order to start an instance of rm-enterprise-repo and rm-enterprise-share
 | 
			
		||||
 | 
			
		||||
If you have a license for jRebel then this can be used from the rm-community-share or rm-enterprise-share directories with:
 | 
			
		||||
```
 | 
			
		||||
docker-compose -f docker-compose.yml -f jrebel-docker-compose.yml --project-name agsdev up --build --force-recreate
 | 
			
		||||
docker compose -f docker-compose.yml -f jrebel-docker-compose.yml --project-name agsdev up --build --force-recreate
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
@@ -4,9 +4,9 @@ set -x
 | 
			
		||||
# Display running containers
 | 
			
		||||
docker ps
 | 
			
		||||
 | 
			
		||||
alfrescoContainerId=$(docker ps -a | grep '_alfresco_' | awk '{print $1}')
 | 
			
		||||
shareContainerId=$(docker ps -a | grep '_share_' | awk '{print $1}')
 | 
			
		||||
solrContainerId=$(docker ps -a | grep '_search_' | awk '{print $1}')
 | 
			
		||||
alfrescoContainerId=$(docker ps -a | grep '\-alfresco\-' | awk '{print $1}')
 | 
			
		||||
shareContainerId=$(docker ps -a | grep '\-share\-' | awk '{print $1}')
 | 
			
		||||
solrContainerId=$(docker ps -a | grep '\-search\-' | awk '{print $1}')
 | 
			
		||||
 | 
			
		||||
docker logs $alfrescoContainerId > alfresco.log
 | 
			
		||||
if [ -n "$shareContainerId" ]; then
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
   <parent>
 | 
			
		||||
      <groupId>org.alfresco</groupId>
 | 
			
		||||
      <artifactId>alfresco-community-repo-amps</artifactId>
 | 
			
		||||
      <version>23.3.0.2</version>
 | 
			
		||||
      <version>23.3.0.87-SNAPSHOT</version>
 | 
			
		||||
   </parent>
 | 
			
		||||
 | 
			
		||||
   <modules>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
   <parent>
 | 
			
		||||
      <groupId>org.alfresco</groupId>
 | 
			
		||||
      <artifactId>alfresco-governance-services-community-parent</artifactId>
 | 
			
		||||
      <version>23.3.0.2</version>
 | 
			
		||||
      <version>23.3.0.87-SNAPSHOT</version>
 | 
			
		||||
   </parent>
 | 
			
		||||
 | 
			
		||||
   <modules>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
   <parent>
 | 
			
		||||
      <groupId>org.alfresco</groupId>
 | 
			
		||||
      <artifactId>alfresco-governance-services-automation-community-repo</artifactId>
 | 
			
		||||
      <version>23.3.0.2</version>
 | 
			
		||||
      <version>23.3.0.87-SNAPSHOT</version>
 | 
			
		||||
   </parent>
 | 
			
		||||
 | 
			
		||||
   <build>
 | 
			
		||||
@@ -74,6 +74,22 @@
 | 
			
		||||
         <artifactId>alfresco-testng</artifactId>
 | 
			
		||||
         <version>1.1</version>
 | 
			
		||||
      </dependency>
 | 
			
		||||
      <dependency>
 | 
			
		||||
         <groupId>com.epam.reportportal</groupId>
 | 
			
		||||
         <artifactId>agent-java-testng</artifactId>
 | 
			
		||||
         <scope>test</scope>
 | 
			
		||||
      </dependency>
 | 
			
		||||
      <dependency>
 | 
			
		||||
         <groupId>com.squareup.okhttp3</groupId>
 | 
			
		||||
         <artifactId>okhttp</artifactId>
 | 
			
		||||
         <scope>test</scope>
 | 
			
		||||
      </dependency>
 | 
			
		||||
      <dependency>
 | 
			
		||||
         <groupId>org.awaitility</groupId>
 | 
			
		||||
         <artifactId>awaitility</artifactId>
 | 
			
		||||
         <version>${dependency.awaitility.version}</version>
 | 
			
		||||
         <scope>test</scope>
 | 
			
		||||
      </dependency>
 | 
			
		||||
      <dependency>
 | 
			
		||||
         <groupId>org.apache.commons</groupId>
 | 
			
		||||
         <artifactId>commons-collections4</artifactId>
 | 
			
		||||
@@ -82,7 +98,7 @@
 | 
			
		||||
      <dependency>
 | 
			
		||||
         <groupId>com.github.docker-java</groupId>
 | 
			
		||||
         <artifactId>docker-java</artifactId>
 | 
			
		||||
         <version>3.3.2</version>
 | 
			
		||||
         <version>3.3.6</version>
 | 
			
		||||
         <exclusions>
 | 
			
		||||
            <exclusion>
 | 
			
		||||
               <groupId>org.bouncycastle</groupId>
 | 
			
		||||
 
 | 
			
		||||
@@ -39,6 +39,7 @@ import org.alfresco.rest.rm.community.requests.gscore.GSCoreAPI;
 | 
			
		||||
import org.alfresco.rest.rm.community.requests.gscore.api.ActionsExecutionAPI;
 | 
			
		||||
import org.alfresco.rest.rm.community.requests.gscore.api.FilePlanAPI;
 | 
			
		||||
import org.alfresco.rest.rm.community.requests.gscore.api.FilesAPI;
 | 
			
		||||
import org.alfresco.rest.rm.community.requests.gscore.api.HoldsAPI;
 | 
			
		||||
import org.alfresco.rest.rm.community.requests.gscore.api.RMSiteAPI;
 | 
			
		||||
import org.alfresco.rest.rm.community.requests.gscore.api.RMUserAPI;
 | 
			
		||||
import org.alfresco.rest.rm.community.requests.gscore.api.RecordCategoryAPI;
 | 
			
		||||
@@ -48,6 +49,7 @@ import org.alfresco.rest.rm.community.requests.gscore.api.TransferAPI;
 | 
			
		||||
import org.alfresco.rest.rm.community.requests.gscore.api.TransferContainerAPI;
 | 
			
		||||
import org.alfresco.rest.rm.community.requests.gscore.api.UnfiledContainerAPI;
 | 
			
		||||
import org.alfresco.rest.rm.community.requests.gscore.api.UnfiledRecordFolderAPI;
 | 
			
		||||
import org.alfresco.rest.rm.community.requests.gscore.api.RetentionScheduleAPI;
 | 
			
		||||
import org.alfresco.utility.data.DataUserAIS;
 | 
			
		||||
import org.alfresco.utility.model.RepoTestModel;
 | 
			
		||||
import org.alfresco.utility.model.UserModel;
 | 
			
		||||
@@ -243,4 +245,24 @@ public class RestAPIFactory
 | 
			
		||||
    {
 | 
			
		||||
        return getGSCoreAPI(null).usingActionsExecutionsAPI();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public HoldsAPI getHoldsAPI()
 | 
			
		||||
    {
 | 
			
		||||
        return getGSCoreAPI(null).usingHoldsAPI();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public HoldsAPI getHoldsAPI(UserModel userModel)
 | 
			
		||||
    {
 | 
			
		||||
        return getGSCoreAPI(userModel).usingHoldsAPI();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public RetentionScheduleAPI getRetentionScheduleAPI()
 | 
			
		||||
    {
 | 
			
		||||
        return getGSCoreAPI(null).usingRetentionScheduleAPI();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public RetentionScheduleAPI getRetentionScheduleAPI(UserModel userModel)
 | 
			
		||||
    {
 | 
			
		||||
        return getGSCoreAPI(userModel).usingRetentionScheduleAPI();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -61,7 +61,6 @@ public class FilePlanComponentFields
 | 
			
		||||
    public static final String PROPERTIES_RECORD_SEARCH_DISPOSITION_EVENTS = "rma:recordSearchDispositionEvents";
 | 
			
		||||
    public static final String PROPERTIES_DECLASSIFICATION_REVIEW_COMPLETED_BY = "rma:declassificationReviewCompletedBy";
 | 
			
		||||
    public static final String PROPERTIES_DECLASSIFICATION_REVIEW_COMPLETED_AT = "rma:declassificationReviewCompletedAt";
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /** File plan properties */
 | 
			
		||||
    public static final String PROPERTIES_COMPONENT_ID = "st:componentId";
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,41 @@
 | 
			
		||||
/*-
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.rm.community.model.hold;
 | 
			
		||||
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.Builder;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
 | 
			
		||||
@Builder
 | 
			
		||||
@Data
 | 
			
		||||
@NoArgsConstructor
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
public class BulkBodyCancel
 | 
			
		||||
{
 | 
			
		||||
    private String reason;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,83 @@
 | 
			
		||||
/*-
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.rm.community.model.hold;
 | 
			
		||||
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonProperty;
 | 
			
		||||
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.Builder;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
import org.alfresco.utility.model.TestModel;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * POJO for hold
 | 
			
		||||
 *
 | 
			
		||||
 * @author Damian Ujma
 | 
			
		||||
 */
 | 
			
		||||
@Builder
 | 
			
		||||
@Data
 | 
			
		||||
@NoArgsConstructor
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
public class Hold extends TestModel
 | 
			
		||||
{
 | 
			
		||||
    @JsonProperty(required = true)
 | 
			
		||||
    private String id;
 | 
			
		||||
 | 
			
		||||
    @JsonProperty(required = true)
 | 
			
		||||
    private String name;
 | 
			
		||||
 | 
			
		||||
    @JsonProperty(required = true)
 | 
			
		||||
    private String description;
 | 
			
		||||
 | 
			
		||||
    @JsonProperty(required = true)
 | 
			
		||||
    private String reason;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean equals(Object o)
 | 
			
		||||
    {
 | 
			
		||||
        if (this == o)
 | 
			
		||||
        {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        if (o == null || getClass() != o.getClass())
 | 
			
		||||
        {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        Hold hold = (Hold) o;
 | 
			
		||||
        return Objects.equals(id, hold.id) && Objects.equals(name, hold.name)
 | 
			
		||||
            && Objects.equals(description, hold.description) && Objects.equals(reason, hold.reason);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int hashCode()
 | 
			
		||||
    {
 | 
			
		||||
        return Objects.hash(id, name, description, reason);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,59 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.rm.community.model.hold;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonProperty;
 | 
			
		||||
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.Builder;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
import org.alfresco.rest.search.RestRequestQueryModel;
 | 
			
		||||
import org.alfresco.utility.model.TestModel;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * POJO for hold bulk request
 | 
			
		||||
 *
 | 
			
		||||
 * @author Damian Ujma
 | 
			
		||||
 */
 | 
			
		||||
@Builder
 | 
			
		||||
@Data
 | 
			
		||||
@NoArgsConstructor
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
public class HoldBulkOperation extends TestModel
 | 
			
		||||
{
 | 
			
		||||
    public enum HoldBulkOperationType
 | 
			
		||||
    {
 | 
			
		||||
        ADD
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @JsonProperty(required = true)
 | 
			
		||||
    private RestRequestQueryModel query;
 | 
			
		||||
    @JsonProperty(required = true)
 | 
			
		||||
    private HoldBulkOperationType op;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,50 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.rm.community.model.hold;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonProperty;
 | 
			
		||||
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.Builder;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * POJO for hold bulk request entry
 | 
			
		||||
 *
 | 
			
		||||
 * @author Damian Ujma
 | 
			
		||||
 */
 | 
			
		||||
@Builder
 | 
			
		||||
@Data
 | 
			
		||||
@NoArgsConstructor
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
public class HoldBulkOperationEntry
 | 
			
		||||
{
 | 
			
		||||
    private String bulkStatusId;
 | 
			
		||||
 | 
			
		||||
    private long totalItems;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,67 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.rm.community.model.hold;
 | 
			
		||||
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.Builder;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
import org.alfresco.utility.model.TestModel;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * POJO for hold bulk request
 | 
			
		||||
 *
 | 
			
		||||
 * @author Damian Ujma
 | 
			
		||||
 */
 | 
			
		||||
@Builder
 | 
			
		||||
@Data
 | 
			
		||||
@NoArgsConstructor
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
public class HoldBulkStatus extends TestModel
 | 
			
		||||
{
 | 
			
		||||
    private String bulkStatusId;
 | 
			
		||||
 | 
			
		||||
    private String startTime;
 | 
			
		||||
 | 
			
		||||
    private String endTime;
 | 
			
		||||
 | 
			
		||||
    private long processedItems;
 | 
			
		||||
 | 
			
		||||
    private long errorsCount;
 | 
			
		||||
 | 
			
		||||
    private long totalItems;
 | 
			
		||||
 | 
			
		||||
    private String lastError;
 | 
			
		||||
 | 
			
		||||
    private String status;
 | 
			
		||||
 | 
			
		||||
    private boolean isCancelled;
 | 
			
		||||
 | 
			
		||||
    private String cancellationReason;
 | 
			
		||||
 | 
			
		||||
    private HoldBulkOperation holdBulkOperation;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,38 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.rm.community.model.hold;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.rest.core.RestModels;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handle collection of {@link HoldBulkStatusEntry}
 | 
			
		||||
 *
 | 
			
		||||
 * @author Damian Ujma
 | 
			
		||||
 */
 | 
			
		||||
public class HoldBulkStatusCollection extends RestModels<HoldBulkStatusEntry, HoldBulkStatusCollection>
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,46 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.rm.community.model.hold;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonProperty;
 | 
			
		||||
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.Builder;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.EqualsAndHashCode;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
import org.alfresco.rest.core.RestModels;
 | 
			
		||||
 | 
			
		||||
@Builder
 | 
			
		||||
@Data
 | 
			
		||||
@EqualsAndHashCode(callSuper = true)
 | 
			
		||||
@NoArgsConstructor
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
public class HoldBulkStatusEntry extends RestModels<HoldBulkStatus, HoldBulkStatusEntry>
 | 
			
		||||
{
 | 
			
		||||
    private HoldBulkStatus entry;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,52 @@
 | 
			
		||||
/*-
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.rm.community.model.hold;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonProperty;
 | 
			
		||||
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.Builder;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.EqualsAndHashCode;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
import org.alfresco.utility.model.TestModel;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * POJO for hold child
 | 
			
		||||
 *
 | 
			
		||||
 * @author Damian Ujma
 | 
			
		||||
 */
 | 
			
		||||
@Builder
 | 
			
		||||
@EqualsAndHashCode(callSuper = true)
 | 
			
		||||
@Data
 | 
			
		||||
@NoArgsConstructor
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
public class HoldChild extends TestModel
 | 
			
		||||
{
 | 
			
		||||
    @JsonProperty(required = true)
 | 
			
		||||
    private String id;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,38 @@
 | 
			
		||||
/*-
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.rm.community.model.hold;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.rest.core.RestModels;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handle collection of {@link HoldChildEntry}
 | 
			
		||||
 *
 | 
			
		||||
 * @author Damian Ujma
 | 
			
		||||
 */
 | 
			
		||||
public class HoldChildCollection extends RestModels<HoldChildEntry, HoldChildCollection>
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,52 @@
 | 
			
		||||
/*-
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.rm.community.model.hold;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonProperty;
 | 
			
		||||
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.Builder;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.EqualsAndHashCode;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
import org.alfresco.rest.core.RestModels;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * POJO for hold child entry
 | 
			
		||||
 *
 | 
			
		||||
 * @author Damian Ujma
 | 
			
		||||
 */
 | 
			
		||||
@Builder
 | 
			
		||||
@Data
 | 
			
		||||
@EqualsAndHashCode(callSuper = true)
 | 
			
		||||
@NoArgsConstructor
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
public class HoldChildEntry extends RestModels<Hold, HoldChildEntry>
 | 
			
		||||
{
 | 
			
		||||
    @JsonProperty
 | 
			
		||||
    private HoldChild entry;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,38 @@
 | 
			
		||||
/*-
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.rm.community.model.hold;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.rest.core.RestModels;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handle collection of {@link HoldEntry}
 | 
			
		||||
 *
 | 
			
		||||
 * @author Damian Ujma
 | 
			
		||||
 */
 | 
			
		||||
public class HoldCollection extends RestModels<HoldEntry, HoldCollection>
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,52 @@
 | 
			
		||||
/*-
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.rm.community.model.hold;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonProperty;
 | 
			
		||||
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.Builder;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.EqualsAndHashCode;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
import org.alfresco.utility.model.TestModel;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * POJO for hold deletion reason
 | 
			
		||||
 *
 | 
			
		||||
 * @author Damian Ujma
 | 
			
		||||
 */
 | 
			
		||||
@Builder
 | 
			
		||||
@Data
 | 
			
		||||
@EqualsAndHashCode(callSuper = true)
 | 
			
		||||
@NoArgsConstructor
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
public class HoldDeletionReason extends TestModel
 | 
			
		||||
{
 | 
			
		||||
    @JsonProperty
 | 
			
		||||
    private String reason;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,52 @@
 | 
			
		||||
/*-
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.rm.community.model.hold;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonProperty;
 | 
			
		||||
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.Builder;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.EqualsAndHashCode;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
import org.alfresco.rest.core.RestModels;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * POJO for hold child entry
 | 
			
		||||
 *
 | 
			
		||||
 * @author Damian Ujma
 | 
			
		||||
 */
 | 
			
		||||
@Builder
 | 
			
		||||
@Data
 | 
			
		||||
@EqualsAndHashCode(callSuper = true)
 | 
			
		||||
@NoArgsConstructor
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
public class HoldDeletionReasonEntry extends RestModels<HoldDeletionReason, HoldDeletionReasonEntry>
 | 
			
		||||
{
 | 
			
		||||
    @JsonProperty
 | 
			
		||||
    private HoldDeletionReason entry;
 | 
			
		||||
}
 | 
			
		||||
@@ -26,31 +26,27 @@
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.rm.community.model.hold;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonProperty;
 | 
			
		||||
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.Builder;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.EqualsAndHashCode;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
import org.alfresco.utility.model.TestModel;
 | 
			
		||||
import org.alfresco.rest.core.RestModels;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * POJO for hold entry
 | 
			
		||||
 *
 | 
			
		||||
 * @author Rodica Sutu
 | 
			
		||||
 * @since 3.2
 | 
			
		||||
 * @author Damian Ujma
 | 
			
		||||
 */
 | 
			
		||||
@Builder
 | 
			
		||||
@Data
 | 
			
		||||
@EqualsAndHashCode(callSuper = true)
 | 
			
		||||
@NoArgsConstructor
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
@JsonIgnoreProperties (ignoreUnknown = true)
 | 
			
		||||
public class HoldEntry extends TestModel
 | 
			
		||||
public class HoldEntry extends RestModels<Hold, HoldEntry>
 | 
			
		||||
{
 | 
			
		||||
    @JsonProperty (required = true)
 | 
			
		||||
    private String name;
 | 
			
		||||
 | 
			
		||||
    @JsonProperty (required = true)
 | 
			
		||||
    private String nodeRef;
 | 
			
		||||
    @JsonProperty
 | 
			
		||||
    private Hold entry;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,56 @@
 | 
			
		||||
/*-
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.rm.community.model.hold.v0;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonProperty;
 | 
			
		||||
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.Builder;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
import org.alfresco.utility.model.TestModel;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * POJO for hold entry
 | 
			
		||||
 *
 | 
			
		||||
 * @author Rodica Sutu
 | 
			
		||||
 * @since 3.2
 | 
			
		||||
 */
 | 
			
		||||
@Builder
 | 
			
		||||
@Data
 | 
			
		||||
@NoArgsConstructor
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
@JsonIgnoreProperties (ignoreUnknown = true)
 | 
			
		||||
public class HoldEntry extends TestModel
 | 
			
		||||
{
 | 
			
		||||
    @JsonProperty (required = true)
 | 
			
		||||
    private String name;
 | 
			
		||||
 | 
			
		||||
    @JsonProperty (required = true)
 | 
			
		||||
    private String nodeRef;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,58 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.rm.community.model.retentionschedule;
 | 
			
		||||
 | 
			
		||||
import lombok.EqualsAndHashCode;
 | 
			
		||||
import org.alfresco.utility.model.TestModel;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * retention schedule
 | 
			
		||||
 */
 | 
			
		||||
@EqualsAndHashCode(callSuper = true)
 | 
			
		||||
@Data
 | 
			
		||||
public class RetentionSchedule extends TestModel
 | 
			
		||||
{
 | 
			
		||||
    private String id ;
 | 
			
		||||
    private String parentId;
 | 
			
		||||
    private String authority;
 | 
			
		||||
    private String instructions;
 | 
			
		||||
    private boolean isRecordLevel;
 | 
			
		||||
    private boolean isUnpublishedUpdates;
 | 
			
		||||
    private List<RetentionScheduleActionDefinition> actions;
 | 
			
		||||
 | 
			
		||||
    public boolean getIsRecordLevel()
 | 
			
		||||
    {
 | 
			
		||||
        return isRecordLevel;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setIsRecordLevel(boolean recordLevel) {
 | 
			
		||||
        isRecordLevel = recordLevel;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,50 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.rm.community.model.retentionschedule;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * retention schedule action definition
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
public class RetentionScheduleActionDefinition
 | 
			
		||||
{
 | 
			
		||||
    private String id;
 | 
			
		||||
    private String name;
 | 
			
		||||
    private int periodAmount;
 | 
			
		||||
    private String period;
 | 
			
		||||
    private String periodProperty;
 | 
			
		||||
    private boolean combineRetentionStepConditions;
 | 
			
		||||
    private List<String> events;
 | 
			
		||||
    private boolean eligibleOnFirstCompleteEvent;
 | 
			
		||||
    private String description;
 | 
			
		||||
    private boolean retainRecordMetadataAfterDestruction;
 | 
			
		||||
    private String location;
 | 
			
		||||
    private int index;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,32 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.rm.community.model.retentionschedule;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.rest.core.RestModels;
 | 
			
		||||
public class RetentionScheduleCollection extends RestModels<RetentionScheduleEntry, RetentionScheduleCollection>
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,37 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.rm.community.model.retentionschedule;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonProperty;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import org.alfresco.rest.core.RestModels;
 | 
			
		||||
@Data
 | 
			
		||||
public class RetentionScheduleEntry extends RestModels<RetentionSchedule, RetentionScheduleEntry>
 | 
			
		||||
{
 | 
			
		||||
    @JsonProperty
 | 
			
		||||
    private RetentionSchedule entry;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,33 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.rm.community.model.retentionschedule;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.rest.core.RestModels;
 | 
			
		||||
 | 
			
		||||
public class RetentionScheduleStepCollection extends RestModels<RetentionScheduleStepEntry, RetentionScheduleStepCollection>
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,38 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.rm.community.model.retentionschedule;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonProperty;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import org.alfresco.rest.core.RestModels;
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
public class RetentionScheduleStepEntry extends RestModels<RetentionScheduleActionDefinition, RetentionScheduleStepEntry>
 | 
			
		||||
{
 | 
			
		||||
    @JsonProperty
 | 
			
		||||
    private RetentionScheduleActionDefinition entry;
 | 
			
		||||
}
 | 
			
		||||
@@ -37,6 +37,7 @@ import org.alfresco.rest.rm.community.requests.gscore.api.ActionsExecutionAPI;
 | 
			
		||||
import org.alfresco.rest.rm.community.requests.RMModelRequest;
 | 
			
		||||
import org.alfresco.rest.rm.community.requests.gscore.api.FilePlanAPI;
 | 
			
		||||
import org.alfresco.rest.rm.community.requests.gscore.api.FilesAPI;
 | 
			
		||||
import org.alfresco.rest.rm.community.requests.gscore.api.HoldsAPI;
 | 
			
		||||
import org.alfresco.rest.rm.community.requests.gscore.api.RMSiteAPI;
 | 
			
		||||
import org.alfresco.rest.rm.community.requests.gscore.api.RMUserAPI;
 | 
			
		||||
import org.alfresco.rest.rm.community.requests.gscore.api.RecordCategoryAPI;
 | 
			
		||||
@@ -46,6 +47,7 @@ import org.alfresco.rest.rm.community.requests.gscore.api.TransferAPI;
 | 
			
		||||
import org.alfresco.rest.rm.community.requests.gscore.api.TransferContainerAPI;
 | 
			
		||||
import org.alfresco.rest.rm.community.requests.gscore.api.UnfiledContainerAPI;
 | 
			
		||||
import org.alfresco.rest.rm.community.requests.gscore.api.UnfiledRecordFolderAPI;
 | 
			
		||||
import org.alfresco.rest.rm.community.requests.gscore.api.RetentionScheduleAPI;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the entire GS Core API
 | 
			
		||||
@@ -190,4 +192,11 @@ public class GSCoreAPI extends RMModelRequest
 | 
			
		||||
    {
 | 
			
		||||
        return new ActionsExecutionAPI(getRmRestWrapper());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public HoldsAPI usingHoldsAPI() { return new HoldsAPI(getRmRestWrapper()); }
 | 
			
		||||
 | 
			
		||||
    public RetentionScheduleAPI usingRetentionScheduleAPI()
 | 
			
		||||
    {
 | 
			
		||||
        return new RetentionScheduleAPI(getRmRestWrapper());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -38,6 +38,8 @@ import static org.springframework.http.HttpMethod.PUT;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.rest.core.RMRestWrapper;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.fileplan.FilePlan;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.Hold;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.HoldCollection;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.recordcategory.RecordCategory;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.recordcategory.RecordCategoryCollection;
 | 
			
		||||
import org.alfresco.rest.rm.community.requests.RMModelRequest;
 | 
			
		||||
@@ -213,4 +215,74 @@ public class FilePlanAPI extends RMModelRequest
 | 
			
		||||
                parameters));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a hold.
 | 
			
		||||
     *
 | 
			
		||||
     * @param holdModel The hold model
 | 
			
		||||
     * @param filePlanId The identifier of a file plan
 | 
			
		||||
     * @param parameters The URL parameters to add
 | 
			
		||||
     * @return The created {@link Hold}
 | 
			
		||||
     * @throws RuntimeException for the following cases:
 | 
			
		||||
     * <ul>
 | 
			
		||||
     *  <li>{@code filePlanId} is not a valid format or {@code filePlanId} is invalid</li>
 | 
			
		||||
     *  <li>authentication fails</li>
 | 
			
		||||
     *  <li>current user does not have permission to add children to {@code filePlanId}</li>
 | 
			
		||||
     *  <li>{@code filePlanIds} does not exist</li>
 | 
			
		||||
     *  <li>new name clashes with an existing node in the current parent container</li>
 | 
			
		||||
     * </ul>
 | 
			
		||||
     */
 | 
			
		||||
    public Hold createHold(Hold holdModel, String filePlanId, String parameters)
 | 
			
		||||
    {
 | 
			
		||||
        mandatoryString("filePlanId", filePlanId);
 | 
			
		||||
        mandatoryObject("holdModel", holdModel);
 | 
			
		||||
 | 
			
		||||
        return getRmRestWrapper().processModel(Hold.class, requestWithBody(
 | 
			
		||||
            POST,
 | 
			
		||||
            toJson(holdModel),
 | 
			
		||||
            "file-plans/{filePlanId}/holds",
 | 
			
		||||
            filePlanId,
 | 
			
		||||
            parameters
 | 
			
		||||
                                                                          ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * See {@link #createHold(Hold, String, String)}
 | 
			
		||||
     */
 | 
			
		||||
    public Hold createHold(Hold holdModel, String filePlanId)
 | 
			
		||||
    {
 | 
			
		||||
        return createHold(holdModel, filePlanId, EMPTY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the holds of a file plan.
 | 
			
		||||
     *
 | 
			
		||||
     * @param filePlanId The identifier of a file plan
 | 
			
		||||
     * @param parameters The URL parameters to add
 | 
			
		||||
     * @return The {@link HoldCollection} for the given {@code filePlanId}
 | 
			
		||||
     * @throws RuntimeException for the following cases:
 | 
			
		||||
     * <ul>
 | 
			
		||||
     *  <li>authentication fails</li>
 | 
			
		||||
     *  <li>current user does not have permission to read {@code filePlanId}</li>
 | 
			
		||||
     *  <li>{@code filePlanId} does not exist</li>
 | 
			
		||||
     *</ul>
 | 
			
		||||
     */
 | 
			
		||||
    public HoldCollection getHolds(String filePlanId, String parameters)
 | 
			
		||||
    {
 | 
			
		||||
        mandatoryString("filePlanId", filePlanId);
 | 
			
		||||
 | 
			
		||||
        return getRmRestWrapper().processModels(HoldCollection.class, simpleRequest(
 | 
			
		||||
            GET,
 | 
			
		||||
            "file-plans/{filePlanId}/holds?{parameters}",
 | 
			
		||||
            filePlanId,
 | 
			
		||||
            parameters
 | 
			
		||||
                                                                                   ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * See {@link #getHolds(String, String)}
 | 
			
		||||
     */
 | 
			
		||||
    public HoldCollection getHolds(String filePlanId)
 | 
			
		||||
    {
 | 
			
		||||
        return getHolds(filePlanId, EMPTY);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,446 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.rm.community.requests.gscore.api;
 | 
			
		||||
 | 
			
		||||
import static org.alfresco.rest.core.RestRequest.requestWithBody;
 | 
			
		||||
import static org.alfresco.rest.core.RestRequest.simpleRequest;
 | 
			
		||||
import static org.alfresco.rest.rm.community.util.ParameterCheck.mandatoryObject;
 | 
			
		||||
import static org.alfresco.rest.rm.community.util.ParameterCheck.mandatoryString;
 | 
			
		||||
import static org.alfresco.rest.rm.community.util.PojoUtility.toJson;
 | 
			
		||||
import static org.apache.commons.lang3.StringUtils.EMPTY;
 | 
			
		||||
import static org.springframework.http.HttpMethod.DELETE;
 | 
			
		||||
import static org.springframework.http.HttpMethod.GET;
 | 
			
		||||
import static org.springframework.http.HttpMethod.POST;
 | 
			
		||||
import static org.springframework.http.HttpMethod.PUT;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.rest.core.RMRestWrapper;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.BulkBodyCancel;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.Hold;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.HoldBulkOperation;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.HoldBulkOperationEntry;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.HoldBulkStatus;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.HoldBulkStatusCollection;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.HoldChild;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.HoldChildCollection;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.HoldDeletionReason;
 | 
			
		||||
import org.alfresco.rest.rm.community.requests.RMModelRequest;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Holds REST API Wrapper
 | 
			
		||||
 *
 | 
			
		||||
 * @author Damian Ujma
 | 
			
		||||
 */
 | 
			
		||||
public class HoldsAPI extends RMModelRequest
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param rmRestWrapper
 | 
			
		||||
     */
 | 
			
		||||
    public HoldsAPI(RMRestWrapper rmRestWrapper)
 | 
			
		||||
    {
 | 
			
		||||
        super(rmRestWrapper);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets a hold.
 | 
			
		||||
     *
 | 
			
		||||
     * @param holdId The identifier of a hold
 | 
			
		||||
     * @param parameters The URL parameters to add
 | 
			
		||||
     * @return The {@link Hold} for the given {@code holdId}
 | 
			
		||||
     * @throws RuntimeException for the following cases:
 | 
			
		||||
     * <ul>
 | 
			
		||||
     *  <li>{@code holdId} is not a valid format</li>
 | 
			
		||||
     *  <li>authentication fails</li>
 | 
			
		||||
     *  <li>current user does not have permission to read {@code holdId}</li>
 | 
			
		||||
     *  <li>{@code holdId} does not exist</li>
 | 
			
		||||
     * </ul>
 | 
			
		||||
     */
 | 
			
		||||
    public Hold getHold(String holdId, String parameters)
 | 
			
		||||
    {
 | 
			
		||||
        mandatoryString("holdId", holdId);
 | 
			
		||||
 | 
			
		||||
        return getRmRestWrapper().processModel(Hold.class, simpleRequest(
 | 
			
		||||
            GET,
 | 
			
		||||
            "holds/{holdId}?{parameters}",
 | 
			
		||||
            holdId,
 | 
			
		||||
            parameters
 | 
			
		||||
                                                                        ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * See {@link #getHold(String, String)}
 | 
			
		||||
     */
 | 
			
		||||
    public Hold getHold(String holdId)
 | 
			
		||||
    {
 | 
			
		||||
        mandatoryString("holdId", holdId);
 | 
			
		||||
 | 
			
		||||
        return getHold(holdId, EMPTY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates a hold.
 | 
			
		||||
     *
 | 
			
		||||
     * @param holdModel     The hold model which holds the information
 | 
			
		||||
     * @param holdId        The identifier of the hold
 | 
			
		||||
     * @param parameters          The URL parameters to add
 | 
			
		||||
     * @throws RuntimeException for the following cases:
 | 
			
		||||
     * <ul>
 | 
			
		||||
     *  <li>the update request is invalid or {@code holdId} is not a valid format or {@code holdModel} is invalid</li>
 | 
			
		||||
     *  <li>authentication fails</li>
 | 
			
		||||
     *  <li>current user does not have permission to update {@code holdId}</li>
 | 
			
		||||
     *  <li>{@code holdId} does not exist</li>
 | 
			
		||||
     * </ul>
 | 
			
		||||
     */
 | 
			
		||||
    public Hold updateHold(Hold holdModel, String holdId, String parameters)
 | 
			
		||||
    {
 | 
			
		||||
        mandatoryObject("holdModel", holdModel);
 | 
			
		||||
        mandatoryString("holdId", holdId);
 | 
			
		||||
 | 
			
		||||
        return getRmRestWrapper().processModel(Hold.class, requestWithBody(
 | 
			
		||||
            PUT,
 | 
			
		||||
            toJson(holdModel),
 | 
			
		||||
            "holds/{holdId}?{parameters}",
 | 
			
		||||
            holdId,
 | 
			
		||||
            parameters
 | 
			
		||||
                                                                            ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * See {@link #updateHold(Hold, String, String)}
 | 
			
		||||
     */
 | 
			
		||||
    public Hold updateHold(Hold holdModel, String holdId)
 | 
			
		||||
    {
 | 
			
		||||
        mandatoryObject("holdModel", holdModel);
 | 
			
		||||
        mandatoryString("holdId", holdId);
 | 
			
		||||
 | 
			
		||||
        return updateHold(holdModel, holdId, EMPTY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Deletes a hold.
 | 
			
		||||
     *
 | 
			
		||||
     * @param holdId The identifier of a hold
 | 
			
		||||
     * @throws RuntimeException for the following cases:
 | 
			
		||||
     * <ul>
 | 
			
		||||
     *  <li>{@code holdId} is not a valid format</li>
 | 
			
		||||
     *  <li>authentication fails</li>
 | 
			
		||||
     *  <li>current user does not have permission to delete {@code holdId}</li>
 | 
			
		||||
     *  <li>{@code holdId} does not exist</li>
 | 
			
		||||
     * </ul>
 | 
			
		||||
     */
 | 
			
		||||
    public void deleteHold(String holdId)
 | 
			
		||||
    {
 | 
			
		||||
        mandatoryString("holdId", holdId);
 | 
			
		||||
 | 
			
		||||
        getRmRestWrapper().processEmptyModel(simpleRequest(
 | 
			
		||||
            DELETE,
 | 
			
		||||
            "holds/{holdId}",
 | 
			
		||||
            holdId
 | 
			
		||||
                                                          ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Deletes a hold and stores a reason for deletion in the audit log.
 | 
			
		||||
     *
 | 
			
		||||
     * @param reason        The reason for hold deletion
 | 
			
		||||
     * @param holdId        The identifier of a hold
 | 
			
		||||
     * @throws RuntimeException for the following cases:
 | 
			
		||||
     * <ul>
 | 
			
		||||
     *  <li>{@code holdId} is not a valid format or {@code reason} is invalid</li>
 | 
			
		||||
     *  <li>authentication fails</li>
 | 
			
		||||
     *  <li>current user does not have permission to delete {@code holdId}</li>
 | 
			
		||||
     *  <li>{@code holdId} does not exist</li>
 | 
			
		||||
     * </ul>
 | 
			
		||||
     */
 | 
			
		||||
    public HoldDeletionReason deleteHoldWithReason(HoldDeletionReason reason, String holdId)
 | 
			
		||||
    {
 | 
			
		||||
        mandatoryObject("reason", reason);
 | 
			
		||||
        mandatoryString("holdId", holdId);
 | 
			
		||||
 | 
			
		||||
        return getRmRestWrapper().processModel(HoldDeletionReason.class, requestWithBody(
 | 
			
		||||
            POST,
 | 
			
		||||
            toJson(reason),
 | 
			
		||||
            "holds/{holdId}/delete",
 | 
			
		||||
            holdId
 | 
			
		||||
                                                          ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds the relationship between a child and a parent hold.
 | 
			
		||||
     *
 | 
			
		||||
     * @param holdChild The hold child model
 | 
			
		||||
     * @param holdId The identifier of a hold
 | 
			
		||||
     * @param parameters The URL parameters to add
 | 
			
		||||
     * @return The created {@link Hold}
 | 
			
		||||
     * @throws RuntimeException for the following cases:
 | 
			
		||||
     * <ul>
 | 
			
		||||
     *  <li>{@code holdId} is not a valid format or {@code holdId} is invalid</li>
 | 
			
		||||
     *  <li>authentication fails</li>
 | 
			
		||||
     *  <li>current user does not have permission to add children to {@code holdId}</li>
 | 
			
		||||
     *  <li>{@code holdId} does not exist</li>
 | 
			
		||||
     * </ul>
 | 
			
		||||
     */
 | 
			
		||||
    public HoldChild addChildToHold(HoldChild holdChild, String holdId, String parameters)
 | 
			
		||||
    {
 | 
			
		||||
        mandatoryObject("holdId", holdId);
 | 
			
		||||
 | 
			
		||||
        return getRmRestWrapper().processModel(HoldChild.class, requestWithBody(
 | 
			
		||||
            POST,
 | 
			
		||||
            toJson(holdChild),
 | 
			
		||||
            "holds/{holdId}/children",
 | 
			
		||||
            holdId,
 | 
			
		||||
            parameters));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * See {@link #addChildToHold(HoldChild, String, String)}
 | 
			
		||||
     */
 | 
			
		||||
    public HoldChild addChildToHold(HoldChild holdChild, String holdId)
 | 
			
		||||
    {
 | 
			
		||||
        return addChildToHold(holdChild, holdId, EMPTY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the children of a hold.
 | 
			
		||||
     *
 | 
			
		||||
     * @param holdId The identifier of a hold
 | 
			
		||||
     * @param parameters The URL parameters to add
 | 
			
		||||
     * @return The {@link HoldChildCollection} for the given {@code holdId}
 | 
			
		||||
     * @throws RuntimeException for the following cases:
 | 
			
		||||
     * <ul>
 | 
			
		||||
     *  <li>authentication fails</li>
 | 
			
		||||
     *  <li>current user does not have permission to read {@code holdId}</li>
 | 
			
		||||
     *  <li>{@code holdId} does not exist</li>
 | 
			
		||||
     *</ul>
 | 
			
		||||
     */
 | 
			
		||||
    public HoldChildCollection getChildren(String holdId, String parameters)
 | 
			
		||||
    {
 | 
			
		||||
        mandatoryString("holdId", holdId);
 | 
			
		||||
 | 
			
		||||
        return getRmRestWrapper().processModels(HoldChildCollection.class, simpleRequest(
 | 
			
		||||
            GET,
 | 
			
		||||
            "holds/{holdId}/children",
 | 
			
		||||
            holdId,
 | 
			
		||||
            parameters
 | 
			
		||||
                                                                                   ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * See {@link #getChildren(String, String)}
 | 
			
		||||
     */
 | 
			
		||||
    public HoldChildCollection getChildren(String holdId)
 | 
			
		||||
    {
 | 
			
		||||
        return getChildren(holdId, EMPTY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Deletes the relationship between a child and a parent hold.
 | 
			
		||||
     *
 | 
			
		||||
     * @param holdChildId The identifier of hold child
 | 
			
		||||
     * @param holdId The identifier of a hold
 | 
			
		||||
     * @param parameters The URL parameters to add
 | 
			
		||||
     * @throws RuntimeException for the following cases:
 | 
			
		||||
     * <ul>
 | 
			
		||||
     *  <li>{@code holdId} or {@code holdChildId} is invalid</li>
 | 
			
		||||
     *  <li>authentication fails</li>
 | 
			
		||||
     *  <li>current user does not have permission to delete children from {@code holdId}</li>
 | 
			
		||||
     *  <li>{@code holdId} does not exist</li>
 | 
			
		||||
     * </ul>
 | 
			
		||||
     */
 | 
			
		||||
    public void deleteHoldChild(String holdId, String holdChildId, String parameters)
 | 
			
		||||
    {
 | 
			
		||||
        mandatoryString("holdId", holdId);
 | 
			
		||||
        mandatoryString("holdChildId", holdChildId);
 | 
			
		||||
 | 
			
		||||
        getRmRestWrapper().processEmptyModel(simpleRequest(
 | 
			
		||||
            DELETE,
 | 
			
		||||
            "holds/{holdId}/children/{holdChildId}",
 | 
			
		||||
            holdId,
 | 
			
		||||
            holdChildId,
 | 
			
		||||
            parameters
 | 
			
		||||
                                                          ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * See {@link #deleteHoldChild(String, String, String)}
 | 
			
		||||
     */
 | 
			
		||||
    public void deleteHoldChild(String holdId, String holdChildId)
 | 
			
		||||
    {
 | 
			
		||||
        deleteHoldChild(holdId, holdChildId, EMPTY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Starts a bulk process for a hold.
 | 
			
		||||
     *
 | 
			
		||||
     * @param holdBulkOperation The bulk operation details
 | 
			
		||||
     * @param hold The identifier of a hold
 | 
			
		||||
     * @param parameters The URL parameters to add
 | 
			
		||||
     * @return The {@link HoldBulkOperationEntry} for the started bulk process
 | 
			
		||||
     * @throws RuntimeException for the following cases:
 | 
			
		||||
     * <ul>
 | 
			
		||||
     *  <li>{@code hold} or {@code holdBulkOperation} is invalid</li>
 | 
			
		||||
     *  <li>authentication fails</li>
 | 
			
		||||
     *  <li>current user does not have permission to start a bulk process for {@code hold}</li>
 | 
			
		||||
     *  <li>{@code hold} does not exist</li>
 | 
			
		||||
     * </ul>
 | 
			
		||||
     */
 | 
			
		||||
    public HoldBulkOperationEntry startBulkProcess(HoldBulkOperation holdBulkOperation, String hold, String parameters)
 | 
			
		||||
    {
 | 
			
		||||
        mandatoryObject("holdBulkOperation", holdBulkOperation);
 | 
			
		||||
        mandatoryString("hold", hold);
 | 
			
		||||
 | 
			
		||||
        return getRmRestWrapper().processModel(HoldBulkOperationEntry.class, requestWithBody(
 | 
			
		||||
            POST,
 | 
			
		||||
            toJson(holdBulkOperation),
 | 
			
		||||
            "holds/{hold}/bulk",
 | 
			
		||||
            hold,
 | 
			
		||||
            parameters
 | 
			
		||||
                                                                                          ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * See {@link #startBulkProcess(HoldBulkOperation, String, String)}
 | 
			
		||||
     */
 | 
			
		||||
    public HoldBulkOperationEntry startBulkProcess(HoldBulkOperation holdBulkOperation, String hold)
 | 
			
		||||
    {
 | 
			
		||||
        return startBulkProcess(holdBulkOperation, hold, EMPTY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the status of a bulk process for a hold.
 | 
			
		||||
     *
 | 
			
		||||
     * @param holdId The identifier of a hold
 | 
			
		||||
     * @param holdBulkStatusId The identifier of a bulk status operation
 | 
			
		||||
     * @param parameters The URL parameters to add
 | 
			
		||||
     * @return The {@link HoldBulkStatus} for the given {@code holdId} and {@code holdBulkStatusId}
 | 
			
		||||
     * @throws RuntimeException for the following cases:
 | 
			
		||||
     * <ul>
 | 
			
		||||
     *  <li>{@code holdId} or {@code holdBulkStatusId} is invalid</li>
 | 
			
		||||
     *  <li>authentication fails</li>
 | 
			
		||||
     *  <li>current user does not have permission to get the bulk status for {@code holdId}</li>
 | 
			
		||||
     *  <li>{@code holdId} or {@code holdBulkStatusId} does not exist</li>
 | 
			
		||||
     * </ul>
 | 
			
		||||
     */
 | 
			
		||||
    public HoldBulkStatus getBulkStatus(String holdId, String holdBulkStatusId, String parameters)
 | 
			
		||||
    {
 | 
			
		||||
        mandatoryString("holdId", holdId);
 | 
			
		||||
        mandatoryString("holdBulkStatusId", holdBulkStatusId);
 | 
			
		||||
 | 
			
		||||
        return getRmRestWrapper().processModel(HoldBulkStatus.class, simpleRequest(
 | 
			
		||||
            GET,
 | 
			
		||||
            "holds/{holdId}/bulk-statuses/{holdBulkStatusId}",
 | 
			
		||||
            holdId,
 | 
			
		||||
            holdBulkStatusId,
 | 
			
		||||
            parameters
 | 
			
		||||
                                                                                   ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * See {@link #getBulkStatus(String, String, String)}
 | 
			
		||||
     */
 | 
			
		||||
    public HoldBulkStatus getBulkStatus(String holdId, String holdBulkStatusId)
 | 
			
		||||
    {
 | 
			
		||||
        return getBulkStatus(holdId, holdBulkStatusId, EMPTY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the statuses of all bulk processes for a hold.
 | 
			
		||||
     *
 | 
			
		||||
     * @param holdId The identifier of a hold
 | 
			
		||||
     * @param parameters The URL parameters to add
 | 
			
		||||
     * @return The {@link HoldBulkStatusCollection} for the given {@code holdId}
 | 
			
		||||
     * @throws RuntimeException for the following cases:
 | 
			
		||||
     * <ul>
 | 
			
		||||
     *     <li>{@code holdId} is invalid</li>
 | 
			
		||||
     *     <li>authentication fails</li>
 | 
			
		||||
     *     <li>current user does not have permission to get the bulk statuses for {@code holdId}</li>
 | 
			
		||||
     *     <li>{@code holdId} does not exist</li>
 | 
			
		||||
     * </ul>
 | 
			
		||||
     */
 | 
			
		||||
    public HoldBulkStatusCollection getBulkStatuses(String holdId, String parameters)
 | 
			
		||||
    {
 | 
			
		||||
        mandatoryString("holdId", holdId);
 | 
			
		||||
 | 
			
		||||
        return getRmRestWrapper().processModels(HoldBulkStatusCollection.class, simpleRequest(
 | 
			
		||||
            GET,
 | 
			
		||||
            "holds/{holdId}/bulk-statuses",
 | 
			
		||||
            holdId,
 | 
			
		||||
            parameters
 | 
			
		||||
                                                                                             ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * See {@link #getBulkStatuses(String, String)}
 | 
			
		||||
     */
 | 
			
		||||
    public HoldBulkStatusCollection getBulkStatuses(String holdId)
 | 
			
		||||
    {
 | 
			
		||||
        return getBulkStatuses(holdId, EMPTY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Cancels a bulk operation for a hold.
 | 
			
		||||
     *
 | 
			
		||||
     * @param holdId The identifier of a hold
 | 
			
		||||
     * @param bulkStatusId The identifier of a bulk status operation
 | 
			
		||||
     * @param bulkBodyCancel The bulk body cancel model
 | 
			
		||||
     * @param parameters The URL parameters to add
 | 
			
		||||
     * @throws RuntimeException for the following cases:
 | 
			
		||||
     * <ul>
 | 
			
		||||
     *     <li>{@code holdId}, {@code bulkStatusId} or {@code bulkBodyCancel} is invalid</li>
 | 
			
		||||
     *     <li>authentication fails</li>
 | 
			
		||||
     *     <li>current user does not have permission to cancel the bulk operation for {@code bulkStatusId}</li>
 | 
			
		||||
     *     <li>{@code holdId} or {@code bulkStatusId} does not exist</li>
 | 
			
		||||
     * </ul>
 | 
			
		||||
     */
 | 
			
		||||
    public void cancelBulkOperation(String holdId, String bulkStatusId, BulkBodyCancel bulkBodyCancel, String parameters)
 | 
			
		||||
    {
 | 
			
		||||
        mandatoryString("holdId", holdId);
 | 
			
		||||
        mandatoryString("bulkStatusId", bulkStatusId);
 | 
			
		||||
        mandatoryObject("bulkBodyCancel", bulkBodyCancel);
 | 
			
		||||
 | 
			
		||||
        getRmRestWrapper().processEmptyModel(requestWithBody(
 | 
			
		||||
            POST,
 | 
			
		||||
            toJson(bulkBodyCancel),
 | 
			
		||||
            "holds/{holdId}/bulk-statuses/{bulkStatusId}/cancel",
 | 
			
		||||
            holdId,
 | 
			
		||||
            bulkStatusId,
 | 
			
		||||
            parameters
 | 
			
		||||
                                                          ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * See {@link #cancelBulkOperation(String, String, BulkBodyCancel, String)}
 | 
			
		||||
     */
 | 
			
		||||
    public void cancelBulkOperation(String holdId, String bulkStatusId, BulkBodyCancel bulkBodyCancel)
 | 
			
		||||
    {
 | 
			
		||||
        mandatoryString("holdId", holdId);
 | 
			
		||||
        mandatoryString("bulkStatusId", bulkStatusId);
 | 
			
		||||
        mandatoryObject("bulkBodyCancel", bulkBodyCancel);
 | 
			
		||||
 | 
			
		||||
        cancelBulkOperation(holdId, bulkStatusId, bulkBodyCancel, EMPTY);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,198 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.rm.community.requests.gscore.api;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.rest.core.RMRestWrapper;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.retentionschedule.RetentionSchedule;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.retentionschedule.RetentionScheduleActionDefinition;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.retentionschedule.RetentionScheduleCollection;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.retentionschedule.RetentionScheduleStepCollection;
 | 
			
		||||
import org.alfresco.rest.rm.community.requests.RMModelRequest;
 | 
			
		||||
 | 
			
		||||
import static org.alfresco.rest.core.RestRequest.requestWithBody;
 | 
			
		||||
import static org.alfresco.rest.core.RestRequest.simpleRequest;
 | 
			
		||||
import static org.alfresco.rest.rm.community.util.ParameterCheck.mandatoryObject;
 | 
			
		||||
import static org.alfresco.rest.rm.community.util.ParameterCheck.mandatoryString;
 | 
			
		||||
import static org.alfresco.rest.rm.community.util.PojoUtility.toJson;
 | 
			
		||||
import static org.apache.commons.lang3.StringUtils.EMPTY;
 | 
			
		||||
import static org.springframework.http.HttpMethod.GET;
 | 
			
		||||
import static org.springframework.http.HttpMethod.POST;
 | 
			
		||||
 | 
			
		||||
public class RetentionScheduleAPI extends RMModelRequest
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param rmRestWrapper
 | 
			
		||||
     */
 | 
			
		||||
    public RetentionScheduleAPI(RMRestWrapper rmRestWrapper)
 | 
			
		||||
    {
 | 
			
		||||
        super(rmRestWrapper);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a retention schedule.
 | 
			
		||||
     *
 | 
			
		||||
     * @param retentionScheduleModel The retentionSchedule model
 | 
			
		||||
     * @param recordCategoryId The identifier of a record category
 | 
			
		||||
     * @param parameters The URL parameters to add
 | 
			
		||||
     * @return The created {@link RetentionSchedule}
 | 
			
		||||
     * @throws RuntimeException for the following cases:
 | 
			
		||||
     * <ul>
 | 
			
		||||
     *  <li>{@code recordCategoryId} is not a valid format or {@code recordCategoryId} is invalid</li>
 | 
			
		||||
     *  <li>authentication fails</li>
 | 
			
		||||
     *  <li>current user does not have permission to add children to {@code recordCategoryId}</li>
 | 
			
		||||
     *  <li>{@code recordCategoryId} does not exist</li>
 | 
			
		||||
     *  <li>new name clashes with an existing node in the current parent container</li>
 | 
			
		||||
     * </ul>
 | 
			
		||||
     */
 | 
			
		||||
    public RetentionSchedule createRetentionSchedule(RetentionSchedule retentionScheduleModel, String recordCategoryId, String parameters)
 | 
			
		||||
    {
 | 
			
		||||
        mandatoryString("recordCategoryId", recordCategoryId);
 | 
			
		||||
        mandatoryObject("retentionScheduleModel", retentionScheduleModel);
 | 
			
		||||
 | 
			
		||||
        return getRmRestWrapper().processModel(RetentionSchedule.class, requestWithBody(
 | 
			
		||||
            POST,
 | 
			
		||||
            toJson(retentionScheduleModel),
 | 
			
		||||
            "record-categories/{recordCategoryId}/retention-schedules",
 | 
			
		||||
            recordCategoryId,
 | 
			
		||||
            parameters
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * See {@link #createRetentionSchedule(RetentionSchedule, String, String)}
 | 
			
		||||
     */
 | 
			
		||||
    public RetentionSchedule createRetentionSchedule(RetentionSchedule retentionScheduleModel, String recordCategoryId)
 | 
			
		||||
    {
 | 
			
		||||
        return createRetentionSchedule(retentionScheduleModel, recordCategoryId, EMPTY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the retentionSchedule of a record category.
 | 
			
		||||
     *
 | 
			
		||||
     * @param recordCategoryId The identifier of a record category
 | 
			
		||||
     * @param parameters The URL parameters to add
 | 
			
		||||
     * @return The {@link RetentionSchedule} for the given {@code recordCategoryId}
 | 
			
		||||
     * @throws RuntimeException for the following cases:
 | 
			
		||||
     * <ul>
 | 
			
		||||
     *  <li>authentication fails</li>
 | 
			
		||||
     *  <li>current user does not have permission to read {@code recordCategoryId}</li>
 | 
			
		||||
     *  <li>{@code recordCategoryId} does not exist</li>
 | 
			
		||||
     *</ul>
 | 
			
		||||
     */
 | 
			
		||||
    public RetentionScheduleCollection getRetentionSchedule(String recordCategoryId, String parameters)
 | 
			
		||||
    {
 | 
			
		||||
        mandatoryString("recordCategoryId", recordCategoryId);
 | 
			
		||||
 | 
			
		||||
        return getRmRestWrapper().processModels(RetentionScheduleCollection.class, simpleRequest(
 | 
			
		||||
            GET,
 | 
			
		||||
            "record-categories/{recordCategoryId}/retention-schedules?{parameters}",
 | 
			
		||||
            recordCategoryId,
 | 
			
		||||
            parameters
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * See {@link #getRetentionSchedule(String, String)}
 | 
			
		||||
     */
 | 
			
		||||
    public RetentionScheduleCollection getRetentionSchedule(String recordCategoryId)
 | 
			
		||||
    {
 | 
			
		||||
        return getRetentionSchedule(recordCategoryId, EMPTY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a step in the retention schedule.
 | 
			
		||||
     *
 | 
			
		||||
     * @param retentionScheduleActionDefinition The retentionScheduleActionDefinition model
 | 
			
		||||
     * @param retentionScheduleId The identifier of a retention schedule id
 | 
			
		||||
     * @param parameters The URL parameters to add
 | 
			
		||||
     * @return The created {@link RetentionScheduleActionDefinition}
 | 
			
		||||
     * @throws RuntimeException for the following cases:
 | 
			
		||||
     * <ul>
 | 
			
		||||
     *  <li>{@code retentionScheduleId} is not a valid format or {@code retentionScheduleId} is invalid</li>
 | 
			
		||||
     *  <li>authentication fails</li>
 | 
			
		||||
     *  <li>current user does not have permission to add children to {@code retentionScheduleId}</li>
 | 
			
		||||
     *  <li>{@code retentionScheduleId} does not exist</li>
 | 
			
		||||
     *  <li>new name clashes with an existing node in the current parent container</li>
 | 
			
		||||
     * </ul>
 | 
			
		||||
     */
 | 
			
		||||
    public RetentionScheduleActionDefinition createRetentionScheduleStep(RetentionScheduleActionDefinition retentionScheduleActionDefinition, String retentionScheduleId, String parameters)
 | 
			
		||||
    {
 | 
			
		||||
        mandatoryString("retentionScheduleId", retentionScheduleId);
 | 
			
		||||
        mandatoryObject("retentionScheduleActionDefinition", retentionScheduleActionDefinition);
 | 
			
		||||
 | 
			
		||||
        return getRmRestWrapper().processModel(RetentionScheduleActionDefinition.class, requestWithBody(
 | 
			
		||||
            POST,
 | 
			
		||||
            toJson(retentionScheduleActionDefinition),
 | 
			
		||||
            "retention-schedules/{retentionScheduleId}/retention-steps",
 | 
			
		||||
            retentionScheduleId,
 | 
			
		||||
            parameters
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * See {@link #createRetentionScheduleStep(RetentionScheduleActionDefinition, String)} (RetentionSchedule, String, String)}
 | 
			
		||||
     */
 | 
			
		||||
    public RetentionScheduleActionDefinition createRetentionScheduleStep(RetentionScheduleActionDefinition retentionScheduleActionDefinition, String retentionScheduleId)
 | 
			
		||||
    {
 | 
			
		||||
        return createRetentionScheduleStep(retentionScheduleActionDefinition, retentionScheduleId, EMPTY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the retentionSchedule of a record category.
 | 
			
		||||
     *
 | 
			
		||||
     * @param retentionScheduleId The identifier of a record category
 | 
			
		||||
     * @param parameters The URL parameters to add
 | 
			
		||||
     * @return The {@link RetentionScheduleActionDefinition} for the given {@code recordCategoryId}
 | 
			
		||||
     * @throws RuntimeException for the following cases:
 | 
			
		||||
     * <ul>
 | 
			
		||||
     *  <li>authentication fails</li>
 | 
			
		||||
     *  <li>current user does not have permission to read {@code recordCategoryId}</li>
 | 
			
		||||
     *  <li>{@code recordCategoryId} does not exist</li>
 | 
			
		||||
     *</ul>
 | 
			
		||||
     */
 | 
			
		||||
    public RetentionScheduleStepCollection getRetentionScheduleStep(String retentionScheduleId, String parameters)
 | 
			
		||||
    {
 | 
			
		||||
        mandatoryString("retentionScheduleId", retentionScheduleId);
 | 
			
		||||
 | 
			
		||||
        return getRmRestWrapper().processModels(RetentionScheduleStepCollection.class, simpleRequest(
 | 
			
		||||
            GET,
 | 
			
		||||
            "retention-schedules/{retentionScheduleId}/retention-steps?{parameters}",
 | 
			
		||||
            retentionScheduleId,
 | 
			
		||||
            parameters
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * See {@link #getRetentionScheduleStep(String, String)}
 | 
			
		||||
     */
 | 
			
		||||
    public RetentionScheduleStepCollection getRetentionScheduleStep(String recordCategoryId)
 | 
			
		||||
    {
 | 
			
		||||
        return getRetentionScheduleStep(recordCategoryId, EMPTY);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -36,7 +36,7 @@ import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.rest.core.v0.APIUtils;
 | 
			
		||||
import org.alfresco.rest.core.v0.BaseAPI;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.HoldEntry;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.v0.HoldEntry;
 | 
			
		||||
import org.alfresco.rest.rm.community.util.PojoUtility;
 | 
			
		||||
import org.alfresco.utility.model.UserModel;
 | 
			
		||||
import org.apache.http.HttpResponse;
 | 
			
		||||
 
 | 
			
		||||
@@ -31,18 +31,18 @@ import static java.util.Arrays.asList;
 | 
			
		||||
import static org.alfresco.rest.rm.community.base.TestData.HOLD_DESCRIPTION;
 | 
			
		||||
import static org.alfresco.rest.rm.community.base.TestData.HOLD_REASON;
 | 
			
		||||
import static org.alfresco.rest.rm.community.model.audit.AuditEvents.ADD_TO_HOLD;
 | 
			
		||||
import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.FILE_PLAN_ALIAS;
 | 
			
		||||
import static org.alfresco.rest.rm.community.util.CommonTestUtils.generateTestPrefix;
 | 
			
		||||
import static org.alfresco.rest.rm.community.utils.RMSiteUtil.FILE_PLAN_PATH;
 | 
			
		||||
import static org.alfresco.utility.Utility.buildPath;
 | 
			
		||||
import static org.alfresco.utility.Utility.removeLastSlash;
 | 
			
		||||
import static org.alfresco.utility.data.RandomData.getRandomName;
 | 
			
		||||
import static org.alfresco.utility.report.log.Step.STEP;
 | 
			
		||||
import static org.apache.commons.httpclient.HttpStatus.SC_INTERNAL_SERVER_ERROR;
 | 
			
		||||
import static org.springframework.http.HttpStatus.FORBIDDEN;
 | 
			
		||||
import static org.testng.AssertJUnit.assertEquals;
 | 
			
		||||
import static org.testng.AssertJUnit.assertTrue;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import com.google.common.collect.ImmutableMap;
 | 
			
		||||
@@ -50,12 +50,13 @@ import com.google.common.collect.ImmutableMap;
 | 
			
		||||
import org.alfresco.dataprep.CMISUtil;
 | 
			
		||||
import org.alfresco.rest.rm.community.base.BaseRMRestTest;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.audit.AuditEntry;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.Hold;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.HoldChild;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.record.Record;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.recordcategory.RecordCategory;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.recordcategory.RecordCategoryChild;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.user.UserPermissions;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.user.UserRoles;
 | 
			
		||||
import org.alfresco.rest.v0.HoldsAPI;
 | 
			
		||||
import org.alfresco.rest.v0.service.RMAuditService;
 | 
			
		||||
import org.alfresco.rest.v0.service.RoleService;
 | 
			
		||||
import org.alfresco.test.AlfrescoTest;
 | 
			
		||||
@@ -85,8 +86,6 @@ public class AuditAddToHoldTests extends BaseRMRestTest
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private RMAuditService rmAuditService;
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private HoldsAPI holdsAPI;
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private RoleService roleService;
 | 
			
		||||
 | 
			
		||||
    private UserModel rmAdmin, rmManagerNoReadOnHold, rmManagerNoReadOnNode;
 | 
			
		||||
@@ -94,17 +93,22 @@ public class AuditAddToHoldTests extends BaseRMRestTest
 | 
			
		||||
    private RecordCategory recordCategory;
 | 
			
		||||
    private RecordCategoryChild recordFolder;
 | 
			
		||||
    private List<AuditEntry> auditEntries;
 | 
			
		||||
    private final List<String> holdsList = asList(HOLD1, HOLD2);
 | 
			
		||||
    private List<String> holdsListRef = new ArrayList<>();
 | 
			
		||||
    private String hold1NodeRef;
 | 
			
		||||
    private String hold2NodeRef;
 | 
			
		||||
 | 
			
		||||
    @BeforeClass (alwaysRun = true)
 | 
			
		||||
    public void preconditionForAuditAddToHoldTests()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Create 2 holds.");
 | 
			
		||||
        hold1NodeRef = holdsAPI.createHoldAndGetNodeRef(getAdminUser().getUsername(),
 | 
			
		||||
                getAdminUser().getPassword(), HOLD1, HOLD_REASON, HOLD_DESCRIPTION);
 | 
			
		||||
        String hold2NodeRef = holdsAPI.createHoldAndGetNodeRef(getAdminUser().getUsername(), getAdminUser().getPassword(), HOLD2, HOLD_REASON, HOLD_DESCRIPTION);
 | 
			
		||||
        hold1NodeRef = getRestAPIFactory()
 | 
			
		||||
            .getFilePlansAPI(rmAdmin)
 | 
			
		||||
            .createHold(Hold.builder().name(HOLD1).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), FILE_PLAN_ALIAS)
 | 
			
		||||
            .getId();
 | 
			
		||||
        hold2NodeRef = getRestAPIFactory()
 | 
			
		||||
            .getFilePlansAPI(rmAdmin)
 | 
			
		||||
            .createHold(Hold.builder().name(HOLD2).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), FILE_PLAN_ALIAS)
 | 
			
		||||
            .getId();
 | 
			
		||||
        holdsListRef = asList(hold1NodeRef, hold2NodeRef);
 | 
			
		||||
 | 
			
		||||
        STEP("Create a new record category with a record folder.");
 | 
			
		||||
@@ -169,7 +173,8 @@ public class AuditAddToHoldTests extends BaseRMRestTest
 | 
			
		||||
        rmAuditService.clearAuditLog();
 | 
			
		||||
 | 
			
		||||
        STEP("Add node to hold.");
 | 
			
		||||
        holdsAPI.addItemToHold(rmAdmin.getUsername(), rmAdmin.getPassword(), nodeId, HOLD1);
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(rmAdmin).addChildToHold(HoldChild.builder().id(nodeId).build(), hold1NodeRef);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        STEP("Check the audit log contains the entry for the add to hold event.");
 | 
			
		||||
        rmAuditService.checkAuditLogForEvent(getAdminUser(), ADD_TO_HOLD, rmAdmin, nodeName, nodePath,
 | 
			
		||||
@@ -191,9 +196,8 @@ public class AuditAddToHoldTests extends BaseRMRestTest
 | 
			
		||||
        rmAuditService.clearAuditLog();
 | 
			
		||||
 | 
			
		||||
        STEP("Try to add the record to a hold by an user with no rights.");
 | 
			
		||||
        holdsAPI.addItemsToHolds(rmManagerNoReadOnHold.getUsername(), rmManagerNoReadOnHold.getPassword(),
 | 
			
		||||
                SC_INTERNAL_SERVER_ERROR, Collections.singletonList(recordToBeAdded.getId()),
 | 
			
		||||
                Collections.singletonList(hold1NodeRef));
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(rmManagerNoReadOnHold).addChildToHold(HoldChild.builder().id(recordToBeAdded.getId()).build(), hold1NodeRef);
 | 
			
		||||
        assertStatusCode(FORBIDDEN);
 | 
			
		||||
 | 
			
		||||
        STEP("Check the audit log doesn't contain the entry for the unsuccessful add to hold.");
 | 
			
		||||
        assertTrue("The list of events should not contain Add to Hold entry ",
 | 
			
		||||
@@ -215,7 +219,7 @@ public class AuditAddToHoldTests extends BaseRMRestTest
 | 
			
		||||
        rmAuditService.clearAuditLog();
 | 
			
		||||
 | 
			
		||||
        STEP("Add record folder to hold.");
 | 
			
		||||
        holdsAPI.addItemToHold(rmAdmin.getUsername(), rmAdmin.getPassword(), notEmptyRecFolder.getId(), HOLD1);
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(rmAdmin).addChildToHold(HoldChild.builder().id(notEmptyRecFolder.getId()).build(), hold1NodeRef);
 | 
			
		||||
 | 
			
		||||
        auditEntries = rmAuditService.getAuditEntriesFilteredByEvent(getAdminUser(), ADD_TO_HOLD);
 | 
			
		||||
 | 
			
		||||
@@ -239,8 +243,9 @@ public class AuditAddToHoldTests extends BaseRMRestTest
 | 
			
		||||
        rmAuditService.clearAuditLog();
 | 
			
		||||
 | 
			
		||||
        STEP("Add record to multiple holds.");
 | 
			
		||||
        holdsAPI.addItemsToHolds(rmAdmin.getUsername(), rmAdmin.getPassword(),
 | 
			
		||||
                Collections.singletonList(recordToBeAdded.getId()), holdsList);
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(rmAdmin).addChildToHold(HoldChild.builder().id(recordToBeAdded.getId()).build(), hold1NodeRef);
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(rmAdmin).addChildToHold(HoldChild.builder().id(recordToBeAdded.getId()).build(), hold2NodeRef);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        auditEntries = rmAuditService.getAuditEntriesFilteredByEvent(getAdminUser(), ADD_TO_HOLD);
 | 
			
		||||
 | 
			
		||||
@@ -268,7 +273,7 @@ public class AuditAddToHoldTests extends BaseRMRestTest
 | 
			
		||||
        rmAuditService.clearAuditLog();
 | 
			
		||||
 | 
			
		||||
        STEP("Add file to hold.");
 | 
			
		||||
        holdsAPI.addItemToHold(rmAdmin.getUsername(), rmAdmin.getPassword(), contentToBeAdded.getNodeRefWithoutVersion(), HOLD1);
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(rmAdmin).addChildToHold(HoldChild.builder().id(contentToBeAdded.getNodeRefWithoutVersion()).build(), hold1NodeRef);
 | 
			
		||||
 | 
			
		||||
        STEP("Check that an user with no Read permissions can't see the entry for the add to hold event.");
 | 
			
		||||
        assertTrue("The list of events should not contain Add to Hold entry ",
 | 
			
		||||
@@ -289,7 +294,7 @@ public class AuditAddToHoldTests extends BaseRMRestTest
 | 
			
		||||
        rmAuditService.clearAuditLog();
 | 
			
		||||
 | 
			
		||||
        STEP("Add file to hold.");
 | 
			
		||||
        holdsAPI.addItemToHold(rmAdmin.getUsername(), rmAdmin.getPassword(), contentToBeAdded.getNodeRefWithoutVersion(), HOLD1);
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(rmAdmin).addChildToHold(HoldChild.builder().id(contentToBeAdded.getNodeRefWithoutVersion()).build(), hold1NodeRef);
 | 
			
		||||
 | 
			
		||||
        auditEntries = rmAuditService.getAuditEntriesFilteredByEvent(rmManagerNoReadOnHold, ADD_TO_HOLD);
 | 
			
		||||
 | 
			
		||||
@@ -304,7 +309,8 @@ public class AuditAddToHoldTests extends BaseRMRestTest
 | 
			
		||||
    @AfterClass (alwaysRun = true)
 | 
			
		||||
    public void cleanUpAuditAddToHoldTests()
 | 
			
		||||
    {
 | 
			
		||||
        holdsListRef.forEach(holdRef -> holdsAPI.deleteHold(getAdminUser(), holdRef));
 | 
			
		||||
        holdsListRef.forEach(holdRef -> getRestAPIFactory().getHoldsAPI(getAdminUser()).deleteHold(holdRef));
 | 
			
		||||
 | 
			
		||||
        dataSite.usingAdmin().deleteSite(privateSite);
 | 
			
		||||
        asList(rmAdmin, rmManagerNoReadOnHold, rmManagerNoReadOnNode).forEach(user -> getDataUser().usingAdmin().deleteUser(user));
 | 
			
		||||
        deleteRecordCategory(recordCategory.getId());
 | 
			
		||||
 
 | 
			
		||||
@@ -31,9 +31,10 @@ import static java.util.Arrays.asList;
 | 
			
		||||
import static org.alfresco.rest.rm.community.base.TestData.HOLD_DESCRIPTION;
 | 
			
		||||
import static org.alfresco.rest.rm.community.base.TestData.HOLD_REASON;
 | 
			
		||||
import static org.alfresco.rest.rm.community.model.audit.AuditEvents.CREATE_HOLD;
 | 
			
		||||
import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.FILE_PLAN_ALIAS;
 | 
			
		||||
import static org.alfresco.rest.rm.community.util.CommonTestUtils.generateTestPrefix;
 | 
			
		||||
import static org.alfresco.utility.report.log.Step.STEP;
 | 
			
		||||
import static org.apache.commons.httpclient.HttpStatus.SC_INTERNAL_SERVER_ERROR;
 | 
			
		||||
import static org.springframework.http.HttpStatus.CONFLICT;
 | 
			
		||||
import static org.testng.AssertJUnit.assertEquals;
 | 
			
		||||
import static org.testng.AssertJUnit.assertTrue;
 | 
			
		||||
 | 
			
		||||
@@ -44,8 +45,8 @@ import com.google.common.collect.ImmutableMap;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.rest.rm.community.base.BaseRMRestTest;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.audit.AuditEntry;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.Hold;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.user.UserRoles;
 | 
			
		||||
import org.alfresco.rest.v0.HoldsAPI;
 | 
			
		||||
import org.alfresco.rest.v0.service.RMAuditService;
 | 
			
		||||
import org.alfresco.rest.v0.service.RoleService;
 | 
			
		||||
import org.alfresco.test.AlfrescoTest;
 | 
			
		||||
@@ -73,8 +74,6 @@ public class AuditCreateHoldTests extends BaseRMRestTest
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private RMAuditService rmAuditService;
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private HoldsAPI holdsAPI;
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private RoleService roleService;
 | 
			
		||||
 | 
			
		||||
    private UserModel rmAdmin, rmManager;
 | 
			
		||||
@@ -102,8 +101,10 @@ public class AuditCreateHoldTests extends BaseRMRestTest
 | 
			
		||||
        rmAuditService.clearAuditLog();
 | 
			
		||||
 | 
			
		||||
        STEP("Create a new hold.");
 | 
			
		||||
        String hold1NodeRef = holdsAPI.createHoldAndGetNodeRef(rmAdmin.getUsername(), rmAdmin.getPassword(), HOLD1,
 | 
			
		||||
                HOLD_REASON, HOLD_DESCRIPTION);
 | 
			
		||||
        String hold1NodeRef = getRestAPIFactory()
 | 
			
		||||
            .getFilePlansAPI(rmAdmin)
 | 
			
		||||
            .createHold(Hold.builder().name(HOLD1).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), FILE_PLAN_ALIAS)
 | 
			
		||||
            .getId();
 | 
			
		||||
        holdsListRef.add(hold1NodeRef);
 | 
			
		||||
        STEP("Check the audit log contains the entry for the created hold with the hold details.");
 | 
			
		||||
        rmAuditService.checkAuditLogForEvent(getAdminUser(), CREATE_HOLD, rmAdmin, HOLD1,
 | 
			
		||||
@@ -120,13 +121,18 @@ public class AuditCreateHoldTests extends BaseRMRestTest
 | 
			
		||||
    public void createHoldEventIsNotAuditedForExistingHold()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Create a new hold.");
 | 
			
		||||
        String hold2NodeRef = holdsAPI.createHoldAndGetNodeRef(rmAdmin.getUsername(), rmAdmin.getPassword(), HOLD2, HOLD_REASON, HOLD_DESCRIPTION);
 | 
			
		||||
        String hold2NodeRef = getRestAPIFactory()
 | 
			
		||||
            .getFilePlansAPI(rmAdmin)
 | 
			
		||||
            .createHold(Hold.builder().name(HOLD2).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), FILE_PLAN_ALIAS)
 | 
			
		||||
            .getId();
 | 
			
		||||
        holdsListRef.add(hold2NodeRef);
 | 
			
		||||
        rmAuditService.clearAuditLog();
 | 
			
		||||
 | 
			
		||||
        STEP("Try to create again the same hold and expect action to fail.");
 | 
			
		||||
        holdsAPI.createHold(rmAdmin.getUsername(), rmAdmin.getPassword(), HOLD2, HOLD_REASON, HOLD_DESCRIPTION,
 | 
			
		||||
                SC_INTERNAL_SERVER_ERROR);
 | 
			
		||||
        getRestAPIFactory()
 | 
			
		||||
            .getFilePlansAPI(rmAdmin)
 | 
			
		||||
            .createHold(Hold.builder().name(HOLD2).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), FILE_PLAN_ALIAS);
 | 
			
		||||
        assertStatusCode(CONFLICT);
 | 
			
		||||
 | 
			
		||||
        STEP("Check the audit log doesn't contain the entry for the second create hold event.");
 | 
			
		||||
        assertTrue("The list of events should not contain Create Hold entry ",
 | 
			
		||||
@@ -145,13 +151,17 @@ public class AuditCreateHoldTests extends BaseRMRestTest
 | 
			
		||||
        rmAuditService.clearAuditLog();
 | 
			
		||||
 | 
			
		||||
        STEP("Create a new hold.");
 | 
			
		||||
        holdsAPI.createHold(rmAdmin.getUsername(), rmAdmin.getPassword(), holdName, HOLD_REASON, HOLD_DESCRIPTION);
 | 
			
		||||
        String nodeRef = getRestAPIFactory()
 | 
			
		||||
            .getFilePlansAPI(rmAdmin)
 | 
			
		||||
            .createHold(Hold.builder().name(holdName).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), FILE_PLAN_ALIAS).getId();
 | 
			
		||||
 | 
			
		||||
        STEP("Get the list of audit entries for the create hold event.");
 | 
			
		||||
        List<AuditEntry> auditEntries = rmAuditService.getAuditEntriesFilteredByEvent(getAdminUser(), CREATE_HOLD);
 | 
			
		||||
 | 
			
		||||
        STEP("Delete the created hold.");
 | 
			
		||||
        holdsAPI.deleteHold(rmAdmin.getUsername(), rmAdmin.getPassword(), holdName);
 | 
			
		||||
        getRestAPIFactory()
 | 
			
		||||
            .getHoldsAPI(rmAdmin)
 | 
			
		||||
            .deleteHold(nodeRef);
 | 
			
		||||
 | 
			
		||||
        STEP("Get again the list of audit entries for the create hold event.");
 | 
			
		||||
        List<AuditEntry> auditEntriesAfterDelete = rmAuditService.getAuditEntriesFilteredByEvent(getAdminUser(), CREATE_HOLD);
 | 
			
		||||
@@ -171,8 +181,10 @@ public class AuditCreateHoldTests extends BaseRMRestTest
 | 
			
		||||
        rmAuditService.clearAuditLog();
 | 
			
		||||
 | 
			
		||||
        STEP("Create a new hold.");
 | 
			
		||||
        String hold3NodeRef = holdsAPI.createHoldAndGetNodeRef(rmAdmin.getUsername(), rmAdmin.getPassword(), HOLD3,
 | 
			
		||||
                HOLD_REASON, HOLD_DESCRIPTION);
 | 
			
		||||
        String hold3NodeRef = getRestAPIFactory()
 | 
			
		||||
            .getFilePlansAPI(rmAdmin)
 | 
			
		||||
            .createHold(Hold.builder().name(HOLD3).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), FILE_PLAN_ALIAS).getId();
 | 
			
		||||
 | 
			
		||||
        holdsListRef.add(hold3NodeRef);
 | 
			
		||||
 | 
			
		||||
        STEP("Check that an user with no Read permissions over the hold can't see the entry for the create hold event");
 | 
			
		||||
@@ -183,7 +195,7 @@ public class AuditCreateHoldTests extends BaseRMRestTest
 | 
			
		||||
    @AfterClass (alwaysRun = true)
 | 
			
		||||
    public void cleanUpAuditCreateHoldTests()
 | 
			
		||||
    {
 | 
			
		||||
        holdsListRef.forEach(holdRef -> holdsAPI.deleteHold(getAdminUser(), holdRef));
 | 
			
		||||
        holdsListRef.forEach(holdRef -> getRestAPIFactory().getHoldsAPI(rmAdmin).deleteHold(holdRef));
 | 
			
		||||
        asList(rmAdmin, rmManager).forEach(user -> getDataUser().usingAdmin().deleteUser(user));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -31,18 +31,20 @@ import static java.util.Arrays.asList;
 | 
			
		||||
import static org.alfresco.rest.rm.community.base.TestData.HOLD_DESCRIPTION;
 | 
			
		||||
import static org.alfresco.rest.rm.community.base.TestData.HOLD_REASON;
 | 
			
		||||
import static org.alfresco.rest.rm.community.model.audit.AuditEvents.DELETE_HOLD;
 | 
			
		||||
import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.FILE_PLAN_ALIAS;
 | 
			
		||||
import static org.alfresco.rest.rm.community.util.CommonTestUtils.generateTestPrefix;
 | 
			
		||||
import static org.alfresco.utility.report.log.Step.STEP;
 | 
			
		||||
import static org.apache.commons.httpclient.HttpStatus.SC_INTERNAL_SERVER_ERROR;
 | 
			
		||||
import static org.springframework.http.HttpStatus.FORBIDDEN;
 | 
			
		||||
import static org.testng.AssertJUnit.assertTrue;
 | 
			
		||||
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import com.google.common.collect.ImmutableMap;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.rest.rm.community.base.BaseRMRestTest;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.Hold;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.HoldDeletionReason;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.user.UserRoles;
 | 
			
		||||
import org.alfresco.rest.v0.HoldsAPI;
 | 
			
		||||
import org.alfresco.rest.v0.service.RMAuditService;
 | 
			
		||||
import org.alfresco.rest.v0.service.RoleService;
 | 
			
		||||
import org.alfresco.test.AlfrescoTest;
 | 
			
		||||
@@ -62,14 +64,13 @@ import org.testng.annotations.Test;
 | 
			
		||||
public class AuditDeleteHoldTests extends BaseRMRestTest
 | 
			
		||||
{
 | 
			
		||||
    private final String PREFIX = generateTestPrefix(AuditDeleteHoldTests.class);
 | 
			
		||||
    private final String HOLD = PREFIX + "holdToBeDeleted";
 | 
			
		||||
    private final String HOLD2 = PREFIX + "deleteHold";
 | 
			
		||||
    private final String hold = PREFIX + "holdToBeDeleted";
 | 
			
		||||
    private final String hold2 = PREFIX + "deleteHold";
 | 
			
		||||
    private final String hold3 = PREFIX + "deleteHoldWithReason";
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private RMAuditService rmAuditService;
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private HoldsAPI holdsAPI;
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private RoleService roleService;
 | 
			
		||||
 | 
			
		||||
    private UserModel rmAdmin, rmManager;
 | 
			
		||||
@@ -79,8 +80,10 @@ public class AuditDeleteHoldTests extends BaseRMRestTest
 | 
			
		||||
    public void preconditionForAuditDeleteHoldTests()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Create a new hold.");
 | 
			
		||||
        holdNodeRef = holdsAPI.createHoldAndGetNodeRef(getAdminUser().getUsername(), getAdminUser().getPassword(), HOLD,
 | 
			
		||||
                HOLD_REASON, HOLD_DESCRIPTION);
 | 
			
		||||
        holdNodeRef = getRestAPIFactory()
 | 
			
		||||
            .getFilePlansAPI(rmAdmin)
 | 
			
		||||
            .createHold(Hold.builder().name(hold).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), FILE_PLAN_ALIAS)
 | 
			
		||||
            .getId();
 | 
			
		||||
 | 
			
		||||
        STEP("Create 2 users with different permissions for the created hold.");
 | 
			
		||||
        rmAdmin = roleService.createUserWithRMRole(UserRoles.ROLE_RM_ADMIN.roleId);
 | 
			
		||||
@@ -99,17 +102,51 @@ public class AuditDeleteHoldTests extends BaseRMRestTest
 | 
			
		||||
    public void deleteHoldEventIsAudited()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Create a new hold.");
 | 
			
		||||
        String holdRef = holdsAPI.createHoldAndGetNodeRef(rmAdmin.getUsername(), rmAdmin.getPassword(), HOLD2,
 | 
			
		||||
                HOLD_REASON, HOLD_DESCRIPTION);
 | 
			
		||||
        String holdRef = getRestAPIFactory()
 | 
			
		||||
            .getFilePlansAPI(rmAdmin)
 | 
			
		||||
            .createHold(Hold.builder().name(hold2).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), FILE_PLAN_ALIAS)
 | 
			
		||||
            .getId();
 | 
			
		||||
 | 
			
		||||
        rmAuditService.clearAuditLog();
 | 
			
		||||
 | 
			
		||||
        STEP("Delete the created hold.");
 | 
			
		||||
        holdsAPI.deleteHold(rmAdmin, holdRef);
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(rmAdmin).deleteHold(holdRef);
 | 
			
		||||
 | 
			
		||||
        STEP("Check the audit log contains the entry for the deleted hold with the hold details.");
 | 
			
		||||
        rmAuditService.checkAuditLogForEvent(getAdminUser(), DELETE_HOLD, rmAdmin, HOLD2,
 | 
			
		||||
                Collections.singletonList(ImmutableMap.of("new", "", "previous", HOLD2, "name", "Hold Name")));
 | 
			
		||||
        rmAuditService.checkAuditLogForEvent(getAdminUser(), DELETE_HOLD, rmAdmin, hold2,
 | 
			
		||||
                List.of(ImmutableMap.of("new", "", "previous", hold2, "name", "Hold Name"),
 | 
			
		||||
                    ImmutableMap.of("new", "", "previous", "", "name", "Hold deletion reason")));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given a hold is deleted with a reason
 | 
			
		||||
     * When I view the audit log
 | 
			
		||||
     * Then an entry has been created in the audit log which contains the following:
 | 
			
		||||
     *      name of the hold
 | 
			
		||||
     *      hold deletion reason
 | 
			
		||||
     *      user who deleted the hold
 | 
			
		||||
     *      date the delete occurred
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void deleteHoldWithReasonEventIsAudited()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Create a new hold.");
 | 
			
		||||
        String holdRef = getRestAPIFactory()
 | 
			
		||||
            .getFilePlansAPI(rmAdmin)
 | 
			
		||||
            .createHold(Hold.builder().name(hold3).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), FILE_PLAN_ALIAS)
 | 
			
		||||
            .getId();
 | 
			
		||||
 | 
			
		||||
        String deletionReason = "Test reason";
 | 
			
		||||
 | 
			
		||||
        rmAuditService.clearAuditLog();
 | 
			
		||||
 | 
			
		||||
        STEP("Delete the created hold with a reason.");
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(rmAdmin).deleteHoldWithReason(HoldDeletionReason.builder().reason(deletionReason).build(), holdRef);
 | 
			
		||||
 | 
			
		||||
        STEP("Check the audit log contains the entry for the deleted hold with the hold details.");
 | 
			
		||||
        rmAuditService.checkAuditLogForEvent(getAdminUser(), DELETE_HOLD, rmAdmin, hold3,
 | 
			
		||||
            List.of(ImmutableMap.of("new", "", "previous", hold3, "name", "Hold Name"),
 | 
			
		||||
                ImmutableMap.of("new", "", "previous", deletionReason, "name", "Hold deletion reason")));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -123,7 +160,8 @@ public class AuditDeleteHoldTests extends BaseRMRestTest
 | 
			
		||||
        rmAuditService.clearAuditLog();
 | 
			
		||||
 | 
			
		||||
        STEP("Try to delete a hold by an user with no Read permissions over the hold.");
 | 
			
		||||
        holdsAPI.deleteHold(rmManager.getUsername(), rmManager.getPassword(), holdNodeRef, SC_INTERNAL_SERVER_ERROR);
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(rmManager).deleteHold(holdNodeRef);
 | 
			
		||||
        assertStatusCode(FORBIDDEN);
 | 
			
		||||
 | 
			
		||||
        STEP("Check the audit log doesn't contain the entry for the unsuccessful delete hold.");
 | 
			
		||||
        assertTrue("The list of events should not contain Delete Hold entry ",
 | 
			
		||||
@@ -133,7 +171,7 @@ public class AuditDeleteHoldTests extends BaseRMRestTest
 | 
			
		||||
    @AfterClass (alwaysRun = true)
 | 
			
		||||
    public void cleanUpAuditDeleteHoldTests()
 | 
			
		||||
    {
 | 
			
		||||
        holdsAPI.deleteHold(getAdminUser(), holdNodeRef);
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(rmManager).deleteHold(holdNodeRef);
 | 
			
		||||
        asList(rmAdmin, rmManager).forEach(user -> getDataUser().usingAdmin().deleteUser(user));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -25,10 +25,14 @@
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.rm.community.audit;
 | 
			
		||||
 | 
			
		||||
import static java.util.Arrays.asList;
 | 
			
		||||
import static org.alfresco.rest.rm.community.base.TestData.*;
 | 
			
		||||
 | 
			
		||||
import static org.alfresco.rest.rm.community.base.TestData.HOLD_DESCRIPTION;
 | 
			
		||||
import static org.alfresco.rest.rm.community.base.TestData.HOLD_REASON;
 | 
			
		||||
import static org.alfresco.rest.rm.community.model.audit.AuditEvents.ADD_TO_HOLD;
 | 
			
		||||
import static org.alfresco.rest.rm.community.model.audit.AuditEvents.REMOVE_FROM_HOLD;
 | 
			
		||||
import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.FILE_PLAN_ALIAS;
 | 
			
		||||
import static org.alfresco.rest.rm.community.util.CommonTestUtils.generateTestPrefix;
 | 
			
		||||
import static org.alfresco.utility.data.RandomData.getRandomName;
 | 
			
		||||
import static org.alfresco.utility.report.log.Step.STEP;
 | 
			
		||||
@@ -37,20 +41,22 @@ import static org.hamcrest.MatcherAssert.assertThat;
 | 
			
		||||
import static org.hamcrest.Matchers.empty;
 | 
			
		||||
import static org.hamcrest.core.IsNot.not;
 | 
			
		||||
import static org.springframework.http.HttpStatus.CREATED;
 | 
			
		||||
import static org.testng.AssertJUnit.*;
 | 
			
		||||
import static org.testng.AssertJUnit.assertFalse;
 | 
			
		||||
import static org.testng.AssertJUnit.assertTrue;
 | 
			
		||||
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.dataprep.CMISUtil;
 | 
			
		||||
import org.alfresco.rest.rm.community.base.BaseRMRestTest;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.audit.AuditEntry;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.audit.AuditEvents;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.Hold;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.HoldChild;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.record.Record;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.recordcategory.RecordCategory;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.recordcategory.RecordCategoryChild;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.recordfolder.RecordFolder;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.user.UserRoles;
 | 
			
		||||
import org.alfresco.rest.v0.HoldsAPI;
 | 
			
		||||
import org.alfresco.rest.v0.service.RMAuditService;
 | 
			
		||||
import org.alfresco.rest.v0.service.RoleService;
 | 
			
		||||
import org.alfresco.utility.model.FileModel;
 | 
			
		||||
@@ -69,8 +75,6 @@ public class AuditHoldsTest extends BaseRMRestTest {
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private RMAuditService rmAuditService;
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private HoldsAPI holdsAPI;
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private RoleService roleService;
 | 
			
		||||
    private UserModel rmAdmin;
 | 
			
		||||
    private RecordCategory recordCategory;
 | 
			
		||||
@@ -85,8 +89,11 @@ public class AuditHoldsTest extends BaseRMRestTest {
 | 
			
		||||
        rmAdmin = roleService.createUserWithRMRole(UserRoles.ROLE_RM_ADMIN.roleId);
 | 
			
		||||
 | 
			
		||||
        STEP("Create a hold");
 | 
			
		||||
        hold1NodeRef = holdsAPI.createHoldAndGetNodeRef(rmAdmin.getUsername(), rmAdmin.getPassword(), HOLD1, HOLD_REASON,
 | 
			
		||||
            HOLD_DESCRIPTION);
 | 
			
		||||
 | 
			
		||||
        hold1NodeRef = getRestAPIFactory()
 | 
			
		||||
                .getFilePlansAPI(rmAdmin)
 | 
			
		||||
                .createHold(Hold.builder().name(HOLD1).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), FILE_PLAN_ALIAS)
 | 
			
		||||
                .getId();
 | 
			
		||||
 | 
			
		||||
        STEP("Create a collaboration site with a test file.");
 | 
			
		||||
        publicSite = dataSite.usingAdmin().createPublicRandomSite();
 | 
			
		||||
@@ -101,9 +108,11 @@ public class AuditHoldsTest extends BaseRMRestTest {
 | 
			
		||||
 | 
			
		||||
        STEP("Add some items to the hold, then remove them from the hold");
 | 
			
		||||
        final List<String> itemsList = asList(testFile.getNodeRefWithoutVersion(), recordToBeAdded.getId(), recordFolder2.getId());
 | 
			
		||||
        final List<String> holdsList = Collections.singletonList(HOLD1);
 | 
			
		||||
        holdsAPI.addItemToHold(rmAdmin.getUsername(), rmAdmin.getPassword(), recordToBeAdded.getId(), HOLD1);
 | 
			
		||||
        holdsAPI.removeItemsFromHolds(rmAdmin.getUsername(), rmAdmin.getPassword(), itemsList, holdsList);
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(rmAdmin).addChildToHold(HoldChild.builder().id(recordToBeAdded.getId()).build(), hold1NodeRef);
 | 
			
		||||
        for(String childId : itemsList)
 | 
			
		||||
        {
 | 
			
		||||
            getRestAPIFactory().getHoldsAPI(rmAdmin).deleteHoldChild(hold1NodeRef, childId);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        STEP("Delete the record folder that was held");
 | 
			
		||||
        getRestAPIFactory().getRecordFolderAPI().deleteRecordFolder(recordFolder2.getId());
 | 
			
		||||
 
 | 
			
		||||
@@ -31,18 +31,18 @@ import static java.util.Arrays.asList;
 | 
			
		||||
import static org.alfresco.rest.rm.community.base.TestData.HOLD_DESCRIPTION;
 | 
			
		||||
import static org.alfresco.rest.rm.community.base.TestData.HOLD_REASON;
 | 
			
		||||
import static org.alfresco.rest.rm.community.model.audit.AuditEvents.REMOVE_FROM_HOLD;
 | 
			
		||||
import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.FILE_PLAN_ALIAS;
 | 
			
		||||
import static org.alfresco.rest.rm.community.util.CommonTestUtils.generateTestPrefix;
 | 
			
		||||
import static org.alfresco.rest.rm.community.utils.RMSiteUtil.FILE_PLAN_PATH;
 | 
			
		||||
import static org.alfresco.utility.Utility.buildPath;
 | 
			
		||||
import static org.alfresco.utility.Utility.removeLastSlash;
 | 
			
		||||
import static org.alfresco.utility.data.RandomData.getRandomName;
 | 
			
		||||
import static org.alfresco.utility.report.log.Step.STEP;
 | 
			
		||||
import static org.apache.commons.httpclient.HttpStatus.SC_INTERNAL_SERVER_ERROR;
 | 
			
		||||
import static org.springframework.http.HttpStatus.FORBIDDEN;
 | 
			
		||||
import static org.testng.AssertJUnit.assertEquals;
 | 
			
		||||
import static org.testng.AssertJUnit.assertTrue;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import com.google.common.collect.ImmutableMap;
 | 
			
		||||
@@ -50,12 +50,13 @@ import com.google.common.collect.ImmutableMap;
 | 
			
		||||
import org.alfresco.dataprep.CMISUtil;
 | 
			
		||||
import org.alfresco.rest.rm.community.base.BaseRMRestTest;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.audit.AuditEntry;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.Hold;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.HoldChild;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.record.Record;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.recordcategory.RecordCategory;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.recordcategory.RecordCategoryChild;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.user.UserPermissions;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.user.UserRoles;
 | 
			
		||||
import org.alfresco.rest.v0.HoldsAPI;
 | 
			
		||||
import org.alfresco.rest.v0.service.RMAuditService;
 | 
			
		||||
import org.alfresco.rest.v0.service.RoleService;
 | 
			
		||||
import org.alfresco.test.AlfrescoTest;
 | 
			
		||||
@@ -86,8 +87,6 @@ public class AuditRemoveFromHoldTests extends BaseRMRestTest
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private RMAuditService rmAuditService;
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private HoldsAPI holdsAPI;
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private RoleService roleService;
 | 
			
		||||
 | 
			
		||||
    private UserModel rmAdmin, rmManagerNoReadOnHold, rmManagerNoReadOnNode;
 | 
			
		||||
@@ -96,10 +95,11 @@ public class AuditRemoveFromHoldTests extends BaseRMRestTest
 | 
			
		||||
    private RecordCategoryChild recordFolder, heldRecordFolder;
 | 
			
		||||
    private Record heldRecord;
 | 
			
		||||
    private List<AuditEntry> auditEntries;
 | 
			
		||||
    private final List<String> holdsList = asList(HOLD1, HOLD2, HOLD3);
 | 
			
		||||
    private List<String> holdsListRef = new ArrayList<>();
 | 
			
		||||
    private FileModel heldContent;
 | 
			
		||||
    private String hold1NodeRef;
 | 
			
		||||
    private String hold2NodeRef;
 | 
			
		||||
    private String hold3NodeRef;
 | 
			
		||||
 | 
			
		||||
    @BeforeClass (alwaysRun = true)
 | 
			
		||||
    public void preconditionForAuditRemoveFromHoldTests()
 | 
			
		||||
@@ -111,10 +111,18 @@ public class AuditRemoveFromHoldTests extends BaseRMRestTest
 | 
			
		||||
        privateSite = dataSite.usingUser(rmAdmin).createPrivateRandomSite();
 | 
			
		||||
 | 
			
		||||
        STEP("Create new holds.");
 | 
			
		||||
        hold1NodeRef = holdsAPI.createHoldAndGetNodeRef(getAdminUser().getUsername(), getAdminUser().getPassword(),
 | 
			
		||||
                HOLD1, HOLD_REASON, HOLD_DESCRIPTION);
 | 
			
		||||
        String hold2NodeRef = holdsAPI.createHoldAndGetNodeRef(getAdminUser().getUsername(), getAdminUser().getPassword(), HOLD2, HOLD_REASON, HOLD_DESCRIPTION);
 | 
			
		||||
        String hold3NodeRef = holdsAPI.createHoldAndGetNodeRef(getAdminUser().getUsername(), getAdminUser().getPassword(), HOLD3, HOLD_REASON, HOLD_DESCRIPTION);
 | 
			
		||||
        hold1NodeRef = getRestAPIFactory()
 | 
			
		||||
            .getFilePlansAPI(rmAdmin)
 | 
			
		||||
            .createHold(Hold.builder().name(HOLD1).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), FILE_PLAN_ALIAS)
 | 
			
		||||
            .getId();
 | 
			
		||||
        hold2NodeRef = getRestAPIFactory()
 | 
			
		||||
            .getFilePlansAPI(rmAdmin)
 | 
			
		||||
            .createHold(Hold.builder().name(HOLD2).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), FILE_PLAN_ALIAS)
 | 
			
		||||
            .getId();
 | 
			
		||||
        hold3NodeRef = getRestAPIFactory()
 | 
			
		||||
            .getFilePlansAPI(rmAdmin)
 | 
			
		||||
            .createHold(Hold.builder().name(HOLD3).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), FILE_PLAN_ALIAS)
 | 
			
		||||
            .getId();
 | 
			
		||||
        holdsListRef = asList(hold1NodeRef, hold2NodeRef, hold3NodeRef);
 | 
			
		||||
 | 
			
		||||
        STEP("Create a new record category with a record folder.");
 | 
			
		||||
@@ -127,9 +135,12 @@ public class AuditRemoveFromHoldTests extends BaseRMRestTest
 | 
			
		||||
        heldRecordFolder = createRecordFolder(recordCategory.getId(), PREFIX + "heldRecFolder");
 | 
			
		||||
        heldRecord = createElectronicRecord(recordFolder.getId(), PREFIX + "record");
 | 
			
		||||
 | 
			
		||||
        holdsAPI.addItemsToHolds(getAdminUser().getUsername(), getAdminUser().getPassword(),
 | 
			
		||||
                asList(heldContent.getNodeRefWithoutVersion(), heldRecordFolder.getId(), heldRecord.getId()),
 | 
			
		||||
                holdsList);
 | 
			
		||||
        holdsListRef.forEach(holdRef ->
 | 
			
		||||
        {
 | 
			
		||||
            getRestAPIFactory().getHoldsAPI(rmAdmin).addChildToHold(HoldChild.builder().id(heldContent.getNodeRefWithoutVersion()).build(), holdRef);
 | 
			
		||||
            getRestAPIFactory().getHoldsAPI(rmAdmin).addChildToHold(HoldChild.builder().id(heldRecordFolder.getId()).build(), holdRef);
 | 
			
		||||
            getRestAPIFactory().getHoldsAPI(rmAdmin).addChildToHold(HoldChild.builder().id(heldRecord.getId()).build(), holdRef);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        STEP("Create users without rights to remove content from a hold.");
 | 
			
		||||
        rmManagerNoReadOnHold = roleService.createUserWithSiteRoleRMRoleAndPermission(privateSite,
 | 
			
		||||
@@ -179,7 +190,7 @@ public class AuditRemoveFromHoldTests extends BaseRMRestTest
 | 
			
		||||
        rmAuditService.clearAuditLog();
 | 
			
		||||
 | 
			
		||||
        STEP("Remove node from hold.");
 | 
			
		||||
        holdsAPI.removeItemFromHold(rmAdmin.getUsername(), rmAdmin.getPassword(), nodeId, HOLD3);
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(rmAdmin).deleteHoldChild(hold3NodeRef, nodeId);
 | 
			
		||||
 | 
			
		||||
        STEP("Check the audit log contains the entry for the remove from hold event.");
 | 
			
		||||
        rmAuditService.checkAuditLogForEvent(getAdminUser(), REMOVE_FROM_HOLD, rmAdmin, nodeName, nodePath,
 | 
			
		||||
@@ -198,9 +209,8 @@ public class AuditRemoveFromHoldTests extends BaseRMRestTest
 | 
			
		||||
        rmAuditService.clearAuditLog();
 | 
			
		||||
 | 
			
		||||
        STEP("Try to remove the record from a hold by an user with no rights.");
 | 
			
		||||
        holdsAPI.removeItemsFromHolds(rmManagerNoReadOnHold.getUsername(), rmManagerNoReadOnHold.getPassword(),
 | 
			
		||||
                SC_INTERNAL_SERVER_ERROR, Collections.singletonList(heldRecord.getId()),
 | 
			
		||||
                Collections.singletonList(hold1NodeRef));
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(rmManagerNoReadOnHold).deleteHoldChild(hold1NodeRef, heldRecord.getId());
 | 
			
		||||
        assertStatusCode(FORBIDDEN);
 | 
			
		||||
 | 
			
		||||
        STEP("Check the audit log doesn't contain the entry for the unsuccessful remove from hold.");
 | 
			
		||||
        assertTrue("The list of events should not contain remove from hold entry ",
 | 
			
		||||
@@ -220,12 +230,12 @@ public class AuditRemoveFromHoldTests extends BaseRMRestTest
 | 
			
		||||
        Record record = createElectronicRecord(notEmptyRecFolder.getId(), PREFIX + "record");
 | 
			
		||||
 | 
			
		||||
        STEP("Add the record folder to a hold.");
 | 
			
		||||
        holdsAPI.addItemToHold(rmAdmin.getUsername(), rmAdmin.getPassword(), notEmptyRecFolder.getId(), HOLD1);
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(rmAdmin).addChildToHold(HoldChild.builder().id(notEmptyRecFolder.getId()).build(), hold1NodeRef);
 | 
			
		||||
 | 
			
		||||
        rmAuditService.clearAuditLog();
 | 
			
		||||
 | 
			
		||||
        STEP("Remove record folder from hold.");
 | 
			
		||||
        holdsAPI.removeItemFromHold(rmAdmin.getUsername(), rmAdmin.getPassword(), notEmptyRecFolder.getId(), HOLD1);
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(rmAdmin).deleteHoldChild(hold1NodeRef, notEmptyRecFolder.getId());
 | 
			
		||||
 | 
			
		||||
        STEP("Get the list of audit entries for the remove from hold event.");
 | 
			
		||||
        auditEntries = rmAuditService.getAuditEntriesFilteredByEvent(getAdminUser(), REMOVE_FROM_HOLD);
 | 
			
		||||
@@ -247,8 +257,8 @@ public class AuditRemoveFromHoldTests extends BaseRMRestTest
 | 
			
		||||
        rmAuditService.clearAuditLog();
 | 
			
		||||
 | 
			
		||||
        STEP("Remove record folder from multiple holds.");
 | 
			
		||||
        holdsAPI.removeItemsFromHolds(rmAdmin.getUsername(), rmAdmin.getPassword(),
 | 
			
		||||
                Collections.singletonList(heldRecordFolder.getId()), asList(HOLD1, HOLD2));
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(rmAdmin).deleteHoldChild(hold1NodeRef, heldRecordFolder.getId());
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(rmAdmin).deleteHoldChild(hold2NodeRef, heldRecordFolder.getId());
 | 
			
		||||
 | 
			
		||||
        STEP("Get the list of audit entries for the remove from hold event.");
 | 
			
		||||
        auditEntries = rmAuditService.getAuditEntriesFilteredByEvent(getAdminUser(), REMOVE_FROM_HOLD);
 | 
			
		||||
@@ -275,12 +285,12 @@ public class AuditRemoveFromHoldTests extends BaseRMRestTest
 | 
			
		||||
        STEP("Add content to a hold.");
 | 
			
		||||
        FileModel heldFile = dataContent.usingAdmin().usingSite(privateSite)
 | 
			
		||||
                                           .createContent(CMISUtil.DocumentType.TEXT_PLAIN);
 | 
			
		||||
        holdsAPI.addItemToHold(rmAdmin.getUsername(), rmAdmin.getPassword(), heldFile.getNodeRefWithoutVersion(), HOLD1);
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(rmAdmin).addChildToHold(HoldChild.builder().id(heldFile.getNodeRefWithoutVersion()).build(), hold1NodeRef);
 | 
			
		||||
 | 
			
		||||
        rmAuditService.clearAuditLog();
 | 
			
		||||
 | 
			
		||||
        STEP("Remove held content from the hold.");
 | 
			
		||||
        holdsAPI.removeItemFromHold(rmAdmin.getUsername(), rmAdmin.getPassword(), heldFile.getNodeRefWithoutVersion(), HOLD1);
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(rmAdmin).deleteHoldChild(hold1NodeRef, heldFile.getNodeRefWithoutVersion());
 | 
			
		||||
 | 
			
		||||
        STEP("Check that an user with no Read permissions can't see the entry for the remove from hold event.");
 | 
			
		||||
        assertTrue("The list of events should not contain Remove from Hold entry ",
 | 
			
		||||
@@ -298,12 +308,12 @@ public class AuditRemoveFromHoldTests extends BaseRMRestTest
 | 
			
		||||
        STEP("Add content to a hold.");
 | 
			
		||||
        FileModel heldFile = dataContent.usingAdmin().usingSite(privateSite)
 | 
			
		||||
                                        .createContent(CMISUtil.DocumentType.TEXT_PLAIN);
 | 
			
		||||
        holdsAPI.addItemToHold(rmAdmin.getUsername(), rmAdmin.getPassword(), heldFile.getNodeRefWithoutVersion(), HOLD1);
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(rmAdmin).addChildToHold(HoldChild.builder().id(heldFile.getNodeRefWithoutVersion()).build(), hold1NodeRef);
 | 
			
		||||
 | 
			
		||||
        rmAuditService.clearAuditLog();
 | 
			
		||||
 | 
			
		||||
        STEP("Remove held content from the hold.");
 | 
			
		||||
        holdsAPI.removeItemFromHold(rmAdmin.getUsername(), rmAdmin.getPassword(), heldFile.getNodeRefWithoutVersion(), HOLD1);
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(rmAdmin).deleteHoldChild(hold1NodeRef, heldFile.getNodeRefWithoutVersion());
 | 
			
		||||
 | 
			
		||||
        auditEntries = rmAuditService.getAuditEntriesFilteredByEvent(rmManagerNoReadOnHold, REMOVE_FROM_HOLD);
 | 
			
		||||
 | 
			
		||||
@@ -318,7 +328,7 @@ public class AuditRemoveFromHoldTests extends BaseRMRestTest
 | 
			
		||||
    @AfterClass (alwaysRun = true)
 | 
			
		||||
    public void cleanUpAuditRemoveFromHoldTests()
 | 
			
		||||
    {
 | 
			
		||||
        holdsListRef.forEach(holdRef -> holdsAPI.deleteHold(getAdminUser(), holdRef));
 | 
			
		||||
        holdsListRef.forEach(holdRef -> getRestAPIFactory().getHoldsAPI(rmAdmin).deleteHold(holdRef));
 | 
			
		||||
        dataSite.usingAdmin().deleteSite(privateSite);
 | 
			
		||||
        asList(rmAdmin, rmManagerNoReadOnHold, rmManagerNoReadOnNode).forEach(user -> getDataUser().usingAdmin().deleteUser(user));
 | 
			
		||||
        deleteRecordCategory(recordCategory.getId());
 | 
			
		||||
 
 | 
			
		||||
@@ -60,12 +60,15 @@ import static org.testng.Assert.fail;
 | 
			
		||||
import static org.testng.AssertJUnit.assertEquals;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.NoSuchElementException;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.rest.rm.community.base.BaseRMRestTest;
 | 
			
		||||
import org.alfresco.rest.rm.community.base.DataProviderClass;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.fileplan.FilePlan;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.fileplan.FilePlanProperties;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.Hold;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.HoldCollection;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.recordcategory.RecordCategory;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.recordcategory.RecordCategoryCollection;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.recordcategory.RecordCategoryProperties;
 | 
			
		||||
@@ -514,5 +517,97 @@ public class FilePlanTests extends BaseRMRestTest
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * <pre>
 | 
			
		||||
     * Given that a file plan exists
 | 
			
		||||
     * When I ask the API to create a hold
 | 
			
		||||
     * Then it is created
 | 
			
		||||
     * </pre>
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void createHolds()
 | 
			
		||||
    {
 | 
			
		||||
        String holdName = "Hold" + getRandomAlphanumeric();
 | 
			
		||||
        String holdDescription = "Description" + getRandomAlphanumeric();
 | 
			
		||||
        String holdReason = "Reason" + getRandomAlphanumeric();
 | 
			
		||||
 | 
			
		||||
        // Create the hold
 | 
			
		||||
        Hold hold = Hold.builder()
 | 
			
		||||
            .name(holdName)
 | 
			
		||||
            .description(holdDescription)
 | 
			
		||||
            .reason(holdReason)
 | 
			
		||||
            .build();
 | 
			
		||||
        Hold createdHold = getRestAPIFactory().getFilePlansAPI()
 | 
			
		||||
            .createHold(hold, FILE_PLAN_ALIAS);
 | 
			
		||||
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(CREATED);
 | 
			
		||||
 | 
			
		||||
        assertEquals(createdHold.getName(), holdName);
 | 
			
		||||
        assertEquals(createdHold.getDescription(), holdDescription);
 | 
			
		||||
        assertEquals(createdHold.getReason(), holdReason);
 | 
			
		||||
        assertNotNull(createdHold.getId());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void listHolds()
 | 
			
		||||
    {
 | 
			
		||||
        // Delete all holds
 | 
			
		||||
        getRestAPIFactory().getFilePlansAPI().getHolds(FILE_PLAN_ALIAS).getEntries().forEach(holdEntry ->
 | 
			
		||||
            getRestAPIFactory().getHoldsAPI().deleteHold(holdEntry.getEntry().getId()));
 | 
			
		||||
 | 
			
		||||
        // Add holds
 | 
			
		||||
        List<Hold> filePlanHolds = new ArrayList<>();
 | 
			
		||||
        for (int i = 0; i < NUMBER_OF_CHILDREN; i++)
 | 
			
		||||
        {
 | 
			
		||||
            String holdName = "Hold name " + getRandomAlphanumeric();
 | 
			
		||||
            String holdDescription = "Hold Description " + getRandomAlphanumeric();
 | 
			
		||||
            String holdReason = "Reason " + getRandomAlphanumeric();
 | 
			
		||||
            // Create a hold
 | 
			
		||||
            Hold hold = Hold.builder()
 | 
			
		||||
                .name(holdName)
 | 
			
		||||
                .description(holdDescription)
 | 
			
		||||
                .reason(holdReason)
 | 
			
		||||
                .build();
 | 
			
		||||
            Hold createdHold = getRestAPIFactory().getFilePlansAPI()
 | 
			
		||||
                .createHold(hold, FILE_PLAN_ALIAS);
 | 
			
		||||
            assertNotNull(createdHold.getId());
 | 
			
		||||
            filePlanHolds.add(createdHold);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Get holds of a file plan
 | 
			
		||||
        HoldCollection holdCollection = getRestAPIFactory().getFilePlansAPI()
 | 
			
		||||
            .getHolds(FILE_PLAN_ALIAS);
 | 
			
		||||
 | 
			
		||||
        // Check status code
 | 
			
		||||
        assertStatusCode(OK);
 | 
			
		||||
 | 
			
		||||
        // Check holds against created list
 | 
			
		||||
        holdCollection.getEntries().forEach(c ->
 | 
			
		||||
            {
 | 
			
		||||
                Hold hold = c.getEntry();
 | 
			
		||||
                String holdId = hold.getId();
 | 
			
		||||
                assertNotNull(holdId);
 | 
			
		||||
                logger.info("Checking hold " + holdId);
 | 
			
		||||
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    // Find this hold in created holds list
 | 
			
		||||
                    Hold createdHold = filePlanHolds.stream()
 | 
			
		||||
                        .filter(child -> child.getId().equals(holdId))
 | 
			
		||||
                        .findFirst()
 | 
			
		||||
                        .orElseThrow();
 | 
			
		||||
 | 
			
		||||
                    assertEquals(createdHold.getName(), hold.getName());
 | 
			
		||||
                    assertEquals(createdHold.getDescription(), hold.getDescription());
 | 
			
		||||
                    assertEquals(createdHold.getReason(), hold.getReason());
 | 
			
		||||
                }
 | 
			
		||||
                catch (NoSuchElementException e)
 | 
			
		||||
                {
 | 
			
		||||
                    fail("No child element for " + hold);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
                                                   );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,614 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.rm.community.hold;
 | 
			
		||||
 | 
			
		||||
import static org.alfresco.rest.rm.community.base.TestData.HOLD_DESCRIPTION;
 | 
			
		||||
import static org.alfresco.rest.rm.community.base.TestData.HOLD_REASON;
 | 
			
		||||
import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.FILE_PLAN_ALIAS;
 | 
			
		||||
import static org.alfresco.rest.rm.community.model.user.UserPermissions.PERMISSION_FILING;
 | 
			
		||||
import static org.alfresco.rest.rm.community.model.user.UserPermissions.PERMISSION_READ_RECORDS;
 | 
			
		||||
import static org.alfresco.rest.rm.community.util.CommonTestUtils.generateTestPrefix;
 | 
			
		||||
import static org.alfresco.utility.report.log.Step.STEP;
 | 
			
		||||
import static org.awaitility.Awaitility.await;
 | 
			
		||||
import static org.junit.Assert.assertEquals;
 | 
			
		||||
import static org.junit.Assert.assertNotNull;
 | 
			
		||||
import static org.junit.Assert.assertTrue;
 | 
			
		||||
import static org.springframework.http.HttpStatus.ACCEPTED;
 | 
			
		||||
import static org.springframework.http.HttpStatus.BAD_REQUEST;
 | 
			
		||||
import static org.springframework.http.HttpStatus.FORBIDDEN;
 | 
			
		||||
import static org.springframework.http.HttpStatus.NOT_FOUND;
 | 
			
		||||
import static org.springframework.http.HttpStatus.OK;
 | 
			
		||||
import static org.springframework.http.HttpStatus.UNAUTHORIZED;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.dataprep.CMISUtil;
 | 
			
		||||
import org.alfresco.dataprep.ContentActions;
 | 
			
		||||
import org.alfresco.rest.rm.community.base.BaseRMRestTest;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.BulkBodyCancel;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.Hold;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.HoldBulkOperation;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.HoldBulkOperation.HoldBulkOperationType;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.HoldBulkOperationEntry;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.HoldBulkStatus;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.HoldBulkStatusCollection;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.HoldBulkStatusEntry;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.HoldChild;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.HoldChildEntry;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.user.UserRoles;
 | 
			
		||||
import org.alfresco.rest.search.RestRequestQueryModel;
 | 
			
		||||
import org.alfresco.rest.search.SearchRequest;
 | 
			
		||||
import org.alfresco.rest.v0.service.RoleService;
 | 
			
		||||
import org.alfresco.utility.constants.UserRole;
 | 
			
		||||
import org.alfresco.utility.model.FileModel;
 | 
			
		||||
import org.alfresco.utility.model.FolderModel;
 | 
			
		||||
import org.alfresco.utility.model.UserModel;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
			
		||||
import org.testng.annotations.AfterClass;
 | 
			
		||||
import org.testng.annotations.BeforeClass;
 | 
			
		||||
import org.testng.annotations.Test;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * API tests for adding items to holds via the bulk process
 | 
			
		||||
 */
 | 
			
		||||
public class AddToHoldsBulkV1Tests extends BaseRMRestTest
 | 
			
		||||
{
 | 
			
		||||
    private static final String ACCESS_DENIED_ERROR_MESSAGE = "Access Denied.  You do not have the appropriate " +
 | 
			
		||||
        "permissions to perform this operation.";
 | 
			
		||||
    private static final int NUMBER_OF_FILES = 5;
 | 
			
		||||
    private final List<FileModel> addedFiles = new ArrayList<>();
 | 
			
		||||
    private final List<UserModel> users = new ArrayList<>();
 | 
			
		||||
    private final List<Hold> holds = new ArrayList<>();
 | 
			
		||||
    private Hold hold;
 | 
			
		||||
    private Hold hold2;
 | 
			
		||||
    private Hold hold3;
 | 
			
		||||
    private FolderModel rootFolder;
 | 
			
		||||
    private HoldBulkOperation holdBulkOperation;
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private RoleService roleService;
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private ContentActions contentActions;
 | 
			
		||||
 | 
			
		||||
    @BeforeClass(alwaysRun = true)
 | 
			
		||||
    public void preconditionForAddContentToHold()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Create a hold.");
 | 
			
		||||
        hold = getRestAPIFactory().getFilePlansAPI(getAdminUser()).createHold(
 | 
			
		||||
            Hold.builder().name("HOLD" + generateTestPrefix(AddToHoldsV1Tests.class)).description(HOLD_DESCRIPTION)
 | 
			
		||||
                .reason(HOLD_REASON).build(), FILE_PLAN_ALIAS);
 | 
			
		||||
        holds.add(hold);
 | 
			
		||||
 | 
			
		||||
        STEP("Create test files.");
 | 
			
		||||
        testSite = dataSite.usingAdmin().createPublicRandomSite();
 | 
			
		||||
 | 
			
		||||
        rootFolder = dataContent.usingAdmin().usingSite(testSite).createFolder();
 | 
			
		||||
        FolderModel folder1 = dataContent.usingAdmin().usingResource(rootFolder).createFolder();
 | 
			
		||||
        FolderModel folder2 = dataContent.usingAdmin().usingResource(folder1).createFolder();
 | 
			
		||||
 | 
			
		||||
        // Add files to subfolders in the site
 | 
			
		||||
        for (int i = 0; i < NUMBER_OF_FILES; i++)
 | 
			
		||||
        {
 | 
			
		||||
            FileModel documentHeld = dataContent.usingAdmin()
 | 
			
		||||
                .usingResource(i % 2 == 0 ? folder1 : folder2)
 | 
			
		||||
                .createContent(CMISUtil.DocumentType.TEXT_PLAIN);
 | 
			
		||||
            addedFiles.add(documentHeld);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        RestRequestQueryModel queryReq = getContentFromSiteQuery(testSite.getId());
 | 
			
		||||
        SearchRequest searchRequest = new SearchRequest();
 | 
			
		||||
        searchRequest.setQuery(queryReq);
 | 
			
		||||
 | 
			
		||||
        STEP("Wait until all files are searchable.");
 | 
			
		||||
        await().atMost(30, TimeUnit.SECONDS)
 | 
			
		||||
            .until(() -> getRestAPIFactory().getSearchAPI(null).search(searchRequest).getPagination()
 | 
			
		||||
                .getTotalItems() == NUMBER_OF_FILES);
 | 
			
		||||
 | 
			
		||||
        holdBulkOperation = HoldBulkOperation.builder()
 | 
			
		||||
            .query(queryReq)
 | 
			
		||||
            .op(HoldBulkOperationType.ADD).build();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given a user with the add to hold capability and hold filing permission
 | 
			
		||||
     * When the user adds content from a site to a hold using the bulk API
 | 
			
		||||
     * Then the content is added to the hold and the status of the bulk operation is DONE
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void addContentFromTestSiteToHoldUsingBulkAPI()
 | 
			
		||||
    {
 | 
			
		||||
        UserModel userAddHoldPermission = roleService.createUserWithSiteRoleRMRoleAndPermission(testSite,
 | 
			
		||||
            UserRole.SiteCollaborator, hold.getId(), UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING);
 | 
			
		||||
        users.add(userAddHoldPermission);
 | 
			
		||||
 | 
			
		||||
        STEP("Add content from the site to the hold using the bulk API.");
 | 
			
		||||
        HoldBulkOperationEntry bulkOperationEntry = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
 | 
			
		||||
            .startBulkProcess(holdBulkOperation, hold.getId());
 | 
			
		||||
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(ACCEPTED);
 | 
			
		||||
        assertEquals(NUMBER_OF_FILES, bulkOperationEntry.getTotalItems());
 | 
			
		||||
 | 
			
		||||
        STEP("Wait until all files are added to the hold.");
 | 
			
		||||
        await().atMost(20, TimeUnit.SECONDS).until(
 | 
			
		||||
            () -> getRestAPIFactory().getHoldsAPI(getAdminUser()).getChildren(hold.getId()).getEntries().size()
 | 
			
		||||
                == NUMBER_OF_FILES);
 | 
			
		||||
        List<String> holdChildrenNodeRefs = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
 | 
			
		||||
            .getChildren(hold.getId()).getEntries().stream().map(HoldChildEntry::getEntry).map(
 | 
			
		||||
                HoldChild::getId).toList();
 | 
			
		||||
        assertEquals(addedFiles.stream().map(FileModel::getNodeRefWithoutVersion).sorted().toList(),
 | 
			
		||||
            holdChildrenNodeRefs.stream().sorted().toList());
 | 
			
		||||
 | 
			
		||||
        STEP("Check the bulk status.");
 | 
			
		||||
        HoldBulkStatus holdBulkStatus = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
 | 
			
		||||
            .getBulkStatus(hold.getId(), bulkOperationEntry.getBulkStatusId());
 | 
			
		||||
        assertBulkProcessStatus(holdBulkStatus, NUMBER_OF_FILES, 0, null, holdBulkOperation);
 | 
			
		||||
 | 
			
		||||
        STEP("Check the bulk statuses.");
 | 
			
		||||
        HoldBulkStatusCollection holdBulkStatusCollection = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
 | 
			
		||||
            .getBulkStatuses(hold.getId());
 | 
			
		||||
        assertEquals(Arrays.asList(holdBulkStatus),
 | 
			
		||||
            holdBulkStatusCollection.getEntries().stream().map(HoldBulkStatusEntry::getEntry).toList());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given a user with the add to hold capability and hold filing permission
 | 
			
		||||
     * When the user adds content from a folder and all subfolders to a hold using the bulk API
 | 
			
		||||
     * Then the content is added to the hold and the status of the bulk operation is DONE
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void addContentFromFolderAndAllSubfoldersToHoldUsingBulkAPI()
 | 
			
		||||
    {
 | 
			
		||||
        hold3 = getRestAPIFactory().getFilePlansAPI(getAdminUser()).createHold(
 | 
			
		||||
            Hold.builder().name("HOLD" + generateTestPrefix(AddToHoldsV1Tests.class)).description(HOLD_DESCRIPTION)
 | 
			
		||||
                .reason(HOLD_REASON).build(), FILE_PLAN_ALIAS);
 | 
			
		||||
        holds.add(hold3);
 | 
			
		||||
 | 
			
		||||
        UserModel userAddHoldPermission = roleService.createUserWithSiteRoleRMRoleAndPermission(testSite,
 | 
			
		||||
            UserRole.SiteCollaborator, hold3.getId(), UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING);
 | 
			
		||||
        users.add(userAddHoldPermission);
 | 
			
		||||
 | 
			
		||||
        STEP("Add content from the site to the hold using the bulk API.");
 | 
			
		||||
        // Get content from folder and all subfolders of the root folder
 | 
			
		||||
        HoldBulkOperation bulkOperation = HoldBulkOperation.builder()
 | 
			
		||||
            .query(getContentFromFolderAndAllSubfoldersQuery(rootFolder.getNodeRefWithoutVersion()))
 | 
			
		||||
            .op(HoldBulkOperationType.ADD).build();
 | 
			
		||||
        HoldBulkOperationEntry bulkOperationEntry = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
 | 
			
		||||
            .startBulkProcess(bulkOperation, hold3.getId());
 | 
			
		||||
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(ACCEPTED);
 | 
			
		||||
        assertEquals(NUMBER_OF_FILES, bulkOperationEntry.getTotalItems());
 | 
			
		||||
 | 
			
		||||
        STEP("Wait until all files are added to the hold.");
 | 
			
		||||
        await().atMost(20, TimeUnit.SECONDS).until(
 | 
			
		||||
            () -> getRestAPIFactory().getHoldsAPI(getAdminUser()).getChildren(hold3.getId()).getEntries().size()
 | 
			
		||||
                == NUMBER_OF_FILES);
 | 
			
		||||
        List<String> holdChildrenNodeRefs = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
 | 
			
		||||
            .getChildren(hold3.getId()).getEntries().stream().map(HoldChildEntry::getEntry).map(
 | 
			
		||||
                HoldChild::getId).toList();
 | 
			
		||||
        assertEquals(addedFiles.stream().map(FileModel::getNodeRefWithoutVersion).sorted().toList(),
 | 
			
		||||
            holdChildrenNodeRefs.stream().sorted().toList());
 | 
			
		||||
 | 
			
		||||
        STEP("Check the bulk status.");
 | 
			
		||||
        HoldBulkStatus holdBulkStatus = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
 | 
			
		||||
            .getBulkStatus(hold3.getId(), bulkOperationEntry.getBulkStatusId());
 | 
			
		||||
        assertBulkProcessStatus(holdBulkStatus, NUMBER_OF_FILES, 0, null, bulkOperation);
 | 
			
		||||
 | 
			
		||||
        STEP("Check the bulk statuses.");
 | 
			
		||||
        HoldBulkStatusCollection holdBulkStatusCollection = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
 | 
			
		||||
            .getBulkStatuses(hold3.getId());
 | 
			
		||||
        assertEquals(List.of(holdBulkStatus),
 | 
			
		||||
            holdBulkStatusCollection.getEntries().stream().map(HoldBulkStatusEntry::getEntry).toList());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given a user without the add to hold capability
 | 
			
		||||
     * When the user adds content from a site to a hold using the bulk API
 | 
			
		||||
     * Then the user receives access denied error
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testBulkProcessWithUserWithoutAddToHoldCapability()
 | 
			
		||||
    {
 | 
			
		||||
        UserModel userWithoutAddToHoldCapability = roleService.createUserWithSiteRoleRMRoleAndPermission(testSite,
 | 
			
		||||
            UserRole
 | 
			
		||||
                .SiteCollaborator,
 | 
			
		||||
            hold.getId(), UserRoles.ROLE_RM_POWER_USER, PERMISSION_FILING);
 | 
			
		||||
        users.add(userWithoutAddToHoldCapability);
 | 
			
		||||
 | 
			
		||||
        STEP("Add content from the site to the hold using the bulk API.");
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(userWithoutAddToHoldCapability)
 | 
			
		||||
            .startBulkProcess(holdBulkOperation, hold.getId());
 | 
			
		||||
 | 
			
		||||
        STEP("Verify the response status code and the error message.");
 | 
			
		||||
        assertStatusCode(FORBIDDEN);
 | 
			
		||||
        getRestAPIFactory().getRmRestWrapper().assertLastError().containsSummary(ACCESS_DENIED_ERROR_MESSAGE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given a user without the filing permission on a hold
 | 
			
		||||
     * When the user adds content from a site to a hold using the bulk API
 | 
			
		||||
     * Then the user receives access denied error
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testBulkProcessWithUserWithoutFilingPermissionOnAHold()
 | 
			
		||||
    {
 | 
			
		||||
        // User without filing permission on a hold
 | 
			
		||||
        UserModel userWithoutPermission = roleService.createUserWithSiteRoleRMRoleAndPermission(testSite,
 | 
			
		||||
            UserRole.SiteCollaborator, hold.getId(), UserRoles.ROLE_RM_MANAGER, PERMISSION_READ_RECORDS);
 | 
			
		||||
        users.add(userWithoutPermission);
 | 
			
		||||
 | 
			
		||||
        STEP("Add content from the site to the hold using the bulk API.");
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(userWithoutPermission)
 | 
			
		||||
            .startBulkProcess(holdBulkOperation, hold.getId());
 | 
			
		||||
 | 
			
		||||
        STEP("Verify the response status code and the error message.");
 | 
			
		||||
        assertStatusCode(FORBIDDEN);
 | 
			
		||||
        getRestAPIFactory().getRmRestWrapper().assertLastError().containsSummary(ACCESS_DENIED_ERROR_MESSAGE);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given a user without the write permission on all the content
 | 
			
		||||
     * When the user adds content from a site to a hold using the bulk API
 | 
			
		||||
     * Then all processed items are marked as errors and the last error message contains access denied error
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testBulkProcessWithUserWithoutWritePermissionOnTheContent()
 | 
			
		||||
    {
 | 
			
		||||
        // User without write permission on the content
 | 
			
		||||
        UserModel userWithoutPermission = roleService.createUserWithSiteRoleRMRoleAndPermission(
 | 
			
		||||
            testSite, UserRole.SiteConsumer,
 | 
			
		||||
            hold.getId(), UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING);
 | 
			
		||||
        users.add(userWithoutPermission);
 | 
			
		||||
 | 
			
		||||
        // Wait until permissions are reverted
 | 
			
		||||
        SearchRequest searchRequest = new SearchRequest();
 | 
			
		||||
        searchRequest.setQuery(holdBulkOperation.getQuery());
 | 
			
		||||
        await().atMost(30, TimeUnit.SECONDS)
 | 
			
		||||
            .until(() -> getRestAPIFactory().getSearchAPI(userWithoutPermission).search(searchRequest).getPagination()
 | 
			
		||||
                .getTotalItems() == NUMBER_OF_FILES);
 | 
			
		||||
 | 
			
		||||
        STEP("Add content from the site to the hold using the bulk API.");
 | 
			
		||||
        HoldBulkOperationEntry bulkOperationEntry = getRestAPIFactory().getHoldsAPI(
 | 
			
		||||
            userWithoutPermission).startBulkProcess(holdBulkOperation, hold.getId());
 | 
			
		||||
 | 
			
		||||
        STEP("Verify the response.");
 | 
			
		||||
        assertStatusCode(ACCEPTED);
 | 
			
		||||
 | 
			
		||||
        await().atMost(20, TimeUnit.SECONDS).until(() ->
 | 
			
		||||
            Objects.equals(getRestAPIFactory().getHoldsAPI(userWithoutPermission)
 | 
			
		||||
                .getBulkStatus(hold.getId(), bulkOperationEntry.getBulkStatusId()).getStatus(), "DONE"));
 | 
			
		||||
 | 
			
		||||
        HoldBulkStatus holdBulkStatus = getRestAPIFactory().getHoldsAPI(userWithoutPermission)
 | 
			
		||||
            .getBulkStatus(hold.getId(), bulkOperationEntry.getBulkStatusId());
 | 
			
		||||
        assertBulkProcessStatus(holdBulkStatus, NUMBER_OF_FILES, NUMBER_OF_FILES, ACCESS_DENIED_ERROR_MESSAGE,
 | 
			
		||||
            holdBulkOperation);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given a user without the write permission on one file
 | 
			
		||||
     * When the user adds content from a site to a hold using the bulk API
 | 
			
		||||
     * Then all processed items are added to the hold except the one that the user does not have write permission
 | 
			
		||||
     * And the status of the bulk operation is DONE, contains the error message and the number of errors is 1
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testBulkProcessWithUserWithoutWritePermissionOnOneFile()
 | 
			
		||||
    {
 | 
			
		||||
        hold2 = getRestAPIFactory().getFilePlansAPI(getAdminUser()).createHold(
 | 
			
		||||
            Hold.builder().name("HOLD" + generateTestPrefix(AddToHoldsV1Tests.class)).description(HOLD_DESCRIPTION)
 | 
			
		||||
                .reason(HOLD_REASON).build(), FILE_PLAN_ALIAS);
 | 
			
		||||
        holds.add(hold2);
 | 
			
		||||
 | 
			
		||||
        UserModel userAddHoldPermission = roleService.createUserWithSiteRoleRMRoleAndPermission(testSite,
 | 
			
		||||
            UserRole.SiteCollaborator, hold2.getId(), UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING);
 | 
			
		||||
        users.add(userAddHoldPermission);
 | 
			
		||||
 | 
			
		||||
        contentActions.setPermissionForUser(getAdminUser().getUsername(), getAdminUser().getPassword(),
 | 
			
		||||
            testSite.getId(), addedFiles.get(0).getName(), userAddHoldPermission.getUsername(),
 | 
			
		||||
            UserRole.SiteConsumer.getRoleId(), false);
 | 
			
		||||
 | 
			
		||||
        STEP("Add content from the site to the hold using the bulk API.");
 | 
			
		||||
        HoldBulkOperationEntry bulkOperationEntry = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
 | 
			
		||||
            .startBulkProcess(holdBulkOperation, hold2.getId());
 | 
			
		||||
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(ACCEPTED);
 | 
			
		||||
        assertEquals(NUMBER_OF_FILES, bulkOperationEntry.getTotalItems());
 | 
			
		||||
 | 
			
		||||
        STEP("Wait until all files are added to the hold.");
 | 
			
		||||
        await().atMost(30, TimeUnit.SECONDS).until(
 | 
			
		||||
            () -> getRestAPIFactory().getHoldsAPI(getAdminUser()).getChildren(hold2.getId()).getEntries().size()
 | 
			
		||||
                == NUMBER_OF_FILES - 1);
 | 
			
		||||
        await().atMost(30, TimeUnit.SECONDS).until(
 | 
			
		||||
            () -> getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
 | 
			
		||||
                .getBulkStatus(hold2.getId(), bulkOperationEntry.getBulkStatusId()).getProcessedItems()
 | 
			
		||||
                == NUMBER_OF_FILES);
 | 
			
		||||
        List<String> holdChildrenNodeRefs = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
 | 
			
		||||
            .getChildren(hold2.getId()).getEntries().stream().map(HoldChildEntry::getEntry).map(
 | 
			
		||||
                HoldChild::getId).toList();
 | 
			
		||||
        assertEquals(addedFiles.stream().skip(1).map(FileModel::getNodeRefWithoutVersion).sorted().toList(),
 | 
			
		||||
            holdChildrenNodeRefs.stream().sorted().toList());
 | 
			
		||||
 | 
			
		||||
        STEP("Check the bulk status.");
 | 
			
		||||
        HoldBulkStatus holdBulkStatus = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
 | 
			
		||||
            .getBulkStatus(hold2.getId(), bulkOperationEntry.getBulkStatusId());
 | 
			
		||||
        assertBulkProcessStatus(holdBulkStatus, NUMBER_OF_FILES, 1, ACCESS_DENIED_ERROR_MESSAGE, holdBulkOperation);
 | 
			
		||||
 | 
			
		||||
        STEP("Check the bulk statuses.");
 | 
			
		||||
        HoldBulkStatusCollection holdBulkStatusCollection = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
 | 
			
		||||
            .getBulkStatuses(hold2.getId());
 | 
			
		||||
        assertEquals(List.of(holdBulkStatus),
 | 
			
		||||
            holdBulkStatusCollection.getEntries().stream().map(HoldBulkStatusEntry::getEntry).toList());
 | 
			
		||||
 | 
			
		||||
        // Revert the permissions
 | 
			
		||||
        contentActions.setPermissionForUser(getAdminUser().getUsername(), getAdminUser().getPassword(),
 | 
			
		||||
            testSite.getId(), addedFiles.get(0).getName(), userAddHoldPermission.getUsername(),
 | 
			
		||||
            UserRole.SiteCollaborator.getRoleId(), true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given an unauthenticated user
 | 
			
		||||
     * When the user adds content from a site to a hold using the bulk API
 | 
			
		||||
     * Then the user receives unauthorized error
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testBulkProcessAsUnauthenticatedUser()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Start bulk process as unauthenticated user");
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(new UserModel(getAdminUser().getUsername(), "wrongPassword"))
 | 
			
		||||
            .startBulkProcess(holdBulkOperation, hold.getId());
 | 
			
		||||
 | 
			
		||||
        STEP("Verify the response status code.");
 | 
			
		||||
        assertStatusCode(UNAUTHORIZED);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given a user with the add to hold capability and hold filing permission
 | 
			
		||||
     * When the user adds content from a site to a hold using the bulk API
 | 
			
		||||
     * And the hold does not exist
 | 
			
		||||
     * Then the user receives not found error
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testBulkProcessForNonExistentHold()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Start bulk process for non existent hold");
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(getAdminUser()).startBulkProcess(holdBulkOperation, "nonExistentHoldId");
 | 
			
		||||
 | 
			
		||||
        STEP("Verify the response status code.");
 | 
			
		||||
        assertStatusCode(NOT_FOUND);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given a user with the add to hold capability and hold filing permission
 | 
			
		||||
     * When the user adds content from a site to a hold using the bulk API
 | 
			
		||||
     * and the bulk operation is invalid
 | 
			
		||||
     * Then the user receives bad request error
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testGetBulkStatusesForInvalidOperation()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Start bulk process for non existent hold");
 | 
			
		||||
 | 
			
		||||
        HoldBulkOperation invalidHoldBulkOperation = HoldBulkOperation.builder().op(null)
 | 
			
		||||
            .query(holdBulkOperation.getQuery()).build();
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(getAdminUser()).startBulkProcess(invalidHoldBulkOperation, hold.getId());
 | 
			
		||||
 | 
			
		||||
        STEP("Verify the response status code.");
 | 
			
		||||
        assertStatusCode(BAD_REQUEST);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given a user with the add to hold capability and hold filing permission
 | 
			
		||||
     * When the user adds content from a site to a hold using the bulk API
 | 
			
		||||
     * And the hold does not exist
 | 
			
		||||
     * Then the user receives not found error
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testGetBulkStatusForNonExistentHold()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Start bulk process for non existent hold");
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(getAdminUser()).getBulkStatus("nonExistentHoldId", "nonExistenBulkStatusId");
 | 
			
		||||
 | 
			
		||||
        STEP("Verify the response status code.");
 | 
			
		||||
        assertStatusCode(NOT_FOUND);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given a user with the add to hold capability and hold filing permission
 | 
			
		||||
     * When the user adds content from a site to a hold using the bulk API
 | 
			
		||||
     * And the bulk status does not exist
 | 
			
		||||
     * Then the user receives not found error
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testGetBulkStatusForNonExistentBulkStatus()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Start bulk process for non bulk status");
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(getAdminUser()).getBulkStatus(hold.getId(), "nonExistenBulkStatusId");
 | 
			
		||||
 | 
			
		||||
        STEP("Verify the response status code.");
 | 
			
		||||
        assertStatusCode(NOT_FOUND);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given a user with the add to hold capability and hold filing permission
 | 
			
		||||
     * When the user adds content from a site to a hold using the bulk API
 | 
			
		||||
     * And the hold does not exist
 | 
			
		||||
     * Then the user receives not found error
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testGetBulkStatusesForNonExistentHold()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Start bulk process for non existent hold");
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(getAdminUser()).getBulkStatuses("nonExistentHoldId");
 | 
			
		||||
 | 
			
		||||
        STEP("Verify the response status code.");
 | 
			
		||||
        assertStatusCode(NOT_FOUND);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given a user with the add to hold capability and hold filing permission
 | 
			
		||||
     * When the user adds content from all sites to a hold using the bulk API to exceed the limit (30 items)
 | 
			
		||||
     * Then the user receives bad request error
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testExceedingBulkOperationLimit()
 | 
			
		||||
    {
 | 
			
		||||
        RestRequestQueryModel queryReq = new RestRequestQueryModel();
 | 
			
		||||
        queryReq.setQuery("TYPE:content");
 | 
			
		||||
        queryReq.setLanguage("afts");
 | 
			
		||||
 | 
			
		||||
        HoldBulkOperation exceedLimitOp = HoldBulkOperation.builder()
 | 
			
		||||
            .query(queryReq)
 | 
			
		||||
            .op(HoldBulkOperationType.ADD).build();
 | 
			
		||||
 | 
			
		||||
        STEP("Start bulk process to exceed the limit");
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(getAdminUser()).startBulkProcess(exceedLimitOp, hold.getId());
 | 
			
		||||
 | 
			
		||||
        STEP("Verify the response status code.");
 | 
			
		||||
        assertStatusCode(BAD_REQUEST);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given a user with the add to hold capability and hold filing permission
 | 
			
		||||
     * When the user adds content from a site to a hold using the bulk API
 | 
			
		||||
     * And then the user cancels the bulk operation
 | 
			
		||||
     * Then the user receives OK status code
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testBulkProcessCancellationWithAllowedUser()
 | 
			
		||||
    {
 | 
			
		||||
        Hold hold4 = getRestAPIFactory().getFilePlansAPI(getAdminUser()).createHold(
 | 
			
		||||
            Hold.builder().name("HOLD" + generateTestPrefix(AddToHoldsV1Tests.class)).description(HOLD_DESCRIPTION)
 | 
			
		||||
                .reason(HOLD_REASON).build(), FILE_PLAN_ALIAS);
 | 
			
		||||
        holds.add(hold4);
 | 
			
		||||
 | 
			
		||||
        UserModel userAddHoldPermission = roleService.createUserWithSiteRoleRMRoleAndPermission(testSite,
 | 
			
		||||
            UserRole.SiteCollaborator, hold4.getId(), UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING);
 | 
			
		||||
        users.add(userAddHoldPermission);
 | 
			
		||||
 | 
			
		||||
        STEP("Add content from the site to the hold using the bulk API.");
 | 
			
		||||
        HoldBulkOperationEntry bulkOperationEntry = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
 | 
			
		||||
            .startBulkProcess(holdBulkOperation, hold4.getId());
 | 
			
		||||
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(ACCEPTED);
 | 
			
		||||
        assertEquals(NUMBER_OF_FILES, bulkOperationEntry.getTotalItems());
 | 
			
		||||
 | 
			
		||||
        STEP("Cancel the bulk operation.");
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
 | 
			
		||||
            .cancelBulkOperation(hold4.getId(), bulkOperationEntry.getBulkStatusId(), new BulkBodyCancel());
 | 
			
		||||
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(OK);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given a user with the add to hold capability and hold filing permission
 | 
			
		||||
     * When the user adds content from a site to a hold using the bulk API
 | 
			
		||||
     * And a 2nd user without the add to hold capability cancels the bulk operation
 | 
			
		||||
     * Then the 2nd user receives access denied error
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testBulkProcessCancellationWithUserWithoutAddToHoldCapability()
 | 
			
		||||
    {
 | 
			
		||||
        Hold hold5 = getRestAPIFactory().getFilePlansAPI(getAdminUser()).createHold(
 | 
			
		||||
            Hold.builder().name("HOLD" + generateTestPrefix(AddToHoldsV1Tests.class)).description(HOLD_DESCRIPTION)
 | 
			
		||||
                .reason(HOLD_REASON).build(), FILE_PLAN_ALIAS);
 | 
			
		||||
        holds.add(hold5);
 | 
			
		||||
 | 
			
		||||
        UserModel userAddHoldPermission = roleService.createUserWithSiteRoleRMRoleAndPermission(testSite,
 | 
			
		||||
            UserRole.SiteCollaborator, hold5.getId(), UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING);
 | 
			
		||||
        users.add(userAddHoldPermission);
 | 
			
		||||
 | 
			
		||||
        STEP("Add content from the site to the hold using the bulk API.");
 | 
			
		||||
        HoldBulkOperationEntry bulkOperationEntry = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
 | 
			
		||||
            .startBulkProcess(holdBulkOperation, hold5.getId());
 | 
			
		||||
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(ACCEPTED);
 | 
			
		||||
        assertEquals(NUMBER_OF_FILES, bulkOperationEntry.getTotalItems());
 | 
			
		||||
 | 
			
		||||
        UserModel userWithoutAddToHoldCapability = roleService.createUserWithSiteRoleRMRoleAndPermission(testSite,
 | 
			
		||||
            UserRole
 | 
			
		||||
                .SiteCollaborator,
 | 
			
		||||
            hold5.getId(), UserRoles.ROLE_RM_POWER_USER, PERMISSION_FILING);
 | 
			
		||||
        users.add(userWithoutAddToHoldCapability);
 | 
			
		||||
 | 
			
		||||
        STEP("Cancel the bulk operation.");
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(userWithoutAddToHoldCapability)
 | 
			
		||||
            .cancelBulkOperation(hold5.getId(), bulkOperationEntry.getBulkStatusId(), new BulkBodyCancel());
 | 
			
		||||
 | 
			
		||||
        STEP("Verify the response status code and the error message.");
 | 
			
		||||
        assertStatusCode(FORBIDDEN);
 | 
			
		||||
        getRestAPIFactory().getRmRestWrapper().assertLastError().containsSummary(ACCESS_DENIED_ERROR_MESSAGE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void assertBulkProcessStatus(HoldBulkStatus holdBulkStatus, long expectedProcessedItems,
 | 
			
		||||
        int expectedErrorsCount, String expectedErrorMessage, HoldBulkOperation holdBulkOperation)
 | 
			
		||||
    {
 | 
			
		||||
        assertEquals("DONE", holdBulkStatus.getStatus());
 | 
			
		||||
        assertEquals(expectedProcessedItems, holdBulkStatus.getTotalItems());
 | 
			
		||||
        assertEquals(expectedProcessedItems, holdBulkStatus.getProcessedItems());
 | 
			
		||||
        assertEquals(expectedErrorsCount, holdBulkStatus.getErrorsCount());
 | 
			
		||||
        assertEquals(holdBulkStatus.getHoldBulkOperation(), holdBulkOperation);
 | 
			
		||||
        assertNotNull(holdBulkStatus.getStartTime());
 | 
			
		||||
        assertNotNull(holdBulkStatus.getEndTime());
 | 
			
		||||
 | 
			
		||||
        if (expectedErrorMessage != null)
 | 
			
		||||
        {
 | 
			
		||||
            assertTrue(holdBulkStatus.getLastError().contains(expectedErrorMessage));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private RestRequestQueryModel getContentFromSiteQuery(String siteId)
 | 
			
		||||
    {
 | 
			
		||||
        RestRequestQueryModel queryReq = new RestRequestQueryModel();
 | 
			
		||||
        queryReq.setQuery("SITE:\"" + siteId + "\" and TYPE:content");
 | 
			
		||||
        queryReq.setLanguage("afts");
 | 
			
		||||
        return queryReq;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private RestRequestQueryModel getContentFromFolderAndAllSubfoldersQuery(String folderId)
 | 
			
		||||
    {
 | 
			
		||||
        RestRequestQueryModel queryReq = new RestRequestQueryModel();
 | 
			
		||||
        queryReq.setQuery("ANCESTOR:\"workspace://SpacesStore/" + folderId + "\" and TYPE:content");
 | 
			
		||||
        queryReq.setLanguage("afts");
 | 
			
		||||
        return queryReq;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @AfterClass(alwaysRun = true)
 | 
			
		||||
    public void cleanupAddToHoldsBulkV1Tests()
 | 
			
		||||
    {
 | 
			
		||||
        dataSite.usingAdmin().deleteSite(testSite);
 | 
			
		||||
        users.forEach(user -> getDataUser().usingAdmin().deleteUser(user));
 | 
			
		||||
        holds.forEach(hold -> getRestAPIFactory().getHoldsAPI(getAdminUser()).deleteHold(hold.getId()));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -60,7 +60,7 @@ import org.alfresco.dataprep.CMISUtil;
 | 
			
		||||
import org.alfresco.dataprep.ContentActions;
 | 
			
		||||
import org.alfresco.rest.model.RestNodeModel;
 | 
			
		||||
import org.alfresco.rest.rm.community.base.BaseRMRestTest;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.HoldEntry;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.v0.HoldEntry;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.record.Record;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.recordcategory.RecordCategory;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.recordcategory.RecordCategoryChild;
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,386 @@
 | 
			
		||||
/*-
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.rm.community.hold;
 | 
			
		||||
 | 
			
		||||
import static org.alfresco.rest.rm.community.base.TestData.HOLD_DESCRIPTION;
 | 
			
		||||
import static org.alfresco.rest.rm.community.base.TestData.HOLD_REASON;
 | 
			
		||||
import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.FILE_PLAN_ALIAS;
 | 
			
		||||
import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.TRANSFERS_ALIAS;
 | 
			
		||||
import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.UNFILED_RECORDS_CONTAINER_ALIAS;
 | 
			
		||||
import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAspects.FROZEN_ASPECT;
 | 
			
		||||
import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentType.UNFILED_RECORD_FOLDER_TYPE;
 | 
			
		||||
import static org.alfresco.rest.rm.community.model.user.UserPermissions.PERMISSION_FILING;
 | 
			
		||||
import static org.alfresco.rest.rm.community.model.user.UserPermissions.PERMISSION_READ_RECORDS;
 | 
			
		||||
import static org.alfresco.rest.rm.community.model.user.UserRoles.ROLE_RM_MANAGER;
 | 
			
		||||
import static org.alfresco.rest.rm.community.util.CommonTestUtils.generateTestPrefix;
 | 
			
		||||
import static org.alfresco.rest.rm.community.utils.CoreUtil.toContentModel;
 | 
			
		||||
import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.IMAGE_FILE;
 | 
			
		||||
import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.createElectronicRecordModel;
 | 
			
		||||
import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.createNonElectronicRecordModel;
 | 
			
		||||
import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.getFile;
 | 
			
		||||
import static org.alfresco.utility.data.RandomData.getRandomAlphanumeric;
 | 
			
		||||
import static org.alfresco.utility.report.log.Step.STEP;
 | 
			
		||||
import static org.apache.commons.httpclient.HttpStatus.SC_BAD_REQUEST;
 | 
			
		||||
import static org.springframework.http.HttpStatus.CREATED;
 | 
			
		||||
import static org.springframework.http.HttpStatus.FORBIDDEN;
 | 
			
		||||
import static org.testng.Assert.assertEquals;
 | 
			
		||||
import static org.testng.Assert.assertTrue;
 | 
			
		||||
import static org.testng.AssertJUnit.assertFalse;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.dataprep.CMISUtil;
 | 
			
		||||
import org.alfresco.dataprep.ContentActions;
 | 
			
		||||
import org.alfresco.rest.model.RestNodeAssociationModelCollection;
 | 
			
		||||
import org.alfresco.rest.model.RestNodeModel;
 | 
			
		||||
import org.alfresco.rest.rm.community.base.BaseRMRestTest;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.Hold;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.HoldChild;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.record.Record;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.recordcategory.RecordCategory;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.recordcategory.RecordCategoryChild;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.user.UserRoles;
 | 
			
		||||
import org.alfresco.rest.rm.community.requests.gscore.api.FilePlanAPI;
 | 
			
		||||
import org.alfresco.rest.rm.community.requests.gscore.api.RecordFolderAPI;
 | 
			
		||||
import org.alfresco.rest.v0.service.RoleService;
 | 
			
		||||
import org.alfresco.utility.constants.UserRole;
 | 
			
		||||
import org.alfresco.utility.model.FileModel;
 | 
			
		||||
import org.alfresco.utility.model.SiteModel;
 | 
			
		||||
import org.alfresco.utility.model.UserModel;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
			
		||||
import org.springframework.http.HttpStatus;
 | 
			
		||||
import org.testng.annotations.AfterClass;
 | 
			
		||||
import org.testng.annotations.BeforeClass;
 | 
			
		||||
import org.testng.annotations.DataProvider;
 | 
			
		||||
import org.testng.annotations.Test;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * V1 API tests for adding content/record folder/records to holds
 | 
			
		||||
 *
 | 
			
		||||
 * @author Damian Ujma
 | 
			
		||||
 */
 | 
			
		||||
public class AddToHoldsV1Tests extends BaseRMRestTest
 | 
			
		||||
{
 | 
			
		||||
    private static final String ACCESS_DENIED_ERROR_MESSAGE = "Access Denied.  You do not have the appropriate " +
 | 
			
		||||
        "permissions to perform this operation.";
 | 
			
		||||
    private static final String INVALID_TYPE_ERROR_MESSAGE = "Only records, record folders or content can be added to a hold.";
 | 
			
		||||
    private static final String LOCKED_FILE_ERROR_MESSAGE = "Locked content can't be added to a hold.";
 | 
			
		||||
 | 
			
		||||
    private static final String HOLD = "HOLD" + generateTestPrefix(AddToHoldsV1Tests.class);
 | 
			
		||||
    private String holdNodeRef;
 | 
			
		||||
    private SiteModel testSite;
 | 
			
		||||
    private FileModel documentHeld;
 | 
			
		||||
    private FileModel contentToAddToHold;
 | 
			
		||||
    private FileModel contentAddToHoldNoPermission;
 | 
			
		||||
    private Hold hold;
 | 
			
		||||
 | 
			
		||||
    private UserModel userAddHoldPermission;
 | 
			
		||||
    private final List<UserModel> users = new ArrayList<>();
 | 
			
		||||
    private final List<String> nodesToBeClean = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private RoleService roleService;
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private ContentActions contentActions;
 | 
			
		||||
 | 
			
		||||
    @BeforeClass(alwaysRun = true)
 | 
			
		||||
    public void preconditionForAddContentToHold()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Create a hold.");
 | 
			
		||||
        hold = createHold(FILE_PLAN_ALIAS,
 | 
			
		||||
            Hold.builder().name(HOLD).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), getAdminUser());
 | 
			
		||||
        holdNodeRef = hold.getId();
 | 
			
		||||
        STEP("Create test files.");
 | 
			
		||||
        testSite = dataSite.usingAdmin().createPublicRandomSite();
 | 
			
		||||
        documentHeld = dataContent.usingAdmin().usingSite(testSite)
 | 
			
		||||
            .createContent(CMISUtil.DocumentType.TEXT_PLAIN);
 | 
			
		||||
        contentToAddToHold = dataContent.usingAdmin().usingSite(testSite)
 | 
			
		||||
            .createContent(CMISUtil.DocumentType.TEXT_PLAIN);
 | 
			
		||||
        contentAddToHoldNoPermission = dataContent.usingAdmin().usingSite(testSite)
 | 
			
		||||
            .createContent(CMISUtil.DocumentType.TEXT_PLAIN);
 | 
			
		||||
 | 
			
		||||
        STEP("Add the content to the hold.");
 | 
			
		||||
        getRestAPIFactory()
 | 
			
		||||
            .getHoldsAPI(getAdminUser())
 | 
			
		||||
            .addChildToHold(HoldChild.builder().id(documentHeld.getNodeRefWithoutVersion()).build(), hold.getId());
 | 
			
		||||
 | 
			
		||||
        STEP("Create users");
 | 
			
		||||
        userAddHoldPermission = roleService.createUserWithSiteRoleRMRoleAndPermission(testSite,
 | 
			
		||||
            UserRole.SiteCollaborator, holdNodeRef, UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING);
 | 
			
		||||
        users.add(userAddHoldPermission);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given a hold that contains at least one active content
 | 
			
		||||
     * When I use the existing REST API to retrieve the contents of the hold
 | 
			
		||||
     * Then I should see all the active content on hold
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void retrieveTheContentOfTheHoldUsingV1API()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Retrieve the list of children from the hold and collect the entries that have the name of the active " +
 | 
			
		||||
            "content held");
 | 
			
		||||
        List<String> documentNames = restClient.authenticateUser(getAdminUser()).withCoreAPI()
 | 
			
		||||
            .usingNode(toContentModel(holdNodeRef))
 | 
			
		||||
            .listChildren().getEntries().stream()
 | 
			
		||||
            .map(RestNodeModel::onModel)
 | 
			
		||||
            .map(RestNodeModel::getName)
 | 
			
		||||
            .filter(documentName -> documentName.equals(documentHeld.getName()))
 | 
			
		||||
            .toList();
 | 
			
		||||
 | 
			
		||||
        STEP("Check the list of active content");
 | 
			
		||||
        assertEquals(documentNames, Set.of(documentHeld.getName()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given a hold that contains at least one active content
 | 
			
		||||
     * When I use the existing REST API to retrieve the holds the content is added
 | 
			
		||||
     * Then the hold where the content held is returned
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void retrieveTheHoldWhereTheContentIsAdded()
 | 
			
		||||
    {
 | 
			
		||||
        RestNodeAssociationModelCollection holdsEntries = getRestAPIFactory()
 | 
			
		||||
            .getNodeAPI(documentHeld).usingParams("where=(assocType='rma:frozenContent')").getParents();
 | 
			
		||||
        Hold retrievedHold = getRestAPIFactory().getHoldsAPI(getAdminUser())
 | 
			
		||||
            .getHold(holdsEntries.getEntries().get(0).getModel().getId());
 | 
			
		||||
        assertEquals(retrievedHold, hold, "Holds are not equal");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Valid nodes to be added to hold
 | 
			
		||||
     */
 | 
			
		||||
    @DataProvider(name = "validNodesForAddToHold")
 | 
			
		||||
    public Object[][] getValidNodesForAddToHold()
 | 
			
		||||
    {
 | 
			
		||||
        //create electronic and nonElectronic record in record folder
 | 
			
		||||
        RecordCategoryChild recordFolder = createCategoryFolderInFilePlan();
 | 
			
		||||
        RecordFolderAPI recordFolderAPI = getRestAPIFactory().getRecordFolderAPI();
 | 
			
		||||
        nodesToBeClean.add(recordFolder.getParentId());
 | 
			
		||||
        Record electronicRecord = recordFolderAPI.createRecord(createElectronicRecordModel(), recordFolder.getId(),
 | 
			
		||||
            getFile
 | 
			
		||||
                (IMAGE_FILE));
 | 
			
		||||
        assertStatusCode(CREATED);
 | 
			
		||||
 | 
			
		||||
        Record nonElectronicRecord = recordFolderAPI.createRecord(createNonElectronicRecordModel(),
 | 
			
		||||
            recordFolder.getId());
 | 
			
		||||
        assertStatusCode(CREATED);
 | 
			
		||||
        getRestAPIFactory().getRMUserAPI().addUserPermission(recordFolder.getId(), userAddHoldPermission,
 | 
			
		||||
            PERMISSION_FILING);
 | 
			
		||||
 | 
			
		||||
        RecordCategoryChild folderToHold = createCategoryFolderInFilePlan();
 | 
			
		||||
        getRestAPIFactory().getRMUserAPI().addUserPermission(folderToHold.getId(), userAddHoldPermission,
 | 
			
		||||
            PERMISSION_FILING);
 | 
			
		||||
        nodesToBeClean.add(folderToHold.getParentId());
 | 
			
		||||
 | 
			
		||||
        return new String[][]
 | 
			
		||||
            {       // record folder
 | 
			
		||||
                { folderToHold.getId() },
 | 
			
		||||
                //electronic record
 | 
			
		||||
                { electronicRecord.getId() },
 | 
			
		||||
                // non electronic record
 | 
			
		||||
                { nonElectronicRecord.getId() },
 | 
			
		||||
                // document from collaboration site
 | 
			
		||||
                { contentToAddToHold.getNodeRefWithoutVersion() },
 | 
			
		||||
            };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given record folder/record/document not on hold
 | 
			
		||||
     * And a hold
 | 
			
		||||
     * And file permission on the hold
 | 
			
		||||
     * And the appropriate capability to add to hold
 | 
			
		||||
     * When I use the existing REST API to add the node to the hold
 | 
			
		||||
     * Then the record folder/record/document is added to the hold
 | 
			
		||||
     * And the item is frozen
 | 
			
		||||
     *
 | 
			
		||||
     * @throws Exception
 | 
			
		||||
     */
 | 
			
		||||
    @Test(dataProvider = "validNodesForAddToHold")
 | 
			
		||||
    public void addValidNodesToHoldWithAllowedUser(String nodeId) throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Add node to hold with user with permission.");
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
 | 
			
		||||
            .addChildToHold(HoldChild.builder().id(nodeId).build(), hold.getId());
 | 
			
		||||
 | 
			
		||||
        STEP("Check the node is frozen.");
 | 
			
		||||
        assertTrue(hasAspect(nodeId, FROZEN_ASPECT));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Data provider with user without correct permission to add to hold and the node ref to be added to hold
 | 
			
		||||
     *
 | 
			
		||||
     * @return object with user model and the node ref to be added to hold
 | 
			
		||||
     */
 | 
			
		||||
    @DataProvider(name = "userWithoutPermissionForAddToHold")
 | 
			
		||||
    public Object[][] getUserWithoutPermissionForAddToHold()
 | 
			
		||||
    {
 | 
			
		||||
        //create record folder
 | 
			
		||||
        RecordCategoryChild recordFolder = createCategoryFolderInFilePlan();
 | 
			
		||||
        //create a rm manager and grant read permission over the record folder created
 | 
			
		||||
        UserModel user = roleService.createUserWithRMRoleAndRMNodePermission(ROLE_RM_MANAGER.roleId,
 | 
			
		||||
            recordFolder.getId(),
 | 
			
		||||
            PERMISSION_READ_RECORDS);
 | 
			
		||||
        getRestAPIFactory().getRMUserAPI().addUserPermission(holdNodeRef, user, PERMISSION_FILING);
 | 
			
		||||
        nodesToBeClean.add(recordFolder.getParentId());
 | 
			
		||||
        return new Object[][]
 | 
			
		||||
            {       // user without write permission on the content
 | 
			
		||||
                {
 | 
			
		||||
                    roleService.createUserWithSiteRoleRMRoleAndPermission(testSite, UserRole.SiteConsumer,
 | 
			
		||||
                        holdNodeRef, UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING),
 | 
			
		||||
                    contentAddToHoldNoPermission.getNodeRefWithoutVersion()
 | 
			
		||||
                },
 | 
			
		||||
                // user with write permission on the content and without filling permission on a hold
 | 
			
		||||
                {
 | 
			
		||||
                    roleService.createUserWithSiteRoleRMRoleAndPermission(testSite, UserRole
 | 
			
		||||
                            .SiteCollaborator,
 | 
			
		||||
                        holdNodeRef, UserRoles.ROLE_RM_MANAGER, PERMISSION_READ_RECORDS),
 | 
			
		||||
                    contentAddToHoldNoPermission.getNodeRefWithoutVersion()
 | 
			
		||||
                },
 | 
			
		||||
                // user with write permission on the content, filling permission on a hold without add to
 | 
			
		||||
                // hold capability
 | 
			
		||||
                {
 | 
			
		||||
                    roleService.createUserWithSiteRoleRMRoleAndPermission(testSite, UserRole
 | 
			
		||||
                            .SiteCollaborator,
 | 
			
		||||
                        holdNodeRef, UserRoles.ROLE_RM_POWER_USER, PERMISSION_READ_RECORDS),
 | 
			
		||||
                    contentAddToHoldNoPermission.getNodeRefWithoutVersion()
 | 
			
		||||
                },
 | 
			
		||||
                //user without write permission on RM  record folder
 | 
			
		||||
                {
 | 
			
		||||
                    user, recordFolder.getId()
 | 
			
		||||
                },
 | 
			
		||||
 | 
			
		||||
            };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given a node not on hold
 | 
			
		||||
     * And a hold
 | 
			
		||||
     * And user without right permission to add to hold
 | 
			
		||||
     * When I use the existing REST API to add the node to the hold
 | 
			
		||||
     * Then the node is not added to the hold
 | 
			
		||||
     * And the node is not frozen
 | 
			
		||||
     *
 | 
			
		||||
     * @throws Exception
 | 
			
		||||
     */
 | 
			
		||||
    @Test(dataProvider = "userWithoutPermissionForAddToHold")
 | 
			
		||||
    public void addContentToHoldWithUserWithoutHoldPermission(UserModel userModel, String nodeToBeAddedToHold)
 | 
			
		||||
        throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        users.add(userModel);
 | 
			
		||||
        STEP("Add the node to the hold with user without permission.");
 | 
			
		||||
 | 
			
		||||
        getRestAPIFactory()
 | 
			
		||||
            .getHoldsAPI(userModel)
 | 
			
		||||
            .addChildToHold(HoldChild.builder().id(nodeToBeAddedToHold).build(), holdNodeRef);
 | 
			
		||||
 | 
			
		||||
        assertStatusCode(FORBIDDEN);
 | 
			
		||||
        getRestAPIFactory().getRmRestWrapper().assertLastError().containsSummary(ACCESS_DENIED_ERROR_MESSAGE);
 | 
			
		||||
 | 
			
		||||
        STEP("Check the node is not frozen.");
 | 
			
		||||
        assertFalse(hasAspect(nodeToBeAddedToHold, FROZEN_ASPECT));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Data provider with invalid node types that can be added to a hold
 | 
			
		||||
     */
 | 
			
		||||
    @DataProvider(name = "invalidNodesForAddToHold")
 | 
			
		||||
    public Object[][] getInvalidNodesForAddToHold()
 | 
			
		||||
    {
 | 
			
		||||
        //create locked file
 | 
			
		||||
        FileModel contentLocked = dataContent.usingAdmin().usingSite(testSite)
 | 
			
		||||
            .createContent(CMISUtil.DocumentType.TEXT_PLAIN);
 | 
			
		||||
 | 
			
		||||
        contentActions.checkOut(getAdminUser().getUsername(), getAdminUser().getPassword(),
 | 
			
		||||
            testSite.getId(), contentLocked.getName());
 | 
			
		||||
        RecordCategory category = createRootCategory(getRandomAlphanumeric());
 | 
			
		||||
        nodesToBeClean.add(category.getId());
 | 
			
		||||
        return new Object[][]
 | 
			
		||||
            {       // file plan node id
 | 
			
		||||
                { getFilePlan(FILE_PLAN_ALIAS).getId(), SC_BAD_REQUEST, INVALID_TYPE_ERROR_MESSAGE },
 | 
			
		||||
                //transfer container
 | 
			
		||||
                { getTransferContainer(TRANSFERS_ALIAS).getId(), SC_BAD_REQUEST, INVALID_TYPE_ERROR_MESSAGE },
 | 
			
		||||
                // a record category
 | 
			
		||||
                { category.getId(), SC_BAD_REQUEST, INVALID_TYPE_ERROR_MESSAGE },
 | 
			
		||||
                // unfiled records root
 | 
			
		||||
                { getUnfiledContainer(UNFILED_RECORDS_CONTAINER_ALIAS).getId(), SC_BAD_REQUEST,
 | 
			
		||||
                    INVALID_TYPE_ERROR_MESSAGE },
 | 
			
		||||
                // an arbitrary unfiled records folder
 | 
			
		||||
                { createUnfiledContainerChild(UNFILED_RECORDS_CONTAINER_ALIAS, "Unfiled Folder " +
 | 
			
		||||
                    getRandomAlphanumeric(), UNFILED_RECORD_FOLDER_TYPE).getId(), SC_BAD_REQUEST,
 | 
			
		||||
                    INVALID_TYPE_ERROR_MESSAGE },
 | 
			
		||||
                //folder,
 | 
			
		||||
                { dataContent.usingAdmin().usingSite(testSite).createFolder().getNodeRef(), SC_BAD_REQUEST,
 | 
			
		||||
                    INVALID_TYPE_ERROR_MESSAGE },
 | 
			
		||||
                //document locked
 | 
			
		||||
                { contentLocked.getNodeRefWithoutVersion(), SC_BAD_REQUEST, LOCKED_FILE_ERROR_MESSAGE }
 | 
			
		||||
            };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given a node that is not a document/record/ record folder ( a valid node type to be added to hold)
 | 
			
		||||
     * And a hold
 | 
			
		||||
     * And user without right permission to add to hold
 | 
			
		||||
     * When I use the existing REST API to add the node to the hold
 | 
			
		||||
     * Then the node is not added to the hold
 | 
			
		||||
     * And the node is not frozen
 | 
			
		||||
     *
 | 
			
		||||
     * @throws Exception
 | 
			
		||||
     */
 | 
			
		||||
    @Test(dataProvider = "invalidNodesForAddToHold")
 | 
			
		||||
    public void addInvalidNodesToHold(String itemNodeRef, int responseCode, String errorMessage) throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Add the node to the hold ");
 | 
			
		||||
 | 
			
		||||
        getRestAPIFactory()
 | 
			
		||||
            .getHoldsAPI(getAdminUser())
 | 
			
		||||
            .addChildToHold(HoldChild.builder().id(itemNodeRef).build(), holdNodeRef);
 | 
			
		||||
 | 
			
		||||
        assertStatusCode(HttpStatus.valueOf(responseCode));
 | 
			
		||||
        getRestAPIFactory().getRmRestWrapper().assertLastError().containsSummary(errorMessage);
 | 
			
		||||
 | 
			
		||||
        STEP("Check node is not frozen.");
 | 
			
		||||
        assertFalse(hasAspect(itemNodeRef, FROZEN_ASPECT));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Hold createHold(String parentId, Hold hold, UserModel user)
 | 
			
		||||
    {
 | 
			
		||||
        FilePlanAPI filePlanAPI = getRestAPIFactory().getFilePlansAPI(user);
 | 
			
		||||
        return filePlanAPI.createHold(hold, parentId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @AfterClass(alwaysRun = true)
 | 
			
		||||
    public void cleanUpAddContentToHold()
 | 
			
		||||
    {
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(getAdminUser()).deleteHold(holdNodeRef);
 | 
			
		||||
        dataSite.usingAdmin().deleteSite(testSite);
 | 
			
		||||
        users.forEach(user -> getDataUser().usingAdmin().deleteUser(user));
 | 
			
		||||
        nodesToBeClean.forEach(this::deleteRecordCategory);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,186 @@
 | 
			
		||||
/*-
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.rm.community.hold;
 | 
			
		||||
 | 
			
		||||
import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.FILE_PLAN_ALIAS;
 | 
			
		||||
import static org.alfresco.utility.data.RandomData.getRandomAlphanumeric;
 | 
			
		||||
import static org.junit.Assert.assertEquals;
 | 
			
		||||
import static org.junit.Assert.assertNotNull;
 | 
			
		||||
import static org.springframework.http.HttpStatus.NOT_FOUND;
 | 
			
		||||
import static org.springframework.http.HttpStatus.NO_CONTENT;
 | 
			
		||||
import static org.springframework.http.HttpStatus.OK;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.rest.rm.community.base.BaseRMRestTest;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.Hold;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.HoldDeletionReason;
 | 
			
		||||
import org.testng.annotations.AfterClass;
 | 
			
		||||
import org.testng.annotations.Test;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class contains the tests for the Holds CRUD V1 API
 | 
			
		||||
 *
 | 
			
		||||
 * @author Damian Ujma
 | 
			
		||||
 */
 | 
			
		||||
public class HoldsTests extends BaseRMRestTest
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    private final List<String> nodeRefs = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testGetHold()
 | 
			
		||||
    {
 | 
			
		||||
        String holdName = "Hold" + getRandomAlphanumeric();
 | 
			
		||||
        String holdDescription = "Description" + getRandomAlphanumeric();
 | 
			
		||||
        String holdReason = "Reason" + getRandomAlphanumeric();
 | 
			
		||||
 | 
			
		||||
        // Create the hold
 | 
			
		||||
        Hold hold = Hold.builder()
 | 
			
		||||
            .name(holdName)
 | 
			
		||||
            .description(holdDescription)
 | 
			
		||||
            .reason(holdReason)
 | 
			
		||||
            .build();
 | 
			
		||||
        Hold createdHold = getRestAPIFactory().getFilePlansAPI()
 | 
			
		||||
            .createHold(hold, FILE_PLAN_ALIAS);
 | 
			
		||||
 | 
			
		||||
        // Get the hold
 | 
			
		||||
        Hold receivedHold = getRestAPIFactory().getHoldsAPI().getHold(createdHold.getId());
 | 
			
		||||
        nodeRefs.add(receivedHold.getId());
 | 
			
		||||
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(OK);
 | 
			
		||||
 | 
			
		||||
        assertEquals(receivedHold.getName(), holdName);
 | 
			
		||||
        assertEquals(receivedHold.getDescription(), holdDescription);
 | 
			
		||||
        assertEquals(receivedHold.getReason(), holdReason);
 | 
			
		||||
        assertNotNull(receivedHold.getId());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testUpdateHold()
 | 
			
		||||
    {
 | 
			
		||||
        String holdName = "Hold" + getRandomAlphanumeric();
 | 
			
		||||
        String holdDescription = "Description" + getRandomAlphanumeric();
 | 
			
		||||
        String holdReason = "Reason" + getRandomAlphanumeric();
 | 
			
		||||
 | 
			
		||||
        // Create the hold
 | 
			
		||||
        Hold hold = Hold.builder()
 | 
			
		||||
            .name(holdName)
 | 
			
		||||
            .description(holdDescription)
 | 
			
		||||
            .reason(holdReason)
 | 
			
		||||
            .build();
 | 
			
		||||
        Hold createdHold = getRestAPIFactory().getFilePlansAPI()
 | 
			
		||||
            .createHold(hold, FILE_PLAN_ALIAS);
 | 
			
		||||
        nodeRefs.add(createdHold.getId());
 | 
			
		||||
 | 
			
		||||
        Hold holdModel = Hold.builder()
 | 
			
		||||
            .name("Updated" + holdName)
 | 
			
		||||
            .description("Updated" + holdDescription)
 | 
			
		||||
            .reason("Updated" + holdReason)
 | 
			
		||||
            .build();
 | 
			
		||||
 | 
			
		||||
        // Update the hold
 | 
			
		||||
        Hold updatedHold = getRestAPIFactory().getHoldsAPI().updateHold(holdModel, createdHold.getId());
 | 
			
		||||
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(OK);
 | 
			
		||||
 | 
			
		||||
        assertEquals(updatedHold.getName(), "Updated" + holdName);
 | 
			
		||||
        assertEquals(updatedHold.getDescription(), "Updated" + holdDescription);
 | 
			
		||||
        assertEquals(updatedHold.getReason(), "Updated" + holdReason);
 | 
			
		||||
        assertNotNull(updatedHold.getId());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testDeleteHold()
 | 
			
		||||
    {
 | 
			
		||||
        String holdName = "Hold" + getRandomAlphanumeric();
 | 
			
		||||
        String holdDescription = "Description" + getRandomAlphanumeric();
 | 
			
		||||
        String holdReason = "Reason" + getRandomAlphanumeric();
 | 
			
		||||
 | 
			
		||||
        // Create the hold
 | 
			
		||||
        Hold hold = Hold.builder()
 | 
			
		||||
            .name(holdName)
 | 
			
		||||
            .description(holdDescription)
 | 
			
		||||
            .reason(holdReason)
 | 
			
		||||
            .build();
 | 
			
		||||
        Hold createdHold = getRestAPIFactory().getFilePlansAPI()
 | 
			
		||||
            .createHold(hold, FILE_PLAN_ALIAS);
 | 
			
		||||
        nodeRefs.add(createdHold.getId());
 | 
			
		||||
 | 
			
		||||
        // Delete the hold
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI().deleteHold(createdHold.getId());
 | 
			
		||||
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(NO_CONTENT);
 | 
			
		||||
 | 
			
		||||
        // Try to get the hold
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI().getHold(createdHold.getId());
 | 
			
		||||
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(NOT_FOUND);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testDeleteHoldWithReason()
 | 
			
		||||
    {
 | 
			
		||||
        String holdName = "Hold" + getRandomAlphanumeric();
 | 
			
		||||
        String holdDescription = "Description" + getRandomAlphanumeric();
 | 
			
		||||
        String holdReason = "Reason" + getRandomAlphanumeric();
 | 
			
		||||
 | 
			
		||||
        // Create the hold
 | 
			
		||||
        Hold hold = Hold.builder()
 | 
			
		||||
            .name(holdName)
 | 
			
		||||
            .description(holdDescription)
 | 
			
		||||
            .reason(holdReason)
 | 
			
		||||
            .build();
 | 
			
		||||
        Hold createdHold = getRestAPIFactory().getFilePlansAPI()
 | 
			
		||||
            .createHold(hold, FILE_PLAN_ALIAS);
 | 
			
		||||
        nodeRefs.add(createdHold.getId());
 | 
			
		||||
 | 
			
		||||
        // Delete the hold with the reason
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI()
 | 
			
		||||
            .deleteHoldWithReason(HoldDeletionReason.builder().reason("Example reason").build(), createdHold.getId());
 | 
			
		||||
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(OK);
 | 
			
		||||
 | 
			
		||||
        // Try to get the hold
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI().getHold(createdHold.getId());
 | 
			
		||||
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(NOT_FOUND);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @AfterClass(alwaysRun = true)
 | 
			
		||||
    public void cleanUpHoldsTests()
 | 
			
		||||
    {
 | 
			
		||||
        nodeRefs.forEach(nodeRef -> getRestAPIFactory().getHoldsAPI(getAdminUser()).deleteHold(nodeRef));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,337 @@
 | 
			
		||||
/*-
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.rm.community.hold;
 | 
			
		||||
 | 
			
		||||
import static org.alfresco.rest.rm.community.base.TestData.HOLD_DESCRIPTION;
 | 
			
		||||
import static org.alfresco.rest.rm.community.base.TestData.HOLD_REASON;
 | 
			
		||||
import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.FILE_PLAN_ALIAS;
 | 
			
		||||
import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAspects.ASPECTS_VITAL_RECORD;
 | 
			
		||||
import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAspects.ASPECTS_VITAL_RECORD_DEFINITION;
 | 
			
		||||
import static org.alfresco.rest.rm.community.util.CommonTestUtils.generateTestPrefix;
 | 
			
		||||
import static org.alfresco.rest.rm.community.utils.CoreUtil.createBodyForMoveCopy;
 | 
			
		||||
import static org.alfresco.utility.data.RandomData.getRandomName;
 | 
			
		||||
import static org.alfresco.utility.report.log.Step.STEP;
 | 
			
		||||
import static org.apache.commons.httpclient.HttpStatus.SC_INTERNAL_SERVER_ERROR;
 | 
			
		||||
import static org.springframework.http.HttpStatus.CREATED;
 | 
			
		||||
import static org.springframework.http.HttpStatus.FORBIDDEN;
 | 
			
		||||
import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
 | 
			
		||||
import static org.springframework.http.HttpStatus.OK;
 | 
			
		||||
import static org.testng.Assert.assertNotNull;
 | 
			
		||||
import static org.testng.Assert.assertTrue;
 | 
			
		||||
import static org.testng.AssertJUnit.assertFalse;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
 | 
			
		||||
import jakarta.json.Json;
 | 
			
		||||
import jakarta.json.JsonObject;
 | 
			
		||||
import org.alfresco.dataprep.CMISUtil;
 | 
			
		||||
import org.alfresco.rest.core.JsonBodyGenerator;
 | 
			
		||||
import org.alfresco.rest.core.v0.BaseAPI.RM_ACTIONS;
 | 
			
		||||
import org.alfresco.rest.rm.community.base.BaseRMRestTest;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.common.ReviewPeriod;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.Hold;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.HoldChild;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.record.Record;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.recordcategory.RecordCategory;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.recordcategory.RecordCategoryChild;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.recordfolder.RecordFolder;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.recordfolder.RecordFolderProperties;
 | 
			
		||||
import org.alfresco.rest.rm.community.requests.gscore.api.FilePlanAPI;
 | 
			
		||||
import org.alfresco.rest.v0.RMRolesAndActionsAPI;
 | 
			
		||||
import org.alfresco.rest.v0.service.DispositionScheduleService;
 | 
			
		||||
import org.alfresco.utility.Utility;
 | 
			
		||||
import org.alfresco.utility.model.FileModel;
 | 
			
		||||
import org.alfresco.utility.model.FolderModel;
 | 
			
		||||
import org.alfresco.utility.model.UserModel;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
			
		||||
import org.testng.annotations.AfterClass;
 | 
			
		||||
import org.testng.annotations.BeforeClass;
 | 
			
		||||
import org.testng.annotations.Test;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * V1 API tests to check actions on frozen content
 | 
			
		||||
 *
 | 
			
		||||
 * @author Damian Ujma
 | 
			
		||||
 */
 | 
			
		||||
public class PreventActionsOnFrozenContentV1Tests extends BaseRMRestTest
 | 
			
		||||
{
 | 
			
		||||
    private static String holdNodeRef;
 | 
			
		||||
    private static FileModel contentHeld;
 | 
			
		||||
    private static File updatedFile;
 | 
			
		||||
    private static FolderModel folderModel;
 | 
			
		||||
    private static RecordCategoryChild recordFolder;
 | 
			
		||||
    private static Record recordFrozen;
 | 
			
		||||
    private static Record recordNotHeld;
 | 
			
		||||
    private static RecordCategory categoryWithRS;
 | 
			
		||||
 | 
			
		||||
    private Hold hold;
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private DispositionScheduleService dispositionScheduleService;
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private RMRolesAndActionsAPI rmRolesAndActionsAPI;
 | 
			
		||||
 | 
			
		||||
    @BeforeClass(alwaysRun = true)
 | 
			
		||||
    public void preconditionForPreventActionsOnFrozenContent()
 | 
			
		||||
    {
 | 
			
		||||
        String holdOne = "HOLD" + generateTestPrefix(PreventActionsOnFrozenContentV1Tests.class);
 | 
			
		||||
 | 
			
		||||
        STEP("Create a hold.");
 | 
			
		||||
        hold = createHold(FILE_PLAN_ALIAS,
 | 
			
		||||
            Hold.builder().name(holdOne).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), getAdminUser());
 | 
			
		||||
        holdNodeRef = hold.getId();
 | 
			
		||||
 | 
			
		||||
        STEP("Create a test file.");
 | 
			
		||||
        testSite = dataSite.usingAdmin().createPublicRandomSite();
 | 
			
		||||
        contentHeld = dataContent.usingAdmin().usingSite(testSite)
 | 
			
		||||
            .createContent(CMISUtil.DocumentType.TEXT_PLAIN);
 | 
			
		||||
 | 
			
		||||
        STEP("Add the file to the hold.");
 | 
			
		||||
        getRestAPIFactory()
 | 
			
		||||
            .getHoldsAPI(getAdminUser())
 | 
			
		||||
            .addChildToHold(HoldChild.builder().id(contentHeld.getNodeRefWithoutVersion()).build(), hold.getId());
 | 
			
		||||
 | 
			
		||||
        STEP("Get a file resource.");
 | 
			
		||||
        updatedFile = Utility.getResourceTestDataFile("SampleTextFile_10kb.txt");
 | 
			
		||||
 | 
			
		||||
        STEP("Create a folder withing the test site .");
 | 
			
		||||
        folderModel = dataContent.usingAdmin().usingSite(testSite)
 | 
			
		||||
            .createFolder();
 | 
			
		||||
 | 
			
		||||
        STEP("Create a record folder with some records");
 | 
			
		||||
        recordFolder = createCategoryFolderInFilePlan();
 | 
			
		||||
        recordFrozen = createElectronicRecord(recordFolder.getId(), getRandomName("elRecordFrozen"));
 | 
			
		||||
        recordNotHeld = createElectronicRecord(recordFolder.getId(), getRandomName("elRecordNotHeld"));
 | 
			
		||||
        assertStatusCode(CREATED);
 | 
			
		||||
 | 
			
		||||
        STEP("Add the record to the hold.");
 | 
			
		||||
        getRestAPIFactory()
 | 
			
		||||
            .getHoldsAPI(getAdminUser())
 | 
			
		||||
            .addChildToHold(HoldChild.builder().id(recordFrozen.getId()).build(), hold.getId());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given active content on hold
 | 
			
		||||
     * When I try to edit the properties
 | 
			
		||||
     * Or perform an action that edits the properties
 | 
			
		||||
     * Then I am not successful
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void editPropertiesForContentHeld() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Update name property of the held content");
 | 
			
		||||
        JsonObject nameUpdated = Json.createObjectBuilder().add("name", "HeldNameUpdated").build();
 | 
			
		||||
        restClient.authenticateUser(getAdminUser()).withCoreAPI().usingNode(contentHeld)
 | 
			
		||||
            .updateNode(nameUpdated.toString());
 | 
			
		||||
 | 
			
		||||
        STEP("Check the request failed.");
 | 
			
		||||
        restClient.assertStatusCodeIs(FORBIDDEN);
 | 
			
		||||
        restClient.assertLastError().containsSummary("Frozen content can't be updated.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * Given active content on hold
 | 
			
		||||
     * When I try to update the content
 | 
			
		||||
     * Then I am not successful
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void updateContentForFrozenFile() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Update content of the held file");
 | 
			
		||||
        restClient.authenticateUser(getAdminUser()).withCoreAPI().usingNode(contentHeld).updateNodeContent(updatedFile);
 | 
			
		||||
 | 
			
		||||
        STEP("Check the request failed.");
 | 
			
		||||
        restClient.assertStatusCodeIs(INTERNAL_SERVER_ERROR);
 | 
			
		||||
        restClient.assertLastError().containsSummary("Frozen content can't be updated.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * Given active content on hold
 | 
			
		||||
     * When I try to delete the content
 | 
			
		||||
     * Then I am not successful
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void deleteFrozenFile() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Delete frozen file");
 | 
			
		||||
        restClient.authenticateUser(getAdminUser()).withCoreAPI().usingNode(contentHeld)
 | 
			
		||||
            .deleteNode(contentHeld.getNodeRefWithoutVersion());
 | 
			
		||||
 | 
			
		||||
        STEP("Check the request failed.");
 | 
			
		||||
        restClient.assertStatusCodeIs(FORBIDDEN);
 | 
			
		||||
        restClient.assertLastError().containsSummary("Frozen content can't be deleted.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given active content on hold
 | 
			
		||||
     * When I try to copy the content
 | 
			
		||||
     * Then I am not successful
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void copyFrozenFile()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Copy frozen file");
 | 
			
		||||
        String postBody = JsonBodyGenerator.keyValueJson("targetParentId", folderModel.getNodeRef());
 | 
			
		||||
        getRestAPIFactory().getNodeAPI(contentHeld).copyNode(postBody);
 | 
			
		||||
 | 
			
		||||
        STEP("Check the request failed.");
 | 
			
		||||
        assertStatusCode(FORBIDDEN);
 | 
			
		||||
        getRestAPIFactory().getRmRestWrapper().assertLastError().containsSummary("Permission was denied");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given active content on hold
 | 
			
		||||
     * When I try to move the content
 | 
			
		||||
     * Then I am not successful
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void moveFrozenFile() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Move frozen file");
 | 
			
		||||
        getRestAPIFactory().getNodeAPI(contentHeld).move(createBodyForMoveCopy(folderModel.getNodeRef()));
 | 
			
		||||
 | 
			
		||||
        STEP("Check the request failed.");
 | 
			
		||||
        assertStatusCode(FORBIDDEN);
 | 
			
		||||
        getRestAPIFactory().getRmRestWrapper().assertLastError().containsSummary("Frozen content can't be moved.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given a record folder with a frozen record and another record not held
 | 
			
		||||
     * When I update the record folder and make the records as vital
 | 
			
		||||
     * Then I am successful and the records not held are marked as vital
 | 
			
		||||
     * And the frozen nodes have the vital record search properties updated
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void updateRecordFolderVitalProperties()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Update the vital record properties for the record folder");
 | 
			
		||||
        // Create the record folder properties to update
 | 
			
		||||
        RecordFolder recordFolderToUpdate = RecordFolder.builder()
 | 
			
		||||
            .properties(RecordFolderProperties.builder()
 | 
			
		||||
                .vitalRecordIndicator(true)
 | 
			
		||||
                .reviewPeriod(new ReviewPeriod("month", "1"))
 | 
			
		||||
                .build())
 | 
			
		||||
            .build();
 | 
			
		||||
 | 
			
		||||
        // Update the record folder
 | 
			
		||||
        RecordFolder updatedRecordFolder = getRestAPIFactory().getRecordFolderAPI().updateRecordFolder
 | 
			
		||||
            (recordFolderToUpdate,
 | 
			
		||||
                recordFolder.getId());
 | 
			
		||||
        assertStatusCode(OK);
 | 
			
		||||
        assertTrue(updatedRecordFolder.getAspectNames().contains(ASPECTS_VITAL_RECORD_DEFINITION));
 | 
			
		||||
 | 
			
		||||
        STEP("Check the frozen record was not marked as vital");
 | 
			
		||||
        recordFrozen = getRestAPIFactory().getRecordsAPI().getRecord(recordFrozen.getId());
 | 
			
		||||
        assertFalse(recordFrozen.getAspectNames().contains(ASPECTS_VITAL_RECORD));
 | 
			
		||||
        assertTrue(recordFrozen.getProperties().getRecordSearchVitalRecordReviewPeriod().contains("month"));
 | 
			
		||||
        assertTrue(recordFrozen.getProperties().getRecordSearchVitalRecordReviewPeriodExpression().contains("1"));
 | 
			
		||||
 | 
			
		||||
        STEP("Check the record not held was marked as vital");
 | 
			
		||||
        recordNotHeld = getRestAPIFactory().getRecordsAPI().getRecord(recordNotHeld.getId());
 | 
			
		||||
        assertTrue(recordNotHeld.getAspectNames().contains(ASPECTS_VITAL_RECORD));
 | 
			
		||||
        assertNotNull(recordNotHeld.getProperties().getReviewAsOf());
 | 
			
		||||
        assertTrue(recordNotHeld.getProperties().getRecordSearchVitalRecordReviewPeriod().contains("month"));
 | 
			
		||||
        assertTrue(recordNotHeld.getProperties().getRecordSearchVitalRecordReviewPeriodExpression().contains("1"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given a record folder with a frozen record and another record not held
 | 
			
		||||
     * When I add a disposition schedule
 | 
			
		||||
     * Then I am successful
 | 
			
		||||
     * And the record search disposition schedule properties are updated
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void createDispositionScheduleOnCategoryWithHeldChildren()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Create a retention schedule on the category with frozen children");
 | 
			
		||||
        RecordCategory categoryWithRS = getRestAPIFactory().getRecordCategoryAPI()
 | 
			
		||||
            .getRecordCategory(recordFolder.getParentId());
 | 
			
		||||
        dispositionScheduleService.createCategoryRetentionSchedule(categoryWithRS.getName(), false);
 | 
			
		||||
        dispositionScheduleService.addCutOffImmediatelyStep(categoryWithRS.getName());
 | 
			
		||||
        dispositionScheduleService.addDestroyWithGhostingImmediatelyAfterCutOff(categoryWithRS.getName());
 | 
			
		||||
 | 
			
		||||
        STEP("Check the record folder has a disposition schedule");
 | 
			
		||||
        RecordFolder folderWithRS = getRestAPIFactory().getRecordFolderAPI().getRecordFolder(recordFolder.getId());
 | 
			
		||||
        assertNotNull(folderWithRS.getProperties().getRecordSearchDispositionAuthority());
 | 
			
		||||
        assertNotNull(folderWithRS.getProperties().getRecordSearchDispositionInstructions());
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given a record category with a disposition schedule applied to records
 | 
			
		||||
     * And the disposition schedule has a retain step  immediately and destroy step immediately
 | 
			
		||||
     * And a complete record added to one hold
 | 
			
		||||
     * When I execute the retain action
 | 
			
		||||
     * Then the action is executed
 | 
			
		||||
     * And the record search disposition schedule properties are updated
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void retainActionOnFrozenHeldRecords()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Add a category with a disposition schedule.");
 | 
			
		||||
        categoryWithRS = createRootCategory(getRandomName("CategoryWithRS"));
 | 
			
		||||
        dispositionScheduleService.createCategoryRetentionSchedule(categoryWithRS.getName(), true);
 | 
			
		||||
        dispositionScheduleService.addRetainAfterPeriodStep(categoryWithRS.getName(), "immediately");
 | 
			
		||||
        dispositionScheduleService.addDestroyWithGhostingImmediatelyAfterCutOff(categoryWithRS.getName());
 | 
			
		||||
 | 
			
		||||
        STEP("Create record folder with a record.");
 | 
			
		||||
        RecordCategoryChild folder = createFolder(categoryWithRS.getId(), getRandomName("RecFolder"));
 | 
			
		||||
        Record record = createElectronicRecord(folder.getId(), getRandomName("elRecord"));
 | 
			
		||||
        completeRecord(record.getId());
 | 
			
		||||
 | 
			
		||||
        STEP("Add the record to the hold");
 | 
			
		||||
        getRestAPIFactory()
 | 
			
		||||
            .getHoldsAPI(getAdminUser())
 | 
			
		||||
            .addChildToHold(HoldChild.builder().id(record.getId()).build(), hold.getId());
 | 
			
		||||
 | 
			
		||||
        STEP("Execute the retain action");
 | 
			
		||||
        rmRolesAndActionsAPI.executeAction(getAdminUser().getUsername(), getAdminUser().getPassword(), record.getName(),
 | 
			
		||||
            RM_ACTIONS.END_RETENTION, null, SC_INTERNAL_SERVER_ERROR);
 | 
			
		||||
 | 
			
		||||
        STEP("Check the record search disposition properties");
 | 
			
		||||
        Record recordUpdated = getRestAPIFactory().getRecordsAPI().getRecord(record.getId());
 | 
			
		||||
        assertTrue(recordUpdated.getProperties().getRecordSearchDispositionActionName()
 | 
			
		||||
            .contains(RM_ACTIONS.END_RETENTION.getAction()));
 | 
			
		||||
        assertTrue(recordUpdated.getProperties().getRecordSearchDispositionPeriod().contains("immediately"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Hold createHold(String parentId, Hold hold, UserModel user)
 | 
			
		||||
    {
 | 
			
		||||
        FilePlanAPI filePlanAPI = getRestAPIFactory().getFilePlansAPI(user);
 | 
			
		||||
        return filePlanAPI.createHold(hold, parentId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @AfterClass(alwaysRun = true)
 | 
			
		||||
    public void cleanUpPreventActionsOnFrozenContent()
 | 
			
		||||
    {
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(getAdminUser()).deleteHold(holdNodeRef);
 | 
			
		||||
        dataSite.usingAdmin().deleteSite(testSite);
 | 
			
		||||
        deleteRecordCategory(recordFolder.getParentId());
 | 
			
		||||
        deleteRecordCategory(categoryWithRS.getId());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -52,7 +52,7 @@ import java.util.Set;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.dataprep.CMISUtil;
 | 
			
		||||
import org.alfresco.rest.rm.community.base.BaseRMRestTest;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.HoldEntry;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.v0.HoldEntry;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.record.Record;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.recordcategory.RecordCategoryChild;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.user.UserRoles;
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,374 @@
 | 
			
		||||
/*-
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.rm.community.hold;
 | 
			
		||||
 | 
			
		||||
import static java.util.Arrays.asList;
 | 
			
		||||
 | 
			
		||||
import static org.alfresco.rest.rm.community.base.TestData.HOLD_DESCRIPTION;
 | 
			
		||||
import static org.alfresco.rest.rm.community.base.TestData.HOLD_REASON;
 | 
			
		||||
import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.FILE_PLAN_ALIAS;
 | 
			
		||||
import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAspects.FROZEN_ASPECT;
 | 
			
		||||
import static org.alfresco.rest.rm.community.model.user.UserPermissions.PERMISSION_FILING;
 | 
			
		||||
import static org.alfresco.rest.rm.community.model.user.UserPermissions.PERMISSION_READ_RECORDS;
 | 
			
		||||
import static org.alfresco.rest.rm.community.model.user.UserRoles.ROLE_RM_MANAGER;
 | 
			
		||||
import static org.alfresco.rest.rm.community.util.CommonTestUtils.generateTestPrefix;
 | 
			
		||||
import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.IMAGE_FILE;
 | 
			
		||||
import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.createElectronicRecordModel;
 | 
			
		||||
import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.createNonElectronicRecordModel;
 | 
			
		||||
import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.getFile;
 | 
			
		||||
import static org.alfresco.utility.report.log.Step.STEP;
 | 
			
		||||
import static org.springframework.http.HttpStatus.CREATED;
 | 
			
		||||
import static org.springframework.http.HttpStatus.FORBIDDEN;
 | 
			
		||||
import static org.testng.Assert.assertFalse;
 | 
			
		||||
import static org.testng.Assert.assertTrue;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.HashSet;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import java.util.stream.Stream;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.dataprep.CMISUtil;
 | 
			
		||||
import org.alfresco.rest.model.RestNodeAssociationModelCollection;
 | 
			
		||||
import org.alfresco.rest.rm.community.base.BaseRMRestTest;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.Hold;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.hold.HoldChild;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.record.Record;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.recordcategory.RecordCategoryChild;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.user.UserRoles;
 | 
			
		||||
import org.alfresco.rest.rm.community.requests.gscore.api.FilePlanAPI;
 | 
			
		||||
import org.alfresco.rest.rm.community.requests.gscore.api.RecordFolderAPI;
 | 
			
		||||
import org.alfresco.rest.rm.community.utils.CoreUtil;
 | 
			
		||||
import org.alfresco.rest.v0.service.RoleService;
 | 
			
		||||
import org.alfresco.utility.constants.UserRole;
 | 
			
		||||
import org.alfresco.utility.model.FileModel;
 | 
			
		||||
import org.alfresco.utility.model.SiteModel;
 | 
			
		||||
import org.alfresco.utility.model.UserModel;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
			
		||||
import org.testng.annotations.AfterClass;
 | 
			
		||||
import org.testng.annotations.BeforeClass;
 | 
			
		||||
import org.testng.annotations.DataProvider;
 | 
			
		||||
import org.testng.annotations.Test;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * V1 API tests for removing content/record folder/record from holds
 | 
			
		||||
 *
 | 
			
		||||
 * @author Damian Ujma
 | 
			
		||||
 */
 | 
			
		||||
public class RemoveFromHoldsV1Tests extends BaseRMRestTest
 | 
			
		||||
{
 | 
			
		||||
    private static final String HOLD_ONE = "HOLD_ONE" + generateTestPrefix(RemoveFromHoldsV1Tests.class);
 | 
			
		||||
    private static final String HOLD_TWO = "HOLD_TWO" + generateTestPrefix(RemoveFromHoldsV1Tests.class);
 | 
			
		||||
    private static final String ACCESS_DENIED_ERROR_MESSAGE = "Access Denied.  You do not have the appropriate " +
 | 
			
		||||
        "permissions to perform this operation.";
 | 
			
		||||
 | 
			
		||||
    private SiteModel testSite;
 | 
			
		||||
    private SiteModel privateSite;
 | 
			
		||||
    private String holdNodeRefOne;
 | 
			
		||||
    private FileModel contentHeld;
 | 
			
		||||
    private FileModel contentAddToManyHolds;
 | 
			
		||||
    private List<String> holdsListRef = new ArrayList<>();
 | 
			
		||||
    private final Set<UserModel> usersToBeClean = new HashSet<>();
 | 
			
		||||
    private final Set<String> nodesToBeClean = new HashSet<>();
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private RoleService roleService;
 | 
			
		||||
 | 
			
		||||
    @BeforeClass(alwaysRun = true)
 | 
			
		||||
    public void preconditionForRemoveContentFromHold()
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Create two holds.");
 | 
			
		||||
 | 
			
		||||
        holdNodeRefOne = createHold(FILE_PLAN_ALIAS,
 | 
			
		||||
            Hold.builder().name(HOLD_ONE).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(),
 | 
			
		||||
            getAdminUser()).getId();
 | 
			
		||||
        String holdNodeRefTwo = createHold(FILE_PLAN_ALIAS,
 | 
			
		||||
            Hold.builder().name(HOLD_TWO).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(),
 | 
			
		||||
            getAdminUser()).getId();
 | 
			
		||||
        holdsListRef = asList(holdNodeRefOne, holdNodeRefTwo);
 | 
			
		||||
 | 
			
		||||
        STEP("Create test files.");
 | 
			
		||||
        testSite = dataSite.usingAdmin().createPublicRandomSite();
 | 
			
		||||
        privateSite = dataSite.usingAdmin().createPrivateRandomSite();
 | 
			
		||||
        contentHeld = dataContent.usingAdmin().usingSite(testSite)
 | 
			
		||||
            .createContent(CMISUtil.DocumentType.TEXT_PLAIN);
 | 
			
		||||
        contentAddToManyHolds = dataContent.usingSite(testSite)
 | 
			
		||||
            .createContent(CMISUtil.DocumentType.TEXT_PLAIN);
 | 
			
		||||
 | 
			
		||||
        STEP("Add content to the holds.");
 | 
			
		||||
        getRestAPIFactory()
 | 
			
		||||
            .getHoldsAPI(getAdminUser())
 | 
			
		||||
            .addChildToHold(HoldChild.builder().id(contentHeld.getNodeRefWithoutVersion()).build(), holdNodeRefOne);
 | 
			
		||||
        getRestAPIFactory()
 | 
			
		||||
            .getHoldsAPI(getAdminUser())
 | 
			
		||||
            .addChildToHold(HoldChild.builder().id(contentAddToManyHolds.getNodeRefWithoutVersion()).build(),
 | 
			
		||||
                holdNodeRefOne);
 | 
			
		||||
        getRestAPIFactory()
 | 
			
		||||
            .getHoldsAPI(getAdminUser())
 | 
			
		||||
            .addChildToHold(HoldChild.builder().id(contentAddToManyHolds.getNodeRefWithoutVersion()).build(),
 | 
			
		||||
                holdNodeRefTwo);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Valid nodes to be removed from hold
 | 
			
		||||
     */
 | 
			
		||||
    @DataProvider(name = "validNodesToRemoveFromHold")
 | 
			
		||||
    public Object[][] getValidNodesToRemoveFromHold()
 | 
			
		||||
    {
 | 
			
		||||
        //create electronic and nonElectronic record in record folder
 | 
			
		||||
        RecordCategoryChild recordFolder = createCategoryFolderInFilePlan();
 | 
			
		||||
        RecordFolderAPI recordFolderAPI = getRestAPIFactory().getRecordFolderAPI();
 | 
			
		||||
        nodesToBeClean.add(recordFolder.getParentId());
 | 
			
		||||
        Record electronicRecord = recordFolderAPI.createRecord(createElectronicRecordModel(), recordFolder.getId(),
 | 
			
		||||
            getFile
 | 
			
		||||
                (IMAGE_FILE));
 | 
			
		||||
        assertStatusCode(CREATED);
 | 
			
		||||
        Record nonElectronicRecord = recordFolderAPI.createRecord(createNonElectronicRecordModel(),
 | 
			
		||||
            recordFolder.getId());
 | 
			
		||||
        assertStatusCode(CREATED);
 | 
			
		||||
 | 
			
		||||
        RecordCategoryChild folderToHeld = createCategoryFolderInFilePlan();
 | 
			
		||||
        nodesToBeClean.add(folderToHeld.getParentId());
 | 
			
		||||
        Stream.of(electronicRecord.getId(), nonElectronicRecord.getId(), folderToHeld.getId())
 | 
			
		||||
            .forEach(id -> getRestAPIFactory()
 | 
			
		||||
                .getHoldsAPI(getAdminUser())
 | 
			
		||||
                .addChildToHold(HoldChild.builder().id(id).build(), holdNodeRefOne));
 | 
			
		||||
 | 
			
		||||
        return new String[][]
 | 
			
		||||
            {       // record folder
 | 
			
		||||
                { folderToHeld.getId() },
 | 
			
		||||
                //electronic record
 | 
			
		||||
                { electronicRecord.getId() },
 | 
			
		||||
                // non electronic record
 | 
			
		||||
                { nonElectronicRecord.getId() },
 | 
			
		||||
                // document from collaboration site
 | 
			
		||||
                { contentHeld.getNodeRefWithoutVersion() },
 | 
			
		||||
            };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given content/record folder/record that is held
 | 
			
		||||
     * And the corresponding hold
 | 
			
		||||
     * When I use the existing REST API to remove the node from the hold
 | 
			
		||||
     * Then the node is removed from the hold
 | 
			
		||||
     * And is no longer frozen
 | 
			
		||||
     */
 | 
			
		||||
    @Test(dataProvider = "validNodesToRemoveFromHold")
 | 
			
		||||
    public void removeContentFromHold(String nodeId) throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Remove node from hold");
 | 
			
		||||
        getRestAPIFactory()
 | 
			
		||||
            .getHoldsAPI(getAdminUser()).deleteHoldChild(holdNodeRefOne, nodeId);
 | 
			
		||||
 | 
			
		||||
        STEP("Check the node is not held");
 | 
			
		||||
        assertFalse(hasAspect(nodeId, FROZEN_ASPECT));
 | 
			
		||||
 | 
			
		||||
        STEP("Check node is not in any hold");
 | 
			
		||||
        RestNodeAssociationModelCollection holdsEntries = getRestAPIFactory()
 | 
			
		||||
            .getNodeAPI(CoreUtil.toContentModel(nodeId)).usingParams("where=(assocType='rma:frozenContent')")
 | 
			
		||||
            .getParents();
 | 
			
		||||
        assertTrue(holdsEntries.getEntries().isEmpty(), "Content held is still added to a hold.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given active content that is held on many holds
 | 
			
		||||
     * When I use the existing REST API to remove the active content from one hold
 | 
			
		||||
     * Then the active content is removed from the specific hold
 | 
			
		||||
     * And is frozen
 | 
			
		||||
     * And in the other holds
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void removeContentAddedToManyHolds() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Remove content from hold. ");
 | 
			
		||||
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(getAdminUser())
 | 
			
		||||
            .deleteHoldChild(holdNodeRefOne, contentAddToManyHolds.getNodeRefWithoutVersion());
 | 
			
		||||
 | 
			
		||||
        STEP("Check the content is held. ");
 | 
			
		||||
        assertTrue(hasAspect(contentAddToManyHolds.getNodeRefWithoutVersion(), FROZEN_ASPECT));
 | 
			
		||||
 | 
			
		||||
        STEP("Check node is in hold HOLD_TWO. ");
 | 
			
		||||
 | 
			
		||||
        RestNodeAssociationModelCollection holdsEntries = getRestAPIFactory()
 | 
			
		||||
            .getNodeAPI(CoreUtil.toContentModel(contentAddToManyHolds.getNodeRefWithoutVersion()))
 | 
			
		||||
            .usingParams("where=(assocType='rma:frozenContent')").getParents();
 | 
			
		||||
        assertFalse(holdsEntries.getEntries().isEmpty(), "Content held is not held after removing from one hold.");
 | 
			
		||||
        assertTrue(holdsEntries.getEntries().stream()
 | 
			
		||||
                .anyMatch(restNodeModel -> restNodeModel.getModel().getName().equals(HOLD_TWO)),
 | 
			
		||||
            "Content held is not held after removing from one hold.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Data provider with user without right permission or capability to remove from hold a specific node
 | 
			
		||||
     *
 | 
			
		||||
     * @return user model and the node ref to be removed from hold
 | 
			
		||||
     */
 | 
			
		||||
    @DataProvider(name = "userWithoutPermissionForRemoveFromHold")
 | 
			
		||||
    public Object[][] getUserWithoutPermissionForAddToHold()
 | 
			
		||||
    {
 | 
			
		||||
        //create record folder
 | 
			
		||||
        RecordCategoryChild recordFolder = createCategoryFolderInFilePlan();
 | 
			
		||||
        nodesToBeClean.add(recordFolder.getParentId());
 | 
			
		||||
        UserModel user = roleService.createUserWithRMRole(ROLE_RM_MANAGER.roleId);
 | 
			
		||||
        getRestAPIFactory().getRMUserAPI().addUserPermission(holdNodeRefOne, user, PERMISSION_FILING);
 | 
			
		||||
        //create files that will be removed from hold
 | 
			
		||||
        FileModel contentNoHoldPerm = dataContent.usingAdmin().usingSite(testSite)
 | 
			
		||||
            .createContent(CMISUtil.DocumentType.TEXT_PLAIN);
 | 
			
		||||
        FileModel contentNoHoldCap = dataContent.usingAdmin().usingSite(testSite)
 | 
			
		||||
            .createContent(CMISUtil.DocumentType.TEXT_PLAIN);
 | 
			
		||||
        FileModel privateFile = dataContent.usingAdmin().usingSite(privateSite)
 | 
			
		||||
            .createContent(CMISUtil.DocumentType.TEXT_PLAIN);
 | 
			
		||||
        //add files to hold
 | 
			
		||||
        asList(recordFolder.getId(), contentNoHoldCap.getNodeRefWithoutVersion(),
 | 
			
		||||
            contentNoHoldPerm.getNodeRefWithoutVersion(), privateFile.getNodeRefWithoutVersion())
 | 
			
		||||
            .forEach(id -> getRestAPIFactory()
 | 
			
		||||
                .getHoldsAPI(getAdminUser())
 | 
			
		||||
                .addChildToHold(HoldChild.builder().id(id).build(), holdNodeRefOne));
 | 
			
		||||
 | 
			
		||||
        return new Object[][]
 | 
			
		||||
            {
 | 
			
		||||
                // user with read permission on the content, with remove from hold capability and without
 | 
			
		||||
                // filling permission on a hold
 | 
			
		||||
                {
 | 
			
		||||
                    roleService.createUserWithSiteRoleRMRoleAndPermission(testSite, UserRole.SiteCollaborator,
 | 
			
		||||
                        holdNodeRefOne, UserRoles.ROLE_RM_MANAGER, PERMISSION_READ_RECORDS),
 | 
			
		||||
                    contentNoHoldPerm.getNodeRefWithoutVersion()
 | 
			
		||||
                },
 | 
			
		||||
                // user with write permission on the content, filling permission on a hold without remove from
 | 
			
		||||
                // hold capability
 | 
			
		||||
                {
 | 
			
		||||
                    roleService.createUserWithSiteRoleRMRoleAndPermission(testSite, UserRole
 | 
			
		||||
                            .SiteCollaborator,
 | 
			
		||||
                        holdNodeRefOne, UserRoles.ROLE_RM_POWER_USER, PERMISSION_FILING),
 | 
			
		||||
                    contentNoHoldCap.getNodeRefWithoutVersion()
 | 
			
		||||
                },
 | 
			
		||||
                //user without read permission on RM  record folder
 | 
			
		||||
                {
 | 
			
		||||
                    user, recordFolder.getId()
 | 
			
		||||
                },
 | 
			
		||||
                //user without read permission over the content from the private site
 | 
			
		||||
                {
 | 
			
		||||
                    user, privateFile.getNodeRefWithoutVersion()
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given node on hold in a single hold location
 | 
			
		||||
     * And the user does not have sufficient permissions or capabilities to remove the node from the hold
 | 
			
		||||
     * When the user tries to remove the node from the hold
 | 
			
		||||
     * Then it's unsuccessful
 | 
			
		||||
     *
 | 
			
		||||
     * @throws Exception
 | 
			
		||||
     */
 | 
			
		||||
    @Test(dataProvider = "userWithoutPermissionForRemoveFromHold")
 | 
			
		||||
    public void removeFromHoldWithUserWithoutPermission(UserModel userModel, String nodeIdToBeRemoved) throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Update the list of users to be deleted after running the tests");
 | 
			
		||||
        usersToBeClean.add(userModel);
 | 
			
		||||
 | 
			
		||||
        STEP("Remove node from hold with user without right permission or capability");
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(userModel).deleteHoldChild(holdNodeRefOne, nodeIdToBeRemoved);
 | 
			
		||||
 | 
			
		||||
        assertStatusCode(FORBIDDEN);
 | 
			
		||||
        getRestAPIFactory().getRmRestWrapper().assertLastError().containsSummary(ACCESS_DENIED_ERROR_MESSAGE);
 | 
			
		||||
 | 
			
		||||
        STEP("Check node is frozen.");
 | 
			
		||||
        assertTrue(hasAspect(nodeIdToBeRemoved, FROZEN_ASPECT));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Data provider with user with right permission or capability to remove from hold a specific node
 | 
			
		||||
     *
 | 
			
		||||
     * @return user model and the node ref to be removed from hold
 | 
			
		||||
     */
 | 
			
		||||
    @DataProvider(name = "userWithPermissionForRemoveFromHold")
 | 
			
		||||
    public Object[][] getUserWithPermissionForAddToHold()
 | 
			
		||||
    {
 | 
			
		||||
        //create record folder
 | 
			
		||||
        RecordCategoryChild recordFolder = createCategoryFolderInFilePlan();
 | 
			
		||||
        nodesToBeClean.add(recordFolder.getParentId());
 | 
			
		||||
        UserModel user = roleService.createUserWithRMRoleAndRMNodePermission(ROLE_RM_MANAGER.roleId,
 | 
			
		||||
            recordFolder.getId(),
 | 
			
		||||
            PERMISSION_READ_RECORDS);
 | 
			
		||||
        getRestAPIFactory().getRMUserAPI().addUserPermission(holdNodeRefOne, user, PERMISSION_FILING);
 | 
			
		||||
        //create file that will be removed from hold
 | 
			
		||||
        FileModel contentPermission = dataContent.usingAdmin().usingSite(testSite)
 | 
			
		||||
            .createContent(CMISUtil.DocumentType.TEXT_PLAIN);
 | 
			
		||||
 | 
			
		||||
        //add files to hold
 | 
			
		||||
        asList(recordFolder.getId(), contentPermission.getNodeRefWithoutVersion())
 | 
			
		||||
            .forEach(id -> getRestAPIFactory()
 | 
			
		||||
                .getHoldsAPI(getAdminUser())
 | 
			
		||||
                .addChildToHold(HoldChild.builder().id(id).build(), holdNodeRefOne));
 | 
			
		||||
 | 
			
		||||
        return new Object[][]
 | 
			
		||||
            {
 | 
			
		||||
                // user with write permission on the content
 | 
			
		||||
                {
 | 
			
		||||
                    roleService.createUserWithSiteRoleRMRoleAndPermission(testSite, UserRole.SiteConsumer,
 | 
			
		||||
                        holdNodeRefOne, UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING),
 | 
			
		||||
                    contentPermission.getNodeRefWithoutVersion()
 | 
			
		||||
                },
 | 
			
		||||
                //user with read permission on RM  record folder
 | 
			
		||||
                {
 | 
			
		||||
                    user, recordFolder.getId()
 | 
			
		||||
                },
 | 
			
		||||
 | 
			
		||||
            };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test(dataProvider = "userWithPermissionForRemoveFromHold")
 | 
			
		||||
    public void removeFromHoldWithUserWithPermission(UserModel userModel, String nodeIdToBeRemoved) throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        STEP("Update the list of users to be deleted after running the tests");
 | 
			
		||||
        usersToBeClean.add(userModel);
 | 
			
		||||
 | 
			
		||||
        STEP("Remove node from hold with user with right permission and capability");
 | 
			
		||||
        getRestAPIFactory().getHoldsAPI(userModel).deleteHoldChild(holdNodeRefOne, nodeIdToBeRemoved);
 | 
			
		||||
 | 
			
		||||
        STEP("Check node is not frozen.");
 | 
			
		||||
        assertFalse(hasAspect(nodeIdToBeRemoved, FROZEN_ASPECT));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Hold createHold(String parentId, Hold hold, UserModel user)
 | 
			
		||||
    {
 | 
			
		||||
        FilePlanAPI filePlanAPI = getRestAPIFactory().getFilePlansAPI(user);
 | 
			
		||||
        return filePlanAPI.createHold(hold, parentId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @AfterClass(alwaysRun = true)
 | 
			
		||||
    public void cleanUpRemoveContentFromHold()
 | 
			
		||||
    {
 | 
			
		||||
        holdsListRef.forEach(holdRef -> getRestAPIFactory().getHoldsAPI(getAdminUser()).deleteHold(holdRef));
 | 
			
		||||
        dataSite.usingAdmin().deleteSite(testSite);
 | 
			
		||||
        dataSite.usingAdmin().deleteSite(privateSite);
 | 
			
		||||
        usersToBeClean.forEach(user -> getDataUser().usingAdmin().deleteUser(user));
 | 
			
		||||
        nodesToBeClean.forEach(this::deleteRecordCategory);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,377 @@
 | 
			
		||||
/*-
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.rm.community.retentionschedule;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.rest.rm.community.base.BaseRMRestTest;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.recordcategory.RecordCategory;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.retentionschedule.RetentionSchedule;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.retentionschedule.RetentionScheduleActionDefinition;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.retentionschedule.RetentionScheduleStepCollection;
 | 
			
		||||
import org.alfresco.rest.v0.RMRolesAndActionsAPI;
 | 
			
		||||
import org.alfresco.utility.model.UserModel;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
			
		||||
import org.testng.annotations.AfterClass;
 | 
			
		||||
import org.testng.annotations.BeforeClass;
 | 
			
		||||
import org.testng.annotations.Test;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import static org.alfresco.rest.core.v0.BaseAPI.RM_SITE_ID;
 | 
			
		||||
import static org.alfresco.utility.data.RandomData.getRandomAlphanumeric;
 | 
			
		||||
import static org.alfresco.utility.data.RandomData.getRandomName;
 | 
			
		||||
import static org.springframework.http.HttpStatus.BAD_REQUEST;
 | 
			
		||||
import static org.springframework.http.HttpStatus.CONFLICT;
 | 
			
		||||
import static org.springframework.http.HttpStatus.CREATED;
 | 
			
		||||
import static org.springframework.http.HttpStatus.FORBIDDEN;
 | 
			
		||||
import static org.springframework.http.HttpStatus.NOT_FOUND;
 | 
			
		||||
import static org.springframework.http.HttpStatus.OK;
 | 
			
		||||
import static org.springframework.http.HttpStatus.UNAUTHORIZED;
 | 
			
		||||
import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY;
 | 
			
		||||
import static org.testng.Assert.assertNotNull;
 | 
			
		||||
import static org.testng.AssertJUnit.assertEquals;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Retention schedule step test case
 | 
			
		||||
 */
 | 
			
		||||
public class RetentionScheduleStepTests extends BaseRMRestTest
 | 
			
		||||
{
 | 
			
		||||
    private RecordCategory recordCategory;
 | 
			
		||||
    private RetentionSchedule createdRetentionSchedule;
 | 
			
		||||
    private final RetentionScheduleActionDefinition retentionScheduleActionDefinition = new RetentionScheduleActionDefinition();
 | 
			
		||||
    private RetentionScheduleActionDefinition createdRetentionActionDefinition;
 | 
			
		||||
    private UserModel nonRMuser;
 | 
			
		||||
    private final List<String> recordCategories = new ArrayList<>();
 | 
			
		||||
    private static final String TEST_USER = "testUser";
 | 
			
		||||
    private static final String RECORD_CATEGORY = "recordCategory";
 | 
			
		||||
    private static final String PERIOD_PROPERTY = "cm:created";
 | 
			
		||||
    private static final String AUTHORITY = "authority";
 | 
			
		||||
    private static final String INSTRUCTIONS = "instructions";
 | 
			
		||||
    private static final int PERIOD_AMOUNT = 5;
 | 
			
		||||
    private static final String PERIOD = "month";
 | 
			
		||||
    private static final List<String> EVENTS = Arrays.asList("case_closed","abolished");
 | 
			
		||||
    private static final String TRANSFER_STEP = "transfer";
 | 
			
		||||
    private static final String RETAIN_STEP = "retain";
 | 
			
		||||
    private static final String INVALID_PERIOD = "random";
 | 
			
		||||
    private static final String CUTOFF_STEP = "cutoff";
 | 
			
		||||
    private static final String DESTROY_STEP = "destroyContent";
 | 
			
		||||
    private static final String INVALID_PASSWORD = "wrongPassword";
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private RMRolesAndActionsAPI rmRolesAndActionsAPI;
 | 
			
		||||
 | 
			
		||||
    @BeforeClass(alwaysRun = true)
 | 
			
		||||
    public void preconditionForRetentionScheduleStepTests()
 | 
			
		||||
    {
 | 
			
		||||
        createRMSiteIfNotExists();
 | 
			
		||||
        // create a non rm user
 | 
			
		||||
        nonRMuser = dataUser.createRandomTestUser(TEST_USER);
 | 
			
		||||
        //Create record category
 | 
			
		||||
        recordCategory = createRootCategory(getRandomName(RECORD_CATEGORY));
 | 
			
		||||
        recordCategories.add(recordCategory.getId());
 | 
			
		||||
        RetentionSchedule retentionSchedule = new RetentionSchedule();
 | 
			
		||||
        retentionSchedule.setAuthority(AUTHORITY + getRandomAlphanumeric());
 | 
			
		||||
        retentionSchedule.setInstructions(INSTRUCTIONS + getRandomAlphanumeric());
 | 
			
		||||
        retentionSchedule.setIsRecordLevel(false);
 | 
			
		||||
        //Create retention schedule with a valid user
 | 
			
		||||
        createdRetentionSchedule = getRestAPIFactory().getRetentionScheduleAPI()
 | 
			
		||||
            .createRetentionSchedule(retentionSchedule, recordCategory.getId());
 | 
			
		||||
 | 
			
		||||
        retentionScheduleActionDefinition.setName(RETAIN_STEP);
 | 
			
		||||
        retentionScheduleActionDefinition.setDescription(INSTRUCTIONS);
 | 
			
		||||
        retentionScheduleActionDefinition.setPeriodAmount(PERIOD_AMOUNT);
 | 
			
		||||
        retentionScheduleActionDefinition.setPeriodProperty(PERIOD_PROPERTY);
 | 
			
		||||
        retentionScheduleActionDefinition.setPeriod(PERIOD);
 | 
			
		||||
        retentionScheduleActionDefinition.setCombineRetentionStepConditions(false);
 | 
			
		||||
        retentionScheduleActionDefinition.setEligibleOnFirstCompleteEvent(true);
 | 
			
		||||
        retentionScheduleActionDefinition.setEvents(EVENTS);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test(priority = 1)
 | 
			
		||||
    public void createRetentionScheduleStepFor422()
 | 
			
		||||
    {
 | 
			
		||||
        RetentionScheduleActionDefinition actionDefinition = getRetentionScheduleActionDefinition();
 | 
			
		||||
        //Creating the first action "transfer" should give 422
 | 
			
		||||
        actionDefinition.setName(TRANSFER_STEP);
 | 
			
		||||
        actionDefinition.setLocation("location");
 | 
			
		||||
        //Create retention schedule action definition
 | 
			
		||||
        getRestAPIFactory().getRetentionScheduleAPI().createRetentionScheduleStep(actionDefinition,createdRetentionSchedule.getId());
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(UNPROCESSABLE_ENTITY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test(priority = 2)
 | 
			
		||||
    public void createRetentionScheduleStepWithInvalidPeriodValue()
 | 
			
		||||
    {
 | 
			
		||||
        RetentionScheduleActionDefinition actionDefinition = getRetentionScheduleActionDefinition();
 | 
			
		||||
        actionDefinition.setName(RETAIN_STEP);
 | 
			
		||||
        //Invalid period value
 | 
			
		||||
        actionDefinition.setPeriod(INVALID_PERIOD);
 | 
			
		||||
        //Create retention schedule action definition
 | 
			
		||||
        getRestAPIFactory().getRetentionScheduleAPI().createRetentionScheduleStep(actionDefinition,createdRetentionSchedule.getId());
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(BAD_REQUEST);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test(priority = 3)
 | 
			
		||||
    public void createRetentionScheduleWithInvalidStep()
 | 
			
		||||
    {
 | 
			
		||||
        RecordCategory recordCategory = createRootCategory(getRandomName(RECORD_CATEGORY));
 | 
			
		||||
        recordCategories.add(recordCategory.getId());
 | 
			
		||||
        RetentionSchedule retentionSchedule = createRetentionSchedule(recordCategory);
 | 
			
		||||
        RetentionScheduleActionDefinition actionDefinition = getRetentionScheduleActionDefinition();
 | 
			
		||||
        actionDefinition.setName(RETAIN_STEP);
 | 
			
		||||
        getRestAPIFactory().getRetentionScheduleAPI().createRetentionScheduleStep(actionDefinition,retentionSchedule.getId());
 | 
			
		||||
 | 
			
		||||
        assertStatusCode(CREATED);
 | 
			
		||||
 | 
			
		||||
        RetentionScheduleActionDefinition actionDefinition1 = getRetentionScheduleActionDefinition();
 | 
			
		||||
        actionDefinition1.setName(TRANSFER_STEP);
 | 
			
		||||
        actionDefinition1.setLocation("location");
 | 
			
		||||
        getRestAPIFactory().getRetentionScheduleAPI().createRetentionScheduleStep(actionDefinition1,retentionSchedule.getId());
 | 
			
		||||
 | 
			
		||||
        assertStatusCode(CREATED);
 | 
			
		||||
 | 
			
		||||
        RetentionScheduleActionDefinition actionDefinition2 = getRetentionScheduleActionDefinition();
 | 
			
		||||
        actionDefinition2.setName(CUTOFF_STEP);
 | 
			
		||||
        //Create retention schedule action definition
 | 
			
		||||
        getRestAPIFactory().getRetentionScheduleAPI().createRetentionScheduleStep(actionDefinition2,retentionSchedule.getId());
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(CONFLICT);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test(priority = 4)
 | 
			
		||||
    public void createRetentionScheduleWithInvalidStepAfterDestroy()
 | 
			
		||||
    {
 | 
			
		||||
        RecordCategory recordCategory = createRootCategory(getRandomName(RECORD_CATEGORY));
 | 
			
		||||
        recordCategories.add(recordCategory.getId());
 | 
			
		||||
        RetentionSchedule retentionSchedule = createRetentionSchedule(recordCategory);
 | 
			
		||||
        RetentionScheduleActionDefinition actionDefinition = getRetentionScheduleActionDefinition();
 | 
			
		||||
        actionDefinition.setName(RETAIN_STEP);
 | 
			
		||||
        getRestAPIFactory().getRetentionScheduleAPI().createRetentionScheduleStep(actionDefinition,retentionSchedule.getId());
 | 
			
		||||
 | 
			
		||||
        assertStatusCode(CREATED);
 | 
			
		||||
 | 
			
		||||
        RetentionScheduleActionDefinition actionDefinition1 = getRetentionScheduleActionDefinition();
 | 
			
		||||
        actionDefinition1.setName(DESTROY_STEP);
 | 
			
		||||
        getRestAPIFactory().getRetentionScheduleAPI().createRetentionScheduleStep(actionDefinition1,retentionSchedule.getId());
 | 
			
		||||
 | 
			
		||||
        assertStatusCode(CREATED);
 | 
			
		||||
 | 
			
		||||
        RetentionScheduleActionDefinition actionDefinition2 = getRetentionScheduleActionDefinition();
 | 
			
		||||
        actionDefinition2.setName(CUTOFF_STEP);
 | 
			
		||||
        //Create retention schedule action definition
 | 
			
		||||
        getRestAPIFactory().getRetentionScheduleAPI().createRetentionScheduleStep(actionDefinition2,retentionSchedule.getId());
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(CONFLICT);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test(priority = 5)
 | 
			
		||||
    public void combineRetentionStepConditionsNotValidForNonAccessionStep()
 | 
			
		||||
    {
 | 
			
		||||
        RecordCategory recordCategory = createRootCategory(getRandomName(RECORD_CATEGORY));
 | 
			
		||||
        recordCategories.add(recordCategory.getId());
 | 
			
		||||
        RetentionSchedule retentionSchedule = createRetentionSchedule(recordCategory);
 | 
			
		||||
        RetentionScheduleActionDefinition actionDefinition = getRetentionScheduleActionDefinition();
 | 
			
		||||
        actionDefinition.setName(RETAIN_STEP);
 | 
			
		||||
        actionDefinition.setCombineRetentionStepConditions(true);
 | 
			
		||||
        getRestAPIFactory().getRetentionScheduleAPI().createRetentionScheduleStep(actionDefinition,retentionSchedule.getId());
 | 
			
		||||
 | 
			
		||||
        assertStatusCode(BAD_REQUEST);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test(priority = 6)
 | 
			
		||||
    public void createRetentionScheduleWithSameStep()
 | 
			
		||||
    {
 | 
			
		||||
        RecordCategory recordCategory = createRootCategory(getRandomName(RECORD_CATEGORY));
 | 
			
		||||
        recordCategories.add(recordCategory.getId());
 | 
			
		||||
        RetentionSchedule retentionSchedule = createRetentionSchedule(recordCategory);
 | 
			
		||||
        RetentionScheduleActionDefinition actionDefinition = getRetentionScheduleActionDefinition();
 | 
			
		||||
        actionDefinition.setName(RETAIN_STEP);
 | 
			
		||||
        getRestAPIFactory().getRetentionScheduleAPI().createRetentionScheduleStep(actionDefinition,retentionSchedule.getId());
 | 
			
		||||
 | 
			
		||||
        assertStatusCode(CREATED);
 | 
			
		||||
 | 
			
		||||
        RetentionScheduleActionDefinition actionDefinition1 = getRetentionScheduleActionDefinition();
 | 
			
		||||
        actionDefinition1.setName(RETAIN_STEP);
 | 
			
		||||
        getRestAPIFactory().getRetentionScheduleAPI().createRetentionScheduleStep(actionDefinition1,retentionSchedule.getId());
 | 
			
		||||
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(CONFLICT);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test(priority = 7)
 | 
			
		||||
    public void createRetentionScheduleWithMultipleTransferStep()
 | 
			
		||||
    {
 | 
			
		||||
        RecordCategory recordCategory = createRootCategory(getRandomName(RECORD_CATEGORY));
 | 
			
		||||
        recordCategories.add(recordCategory.getId());
 | 
			
		||||
        RetentionSchedule retentionSchedule = createRetentionSchedule(recordCategory);
 | 
			
		||||
        RetentionScheduleActionDefinition actionDefinition = getRetentionScheduleActionDefinition();
 | 
			
		||||
        actionDefinition.setName(RETAIN_STEP);
 | 
			
		||||
        getRestAPIFactory().getRetentionScheduleAPI().createRetentionScheduleStep(actionDefinition,retentionSchedule.getId());
 | 
			
		||||
 | 
			
		||||
        assertStatusCode(CREATED);
 | 
			
		||||
 | 
			
		||||
        RetentionScheduleActionDefinition actionDefinition1 = getRetentionScheduleActionDefinition();
 | 
			
		||||
        actionDefinition1.setName(TRANSFER_STEP);
 | 
			
		||||
        actionDefinition1.setLocation("location");
 | 
			
		||||
 | 
			
		||||
        getRestAPIFactory().getRetentionScheduleAPI().createRetentionScheduleStep(actionDefinition1, retentionSchedule.getId());
 | 
			
		||||
 | 
			
		||||
        RetentionScheduleActionDefinition actionDefinition2 = getRetentionScheduleActionDefinition();
 | 
			
		||||
        actionDefinition2.setName(TRANSFER_STEP);
 | 
			
		||||
        actionDefinition2.setLocation("location");
 | 
			
		||||
        getRestAPIFactory().getRetentionScheduleAPI().createRetentionScheduleStep(actionDefinition2, retentionSchedule.getId());
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(CREATED);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test(priority = 8)
 | 
			
		||||
    public void createRetentionScheduleStepFor201()
 | 
			
		||||
    {
 | 
			
		||||
        //Create retention schedule action definition
 | 
			
		||||
        createdRetentionActionDefinition = getRestAPIFactory().getRetentionScheduleAPI().createRetentionScheduleStep(retentionScheduleActionDefinition,createdRetentionSchedule.getId());
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(CREATED);
 | 
			
		||||
        // Find this retention schedule is created one or not
 | 
			
		||||
        assertEquals(createdRetentionActionDefinition.getName(), retentionScheduleActionDefinition.getName());
 | 
			
		||||
        assertEquals(createdRetentionActionDefinition.getDescription(), retentionScheduleActionDefinition.getDescription());
 | 
			
		||||
        assertEquals(createdRetentionActionDefinition.getPeriodAmount(), retentionScheduleActionDefinition.getPeriodAmount());
 | 
			
		||||
        assertEquals(createdRetentionActionDefinition.isCombineRetentionStepConditions(), retentionScheduleActionDefinition.isCombineRetentionStepConditions());
 | 
			
		||||
        assertEquals(createdRetentionActionDefinition.isEligibleOnFirstCompleteEvent(), retentionScheduleActionDefinition.isEligibleOnFirstCompleteEvent());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test(priority = 9)
 | 
			
		||||
    public void createRetentionScheduleStepFor401()
 | 
			
		||||
    {
 | 
			
		||||
        //Create retention schedule action definition
 | 
			
		||||
        getRestAPIFactory().getRetentionScheduleAPI(new UserModel(getAdminUser().getUsername(), INVALID_PASSWORD)).createRetentionScheduleStep(retentionScheduleActionDefinition,createdRetentionSchedule.getId());
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(UNAUTHORIZED);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test(priority = 10)
 | 
			
		||||
    public void createRetentionScheduleStepFor403()
 | 
			
		||||
    {
 | 
			
		||||
        //Create retention schedule action definition
 | 
			
		||||
        getRestAPIFactory().getRetentionScheduleAPI(nonRMuser).createRetentionScheduleStep(retentionScheduleActionDefinition,createdRetentionSchedule.getId());
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(FORBIDDEN);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test(priority = 11)
 | 
			
		||||
    public void retentionScheduleStepFor400()
 | 
			
		||||
    {
 | 
			
		||||
        getRestAPIFactory().getRetentionScheduleAPI().getRetentionScheduleStep(recordCategory.getId());
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(BAD_REQUEST);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test(priority = 12)
 | 
			
		||||
    public void createRetentionScheduleStepFor404()
 | 
			
		||||
    {
 | 
			
		||||
        //Create retention schedule action definition
 | 
			
		||||
        getRestAPIFactory().getRetentionScheduleAPI().createRetentionScheduleStep(retentionScheduleActionDefinition,getRandomAlphanumeric());
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(NOT_FOUND);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test(priority = 13)
 | 
			
		||||
    public void retentionScheduleStepFor403()
 | 
			
		||||
    {
 | 
			
		||||
        // Get retention schedule steps with user having no rights
 | 
			
		||||
        getRestAPIFactory().getRetentionScheduleAPI(nonRMuser).getRetentionScheduleStep(createdRetentionSchedule.getId());
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(FORBIDDEN);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test(priority = 14)
 | 
			
		||||
    public void retentionScheduleStepFor401()
 | 
			
		||||
    {
 | 
			
		||||
        getRestAPIFactory().getRetentionScheduleAPI(new UserModel(getAdminUser().getUsername(), INVALID_PASSWORD)).getRetentionScheduleStep(createdRetentionSchedule.getId());
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(UNAUTHORIZED);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test(priority = 15)
 | 
			
		||||
    public void retentionScheduleStepWith200()
 | 
			
		||||
    {
 | 
			
		||||
        RetentionScheduleStepCollection receiveRetentionStepCollection = getRestAPIFactory().getRetentionScheduleAPI().getRetentionScheduleStep(createdRetentionSchedule.getId());
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(OK);
 | 
			
		||||
        receiveRetentionStepCollection.getEntries().forEach(c ->
 | 
			
		||||
        {
 | 
			
		||||
            RetentionScheduleActionDefinition retentionActionDef = c.getEntry();
 | 
			
		||||
            assertNotNull(retentionActionDef.getId());
 | 
			
		||||
            // Find this retention schedule is created one or not
 | 
			
		||||
            assertEquals(createdRetentionActionDefinition.getId(), retentionActionDef.getId());
 | 
			
		||||
            assertEquals(createdRetentionActionDefinition.getName(), retentionActionDef.getName());
 | 
			
		||||
            assertEquals(createdRetentionActionDefinition.getDescription(), retentionActionDef.getDescription());
 | 
			
		||||
            assertEquals(createdRetentionActionDefinition.getPeriod(), retentionActionDef.getPeriod());
 | 
			
		||||
            assertEquals(createdRetentionActionDefinition.getPeriodAmount(), retentionActionDef.getPeriodAmount());
 | 
			
		||||
            assertEquals(createdRetentionActionDefinition.isCombineRetentionStepConditions(), retentionActionDef.isCombineRetentionStepConditions());
 | 
			
		||||
            assertEquals(createdRetentionActionDefinition.isEligibleOnFirstCompleteEvent(), retentionActionDef.isEligibleOnFirstCompleteEvent());
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private RetentionSchedule createRetentionSchedule(RecordCategory recordCategory)
 | 
			
		||||
    {
 | 
			
		||||
        RetentionSchedule retentionSchedule = new RetentionSchedule();
 | 
			
		||||
        retentionSchedule.setAuthority(AUTHORITY + getRandomAlphanumeric());
 | 
			
		||||
        retentionSchedule.setInstructions(INSTRUCTIONS + getRandomAlphanumeric());
 | 
			
		||||
        retentionSchedule.setIsRecordLevel(false);
 | 
			
		||||
        //Create retention schedule with a valid user
 | 
			
		||||
        retentionSchedule = getRestAPIFactory().getRetentionScheduleAPI()
 | 
			
		||||
            .createRetentionSchedule(retentionSchedule, recordCategory.getId());
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(CREATED);
 | 
			
		||||
        return retentionSchedule;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static RetentionScheduleActionDefinition getRetentionScheduleActionDefinition()
 | 
			
		||||
    {
 | 
			
		||||
        RetentionScheduleActionDefinition actionDefinition = new RetentionScheduleActionDefinition();
 | 
			
		||||
        actionDefinition.setDescription(INSTRUCTIONS);
 | 
			
		||||
        actionDefinition.setPeriodAmount(PERIOD_AMOUNT);
 | 
			
		||||
        actionDefinition.setPeriodProperty(PERIOD_PROPERTY);
 | 
			
		||||
        actionDefinition.setPeriod(PERIOD);
 | 
			
		||||
        actionDefinition.setCombineRetentionStepConditions(false);
 | 
			
		||||
        actionDefinition.setEligibleOnFirstCompleteEvent(true);
 | 
			
		||||
        actionDefinition.setEvents(EVENTS);
 | 
			
		||||
        return actionDefinition;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @AfterClass(alwaysRun = true)
 | 
			
		||||
    public void cleanUpRetentionScheduleStepTests()
 | 
			
		||||
    {
 | 
			
		||||
        rmRolesAndActionsAPI.deleteAllItemsInContainer(getDataUser().usingAdmin().getAdminUser().getUsername(),
 | 
			
		||||
            getDataUser().usingAdmin().getAdminUser().getPassword(), RM_SITE_ID, recordCategory.getName());
 | 
			
		||||
        recordCategories.forEach(this::deleteRecordCategory);
 | 
			
		||||
        dataUser.deleteUser(nonRMuser);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,270 @@
 | 
			
		||||
/*-
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rest.rm.community.retentionschedule;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.rest.rm.community.base.BaseRMRestTest;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.recordcategory.RecordCategory;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.retentionschedule.RetentionSchedule;
 | 
			
		||||
import org.alfresco.rest.rm.community.model.retentionschedule.RetentionScheduleCollection;
 | 
			
		||||
import org.alfresco.rest.v0.RMRolesAndActionsAPI;
 | 
			
		||||
import org.alfresco.utility.model.UserModel;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
			
		||||
import org.testng.annotations.AfterClass;
 | 
			
		||||
import org.testng.annotations.BeforeClass;
 | 
			
		||||
import org.testng.annotations.Test;
 | 
			
		||||
 | 
			
		||||
import static org.alfresco.rest.core.v0.BaseAPI.RM_SITE_ID;
 | 
			
		||||
import static org.alfresco.utility.data.RandomData.getRandomAlphanumeric;
 | 
			
		||||
import static org.alfresco.utility.data.RandomData.getRandomName;
 | 
			
		||||
import static org.springframework.http.HttpStatus.CONFLICT;
 | 
			
		||||
import static org.springframework.http.HttpStatus.CREATED;
 | 
			
		||||
import static org.springframework.http.HttpStatus.FORBIDDEN;
 | 
			
		||||
import static org.springframework.http.HttpStatus.NOT_FOUND;
 | 
			
		||||
import static org.springframework.http.HttpStatus.OK;
 | 
			
		||||
import static org.springframework.http.HttpStatus.UNAUTHORIZED;
 | 
			
		||||
import static org.testng.Assert.assertFalse;
 | 
			
		||||
import static org.testng.Assert.assertNotNull;
 | 
			
		||||
import static org.testng.AssertJUnit.assertEquals;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class contains the tests for the Retention Schedule CRUD V1 API
 | 
			
		||||
 */
 | 
			
		||||
public class RetentionScheduleTests extends BaseRMRestTest
 | 
			
		||||
{
 | 
			
		||||
    private RecordCategory recordCategory;
 | 
			
		||||
    private RetentionSchedule createdRetentionSchedule;
 | 
			
		||||
    private UserModel nonRMuser;
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private RMRolesAndActionsAPI rmRolesAndActionsAPI;
 | 
			
		||||
 | 
			
		||||
    @BeforeClass(alwaysRun = true)
 | 
			
		||||
    public void preconditionForRetentionScheduleTests()
 | 
			
		||||
    {
 | 
			
		||||
        createRMSiteIfNotExists();
 | 
			
		||||
        // create a non rm user
 | 
			
		||||
        nonRMuser = dataUser.createRandomTestUser("testUser");
 | 
			
		||||
        //Create record category
 | 
			
		||||
        recordCategory = createRootCategory(getRandomName("recordCategory"));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * <pre>
 | 
			
		||||
     * Given that a record category exists
 | 
			
		||||
     * When I ask the API to create a retention schedule with a user having no rights
 | 
			
		||||
     * Then it will give 403 as status code
 | 
			
		||||
     * </pre>
 | 
			
		||||
     */
 | 
			
		||||
    @Test(priority = 1)
 | 
			
		||||
    public void createRetentionScheduleFor403()
 | 
			
		||||
    {
 | 
			
		||||
        RetentionSchedule retentionSchedule = new RetentionSchedule();
 | 
			
		||||
 | 
			
		||||
        // Create retention schedule with user having no rights
 | 
			
		||||
        getRestAPIFactory().getRetentionScheduleAPI(nonRMuser).createRetentionSchedule(retentionSchedule, recordCategory.getId());
 | 
			
		||||
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(FORBIDDEN);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * <pre>
 | 
			
		||||
     * Given that a record category does not exists
 | 
			
		||||
     * When I ask the API to create a retention schedule on a category Id
 | 
			
		||||
     * Then it will give 404 as a status code
 | 
			
		||||
     * </pre>
 | 
			
		||||
     */
 | 
			
		||||
    @Test(priority = 2)
 | 
			
		||||
    public void createRetentionScheduleFor404()
 | 
			
		||||
    {
 | 
			
		||||
        RetentionSchedule retentionSchedule = new RetentionSchedule();
 | 
			
		||||
 | 
			
		||||
        //Create retention schedule with category id not exist
 | 
			
		||||
        getRestAPIFactory().getRetentionScheduleAPI().createRetentionSchedule(retentionSchedule, getRandomAlphanumeric());
 | 
			
		||||
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(NOT_FOUND);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * <pre>
 | 
			
		||||
     * Given that a record category exists
 | 
			
		||||
     * When I ask the API to create a retention schedule on a category id with a user having unauthorized access
 | 
			
		||||
     * Then it will give 401 as a status code
 | 
			
		||||
     * </pre>
 | 
			
		||||
     */
 | 
			
		||||
    @Test(priority = 3)
 | 
			
		||||
    public void createRetentionScheduleFor401()
 | 
			
		||||
    {
 | 
			
		||||
        RetentionSchedule retentionSchedule = new RetentionSchedule();
 | 
			
		||||
 | 
			
		||||
        //Create retention schedule with a user with unauthorized access
 | 
			
		||||
        createdRetentionSchedule = getRestAPIFactory().getRetentionScheduleAPI(new UserModel(getAdminUser().getUsername(), "wrongPassword")).createRetentionSchedule(retentionSchedule, recordCategory.getId());
 | 
			
		||||
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(UNAUTHORIZED);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * <pre>
 | 
			
		||||
     * Given that a record category exists
 | 
			
		||||
     * When I ask the API to create a retention schedule with a user having access
 | 
			
		||||
     * Then it is created with a 201 status code
 | 
			
		||||
     * </pre>
 | 
			
		||||
     */
 | 
			
		||||
    @Test(priority = 4)
 | 
			
		||||
    public void createRetentionScheduleFor201()
 | 
			
		||||
    {
 | 
			
		||||
        RetentionSchedule retentionSchedule = new RetentionSchedule();
 | 
			
		||||
        String authority = "authority" + getRandomAlphanumeric();
 | 
			
		||||
        String instructions = "instructions" + getRandomAlphanumeric();
 | 
			
		||||
        boolean isRecordLevel = false;
 | 
			
		||||
        retentionSchedule.setAuthority(authority);
 | 
			
		||||
        retentionSchedule.setInstructions(instructions);
 | 
			
		||||
        retentionSchedule.setIsRecordLevel(isRecordLevel);
 | 
			
		||||
 | 
			
		||||
        //Create retention schedule with a valid user
 | 
			
		||||
        createdRetentionSchedule = getRestAPIFactory().getRetentionScheduleAPI()
 | 
			
		||||
            .createRetentionSchedule(retentionSchedule, recordCategory.getId());
 | 
			
		||||
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(CREATED);
 | 
			
		||||
        assertEquals(createdRetentionSchedule.getAuthority(), authority);
 | 
			
		||||
        assertEquals(createdRetentionSchedule.getInstructions(), instructions);
 | 
			
		||||
        assertFalse(createdRetentionSchedule.getIsRecordLevel());
 | 
			
		||||
        assertNotNull(createdRetentionSchedule.getId());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * <pre>
 | 
			
		||||
     * Given that a record category exists
 | 
			
		||||
     * When I ask the API to create a retention schedule on a category id having retention schedule already
 | 
			
		||||
     * Then it will give 409 as a status code
 | 
			
		||||
     * </pre>
 | 
			
		||||
     */
 | 
			
		||||
    @Test(priority = 5)
 | 
			
		||||
    public void createRetentionScheduleFor409()
 | 
			
		||||
    {
 | 
			
		||||
        RetentionSchedule retentionSchedule = new RetentionSchedule();
 | 
			
		||||
        //Create retention schedule on a category with already having retention schedule
 | 
			
		||||
        getRestAPIFactory().getRetentionScheduleAPI()
 | 
			
		||||
            .createRetentionSchedule(retentionSchedule, recordCategory.getId());
 | 
			
		||||
 | 
			
		||||
        assertStatusCode(CONFLICT);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * <pre>
 | 
			
		||||
     * Given that a record category exists
 | 
			
		||||
     * When I ask the API to get a retention schedule on a given categoryId with a user having no rights
 | 
			
		||||
     * Then it will give 403
 | 
			
		||||
     * </pre>
 | 
			
		||||
     */
 | 
			
		||||
    @Test(priority = 6)
 | 
			
		||||
    public void retentionScheduleWith403()
 | 
			
		||||
    {
 | 
			
		||||
        //Get retention schedule with user having no rights
 | 
			
		||||
        getRestAPIFactory().getRetentionScheduleAPI(nonRMuser).getRetentionSchedule(recordCategory.getId());
 | 
			
		||||
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(FORBIDDEN);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * <pre>
 | 
			
		||||
     * Given that a record category does not exists
 | 
			
		||||
     * When I ask the API to get a retention schedule on a category Id
 | 
			
		||||
     * Then it will give 404 as a status code
 | 
			
		||||
     * </pre>
 | 
			
		||||
     */
 | 
			
		||||
    @Test(priority = 7)
 | 
			
		||||
    public void retentionScheduleWith404()
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        //Get retention schedule with category id that does not exist
 | 
			
		||||
        getRestAPIFactory().getRetentionScheduleAPI().getRetentionSchedule(getRandomAlphanumeric());
 | 
			
		||||
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(NOT_FOUND);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * <pre>
 | 
			
		||||
     * Given that a record category exists
 | 
			
		||||
     * When I ask the API to get a retention schedule on a categoryId with a user having unauthorized access
 | 
			
		||||
     * Then it will give 401 as a status code
 | 
			
		||||
     * </pre>
 | 
			
		||||
     */
 | 
			
		||||
    @Test(priority = 8)
 | 
			
		||||
    public void retentionScheduleWith401()
 | 
			
		||||
    {
 | 
			
		||||
        //Create retention schedule with a user with unauthorized access
 | 
			
		||||
        getRestAPIFactory().getRetentionScheduleAPI(new UserModel(getAdminUser().getUsername(), "wrongPassword")).getRetentionSchedule(recordCategory.getId());
 | 
			
		||||
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(UNAUTHORIZED);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * <pre>
 | 
			
		||||
     * Given that a record category exists
 | 
			
		||||
     * When I ask the API to get a retention schedule on a categoryId with a user having access
 | 
			
		||||
     * Then it will give retentionSchedule with 200 as a status code
 | 
			
		||||
     * </pre>
 | 
			
		||||
     */
 | 
			
		||||
    @Test(priority = 9)
 | 
			
		||||
    public void retentionScheduleWith200()
 | 
			
		||||
    {
 | 
			
		||||
        RetentionScheduleCollection retentionScheduleCollection = getRestAPIFactory().getRetentionScheduleAPI().getRetentionSchedule(recordCategory.getId());
 | 
			
		||||
        // Verify the status code
 | 
			
		||||
        assertStatusCode(OK);
 | 
			
		||||
        retentionScheduleCollection.getEntries().forEach(c ->
 | 
			
		||||
        {
 | 
			
		||||
            RetentionSchedule retentionSchedule = c.getEntry();
 | 
			
		||||
            String retentionScheduleId = retentionSchedule.getId();
 | 
			
		||||
            assertNotNull(retentionScheduleId);
 | 
			
		||||
            logger.info("Checking retention schedule " + retentionScheduleId);
 | 
			
		||||
 | 
			
		||||
            // Find this retention schedule is created one or not
 | 
			
		||||
            assertEquals(createdRetentionSchedule.getId(), retentionScheduleId);
 | 
			
		||||
            assertEquals(createdRetentionSchedule.getParentId(),retentionSchedule.getParentId());
 | 
			
		||||
            assertEquals(createdRetentionSchedule.getAuthority(), retentionSchedule.getAuthority());
 | 
			
		||||
            assertEquals(createdRetentionSchedule.getInstructions(), retentionSchedule.getInstructions());
 | 
			
		||||
            assertEquals(createdRetentionSchedule.getIsRecordLevel(), retentionSchedule.getIsRecordLevel());
 | 
			
		||||
            assertEquals(createdRetentionSchedule.isUnpublishedUpdates(), retentionSchedule.isUnpublishedUpdates());
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @AfterClass(alwaysRun = true)
 | 
			
		||||
    public void cleanUpRetentionScheduleTests()
 | 
			
		||||
    {
 | 
			
		||||
        rmRolesAndActionsAPI.deleteAllItemsInContainer(getDataUser().usingAdmin().getAdminUser().getUsername(),
 | 
			
		||||
            getDataUser().usingAdmin().getAdminUser().getPassword(), RM_SITE_ID, recordCategory.getName());
 | 
			
		||||
        deleteRecordCategory(recordCategory.getId());
 | 
			
		||||
        dataUser.deleteUser(nonRMuser);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
com.epam.reportportal.testng.ReportPortalTestNGListener
 | 
			
		||||
@@ -45,9 +45,9 @@ serverHealth.showTenants=false
 | 
			
		||||
# testManagement.username=<username>
 | 
			
		||||
# testManagement.apiKey=<api-key>
 | 
			
		||||
# testManagement.project=<id-of-your-project
 | 
			
		||||
# testManagement.testRun=<test-run-name> 
 | 
			
		||||
# testManagement.testRun=<test-run-name>
 | 
			
		||||
# testManagement.includeOnlyTestCasesExecuted=true #if you want to include in your run ONLY the test cases that you run, then set this value to false
 | 
			
		||||
# testManagement.rateLimitInSeconds=1 #is the default rate limit after what minimum time, should we upload the next request. http://docs.gurock.com/testrail-api2/introduction #Rate Limit 
 | 
			
		||||
# testManagement.rateLimitInSeconds=1 #is the default rate limit after what minimum time, should we upload the next request. http://docs.gurock.com/testrail-api2/introduction #Rate Limit
 | 
			
		||||
# testManagement.suiteId=23 (the id of the Master suite)
 | 
			
		||||
# ------------------------------------------------------
 | 
			
		||||
testManagement.enabled=false
 | 
			
		||||
@@ -72,7 +72,7 @@ reports.path=./target/reports
 | 
			
		||||
#
 | 
			
		||||
# MySQL:
 | 
			
		||||
# db.url = jdbc:mysql://${alfresco.server}:3306/alfresco
 | 
			
		||||
# 
 | 
			
		||||
#
 | 
			
		||||
# PostgreSQL:
 | 
			
		||||
# db.url = jdbc:postgresql://<your-DB-IP>:3306/alfresco
 | 
			
		||||
#
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
   <parent>
 | 
			
		||||
      <groupId>org.alfresco</groupId>
 | 
			
		||||
      <artifactId>alfresco-governance-services-community-parent</artifactId>
 | 
			
		||||
      <version>23.3.0.2</version>
 | 
			
		||||
      <version>23.3.0.87-SNAPSHOT</version>
 | 
			
		||||
   </parent>
 | 
			
		||||
 | 
			
		||||
   <modules>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,3 @@
 | 
			
		||||
SOLR6_TAG=2.0.8.1
 | 
			
		||||
SOLR6_TAG=2.0.11
 | 
			
		||||
POSTGRES_TAG=15.4
 | 
			
		||||
ACTIVEMQ_TAG=5.18.3-jre17-rockylinux8
 | 
			
		||||
 
 | 
			
		||||
@@ -139,3 +139,26 @@ content.metadata.async.extract.6.enabled=false
 | 
			
		||||
 | 
			
		||||
# Max number of entries returned in Record search view
 | 
			
		||||
rm.recordSearch.maxItems=500
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# Hold bulk
 | 
			
		||||
#
 | 
			
		||||
# The number of worker threads.
 | 
			
		||||
rm.hold.bulk.threadCount=2
 | 
			
		||||
# The maximum number of total items to process in a single bulk operation.
 | 
			
		||||
rm.hold.bulk.maxItems=1000
 | 
			
		||||
# The number of entries to be fetched from the Search Service as a next set of work object to process.
 | 
			
		||||
rm.hold.bulk.batchSize=100
 | 
			
		||||
# The number of entries to process before reporting progress.
 | 
			
		||||
rm.hold.bulk.logging.interval=100
 | 
			
		||||
# The number of entries we process at a time in a transaction.
 | 
			
		||||
rm.hold.bulk.itemsPerTransaction=1
 | 
			
		||||
# The maximum number of bulk requests we can process in parallel.
 | 
			
		||||
rm.hold.bulk.maxParallelRequests=10
 | 
			
		||||
 | 
			
		||||
cache.bulkHoldStatusCache.cluster.type=fully-distributed
 | 
			
		||||
cache.bulkHoldStatusCache.timeToLiveSeconds=2592000
 | 
			
		||||
cache.bulkHoldRegistryCache.cluster.type=fully-distributed
 | 
			
		||||
cache.bulkHoldRegistryCache.timeToLiveSeconds=2592000
 | 
			
		||||
cache.bulkCancellationsCache.cluster.type=fully-distributed
 | 
			
		||||
cache.bulkCancellationsCache.timeToLiveSeconds=2592000
 | 
			
		||||
@@ -31,6 +31,11 @@
 | 
			
		||||
         <cm:description>Configuration information for the Records Management application.</cm:description>
 | 
			
		||||
      </view:properties>
 | 
			
		||||
 | 
			
		||||
      <view:aspects>
 | 
			
		||||
         <sys:undeletable/>
 | 
			
		||||
         <sys:unmovable/>
 | 
			
		||||
      </view:aspects>
 | 
			
		||||
 | 
			
		||||
      <view:associations>
 | 
			
		||||
         <cm:contains>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -125,7 +125,7 @@
 | 
			
		||||
         parent="declarativeCapability">
 | 
			
		||||
      <property name="name" value="DeleteRecordFolder"/>
 | 
			
		||||
      <property name="private" value="true"/>
 | 
			
		||||
      <property name="permission" value="CreateModifyDestroyFolders"/>
 | 
			
		||||
      <property name="permission" value="DeleteRecords"/>
 | 
			
		||||
      <property name="kinds">
 | 
			
		||||
         <list>
 | 
			
		||||
            <value>RECORD_FOLDER</value>
 | 
			
		||||
 
 | 
			
		||||
@@ -538,6 +538,11 @@
 | 
			
		||||
               <type>d:text</type>
 | 
			
		||||
               <mandatory>true</mandatory>
 | 
			
		||||
            </property>
 | 
			
		||||
            <property name="rma:holdDeletionReason">
 | 
			
		||||
               <title>Hold Deletion Reason</title>
 | 
			
		||||
               <type>d:text</type>
 | 
			
		||||
               <mandatory>false</mandatory>
 | 
			
		||||
            </property>
 | 
			
		||||
         </properties>
 | 
			
		||||
 | 
			
		||||
         <associations>
 | 
			
		||||
 
 | 
			
		||||
@@ -89,6 +89,9 @@
 | 
			
		||||
   <!-- Import RM Audit -->
 | 
			
		||||
   <import resource="classpath:alfresco/module/org_alfresco_module_rm/rm-audit-context.xml"/>
 | 
			
		||||
 | 
			
		||||
   <!-- Import RM Bulk -->
 | 
			
		||||
   <import resource="classpath:alfresco/module/org_alfresco_module_rm/rm-bulk-context.xml"/>
 | 
			
		||||
 | 
			
		||||
   <!--  Import RM query context -->
 | 
			
		||||
   <import resource="classpath:alfresco/module/org_alfresco_module_rm/query/rm-query-context.xml" />
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,59 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<beans xmlns="http://www.springframework.org/schema/beans"
 | 
			
		||||
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | 
			
		||||
       xmlns:context="http://www.springframework.org/schema/context"
 | 
			
		||||
       xsi:schemaLocation="http://www.springframework.org/schema/beans
 | 
			
		||||
                        http://www.springframework.org/schema/beans/spring-beans.xsd
 | 
			
		||||
                        http://www.springframework.org/schema/context
 | 
			
		||||
                        http://www.springframework.org/schema/context/spring-context.xsd">
 | 
			
		||||
 | 
			
		||||
   <bean id="holdBulkService"
 | 
			
		||||
         class="org.alfresco.module.org_alfresco_module_rm.bulk.hold.HoldBulkServiceImpl">
 | 
			
		||||
      <property name="serviceRegistry" ref="ServiceRegistry" />
 | 
			
		||||
      <property name="transactionService" ref="transactionService" />
 | 
			
		||||
      <property name="searchMapper" ref="searchapiSearchMapper" />
 | 
			
		||||
      <property name="bulkMonitor" ref="holdBulkMonitor" />
 | 
			
		||||
      <property name="holdService" ref="HoldService" />
 | 
			
		||||
      <property name="capabilityService" ref="CapabilityService" />
 | 
			
		||||
      <property name="permissionService" ref="PermissionService" />
 | 
			
		||||
      <property name="nodeService" ref="NodeService" />
 | 
			
		||||
      <property name="threadCount">
 | 
			
		||||
         <value>${rm.hold.bulk.threadCount}</value>
 | 
			
		||||
      </property>
 | 
			
		||||
      <property name="batchSize">
 | 
			
		||||
         <value>${rm.hold.bulk.batchSize}</value>
 | 
			
		||||
      </property>
 | 
			
		||||
      <property name="maxItems">
 | 
			
		||||
         <value>${rm.hold.bulk.maxItems}</value>
 | 
			
		||||
      </property>
 | 
			
		||||
      <property name="loggingInterval">
 | 
			
		||||
         <value>${rm.hold.bulk.logging.interval}</value>
 | 
			
		||||
      </property>
 | 
			
		||||
      <property name="itemsPerTransaction">
 | 
			
		||||
         <value>${rm.hold.bulk.itemsPerTransaction}</value>
 | 
			
		||||
      </property>
 | 
			
		||||
      <property name="maxParallelRequests">
 | 
			
		||||
         <value>${rm.hold.bulk.maxParallelRequests}</value>
 | 
			
		||||
      </property>
 | 
			
		||||
   </bean>
 | 
			
		||||
 | 
			
		||||
   <bean id="holdBulkMonitor" class="org.alfresco.module.org_alfresco_module_rm.bulk.hold.DefaultHoldBulkMonitor">
 | 
			
		||||
      <property name="holdProgressCache" ref="holdProgressCache" />
 | 
			
		||||
      <property name="holdProcessRegistry" ref="holdProcessRegistry" />
 | 
			
		||||
      <property name="bulkCancellationsCache" ref="bulkCancellationsCache" />
 | 
			
		||||
   </bean>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
   <bean name="holdProgressCache" factory-bean="cacheFactory" factory-method="createCache">
 | 
			
		||||
      <constructor-arg value="cache.bulkHoldStatusCache" />
 | 
			
		||||
   </bean>
 | 
			
		||||
 | 
			
		||||
   <bean name="holdProcessRegistry" factory-bean="cacheFactory" factory-method="createCache">
 | 
			
		||||
      <constructor-arg value="cache.bulkHoldRegistryCache" />
 | 
			
		||||
   </bean>
 | 
			
		||||
 | 
			
		||||
   <bean name="bulkCancellationsCache" factory-bean="cacheFactory" factory-method="createCache">
 | 
			
		||||
      <constructor-arg value="cache.bulkCancellationsCache" />
 | 
			
		||||
   </bean>
 | 
			
		||||
 | 
			
		||||
</beans>
 | 
			
		||||
@@ -31,6 +31,7 @@
 | 
			
		||||
        <property name="personService" ref="PersonService"/>
 | 
			
		||||
        <property name="dispositionService" ref="DispositionService"/>
 | 
			
		||||
        <property name="serviceRegistry" ref="ServiceRegistry"/>
 | 
			
		||||
        <property name="recordsManagementServiceRegistry" ref="RecordsManagementServiceRegistry"/>
 | 
			
		||||
    </bean>
 | 
			
		||||
 | 
			
		||||
    <bean id="searchTypesFactory" class="org.alfresco.rm.rest.api.impl.SearchTypesFactory">
 | 
			
		||||
@@ -69,7 +70,40 @@
 | 
			
		||||
       <property name="transactionService" ref="transactionService" />
 | 
			
		||||
    </bean>
 | 
			
		||||
 | 
			
		||||
    <bean class="org.alfresco.rm.rest.api.unfiledcontainers.UnfiledContainerEntityResource">
 | 
			
		||||
   <bean class="org.alfresco.rm.rest.api.fileplans.FilePlanHoldsRelation">
 | 
			
		||||
      <property name="apiUtils" ref="apiUtils" />
 | 
			
		||||
      <property name="nodesModelFactory" ref="nodesModelFactory" />
 | 
			
		||||
      <property name="holdService" ref="HoldService" />
 | 
			
		||||
      <property name="fileFolderService" ref="FileFolderService" />
 | 
			
		||||
      <property name="transactionService" ref="transactionService" />
 | 
			
		||||
   </bean>
 | 
			
		||||
 | 
			
		||||
   <bean class="org.alfresco.rm.rest.api.holds.HoldsEntityResource" >
 | 
			
		||||
      <property name="holdService" ref="HoldService" />
 | 
			
		||||
      <property name="apiUtils" ref="apiUtils" />
 | 
			
		||||
      <property name="nodesModelFactory" ref="nodesModelFactory" />
 | 
			
		||||
      <property name="fileFolderService" ref="FileFolderService" />
 | 
			
		||||
      <property name="transactionService" ref="transactionService" />
 | 
			
		||||
      <property name="holdBulkService" ref="holdBulkService" />
 | 
			
		||||
   </bean>
 | 
			
		||||
 | 
			
		||||
   <bean class="org.alfresco.rm.rest.api.holds.HoldsBulkStatusesRelation" >
 | 
			
		||||
      <property name="holdBulkMonitor" ref="holdBulkMonitor" />
 | 
			
		||||
      <property name="holdBulkService" ref="holdBulkService" />
 | 
			
		||||
      <property name="apiUtils" ref="apiUtils" />
 | 
			
		||||
      <property name="permissionService" ref="PermissionService" />
 | 
			
		||||
   </bean>
 | 
			
		||||
 | 
			
		||||
   <bean class="org.alfresco.rm.rest.api.holds.HoldsChildrenRelation">
 | 
			
		||||
      <property name="holdService" ref="HoldService" />
 | 
			
		||||
      <property name="apiUtils" ref="apiUtils" />
 | 
			
		||||
      <property name="nodesModelFactory" ref="nodesModelFactory" />
 | 
			
		||||
      <property name="fileFolderService" ref="FileFolderService" />
 | 
			
		||||
      <property name="transactionService" ref="transactionService" />
 | 
			
		||||
      <property name="permissionService" ref="PermissionService" />
 | 
			
		||||
   </bean>
 | 
			
		||||
 | 
			
		||||
   <bean class="org.alfresco.rm.rest.api.unfiledcontainers.UnfiledContainerEntityResource">
 | 
			
		||||
       <property name="apiUtils" ref="apiUtils" />
 | 
			
		||||
       <property name="fileFolderService" ref="FileFolderService" />
 | 
			
		||||
       <property name="nodesModelFactory" ref="nodesModelFactory" />
 | 
			
		||||
@@ -114,6 +148,20 @@
 | 
			
		||||
       <property name="transactionService" ref="transactionService" />
 | 
			
		||||
    </bean>
 | 
			
		||||
 | 
			
		||||
    <bean class="org.alfresco.rm.rest.api.retentionschedule.RetentionScheduleRelation">
 | 
			
		||||
        <property name="apiUtils" ref="apiUtils" />
 | 
			
		||||
        <property name="nodesModelFactory" ref="nodesModelFactory" />
 | 
			
		||||
        <property name="dispositionService" ref="DispositionService"/>
 | 
			
		||||
        <property name="nodeService" ref="NodeService"/>
 | 
			
		||||
    </bean>
 | 
			
		||||
 | 
			
		||||
    <bean class="org.alfresco.rm.rest.api.retentionschedule.RetentionScheduleActionRelation">
 | 
			
		||||
        <property name="apiUtils" ref="apiUtils" />
 | 
			
		||||
        <property name="nodesModelFactory" ref="nodesModelFactory" />
 | 
			
		||||
        <property name="nodeService" ref="NodeService"/>
 | 
			
		||||
        <property name="recordsManagementServiceRegistry" ref="RecordsManagementServiceRegistry"/>
 | 
			
		||||
    </bean>
 | 
			
		||||
 | 
			
		||||
    <bean class="org.alfresco.rm.rest.api.recordfolders.RecordFolderEntityResource">
 | 
			
		||||
       <property name="apiUtils" ref="apiUtils" />
 | 
			
		||||
       <property name="fileFolderService" ref="FileFolderService" />
 | 
			
		||||
 
 | 
			
		||||
@@ -1614,6 +1614,8 @@
 | 
			
		||||
            org.alfresco.module.org_alfresco_module_rm.hold.HoldService.createHold=RM_CAP.0.rma:filePlanComponent.CreateHold
 | 
			
		||||
            org.alfresco.module.org_alfresco_module_rm.hold.HoldService.getHoldReason=RM.Read.0
 | 
			
		||||
            org.alfresco.module.org_alfresco_module_rm.hold.HoldService.setHoldReason=RM_CAP.0.rma:filePlanComponent.EditHold
 | 
			
		||||
            org.alfresco.module.org_alfresco_module_rm.hold.HoldService.setHoldDeletionReason=RM_CAP.0.rma:filePlanComponent.EditHold
 | 
			
		||||
            org.alfresco.module.org_alfresco_module_rm.hold.HoldService.updateHold=RM_CAP.0.rma:filePlanComponent.EditHold
 | 
			
		||||
            org.alfresco.module.org_alfresco_module_rm.hold.HoldService.deleteHold=RM_CAP.0.rma:filePlanComponent.DeleteHold
 | 
			
		||||
            org.alfresco.module.org_alfresco_module_rm.hold.HoldService.addToHold=RM_CAP.0.rma:filePlanComponent.AddToHold
 | 
			
		||||
            org.alfresco.module.org_alfresco_module_rm.hold.HoldService.addToHolds=RM_ALLOW
 | 
			
		||||
 
 | 
			
		||||
@@ -41,6 +41,8 @@ services:
 | 
			
		||||
                -Daos.baseUrlOverwrite=http://localhost:8080/alfresco/aos
 | 
			
		||||
                -Dmessaging.broker.url=\"failover:(tcp://activemq:61616)?timeout=3000&jms.useCompression=true\"
 | 
			
		||||
                -DlocalTransform.core-aio.url=http://transform-core-aio:8090/
 | 
			
		||||
                -Drm.hold.bulk.maxItems=5
 | 
			
		||||
                -Drm.hold.bulk.batchSize=2
 | 
			
		||||
                "
 | 
			
		||||
        ports:
 | 
			
		||||
            - 8080:8080
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
   <parent>
 | 
			
		||||
      <groupId>org.alfresco</groupId>
 | 
			
		||||
      <artifactId>alfresco-governance-services-community-repo-parent</artifactId>
 | 
			
		||||
      <version>23.3.0.2</version>
 | 
			
		||||
      <version>23.3.0.87-SNAPSHOT</version>
 | 
			
		||||
   </parent>
 | 
			
		||||
 | 
			
		||||
   <properties>
 | 
			
		||||
@@ -84,6 +84,11 @@
 | 
			
		||||
         <artifactId>junit</artifactId>
 | 
			
		||||
         <scope>test</scope>
 | 
			
		||||
      </dependency>
 | 
			
		||||
      <dependency>
 | 
			
		||||
         <groupId>com.epam.reportportal</groupId>
 | 
			
		||||
         <artifactId>agent-java-testng</artifactId>
 | 
			
		||||
         <scope>test</scope>
 | 
			
		||||
      </dependency>
 | 
			
		||||
      <dependency>
 | 
			
		||||
         <groupId>org.postgresql</groupId>
 | 
			
		||||
         <artifactId>postgresql</artifactId>
 | 
			
		||||
@@ -150,6 +155,12 @@
 | 
			
		||||
           <artifactId>lombok</artifactId>
 | 
			
		||||
           <scope>provided</scope>
 | 
			
		||||
       </dependency>
 | 
			
		||||
      <dependency>
 | 
			
		||||
         <groupId>org.awaitility</groupId>
 | 
			
		||||
         <artifactId>awaitility</artifactId>
 | 
			
		||||
         <version>${dependency.awaitility.version}</version>
 | 
			
		||||
         <scope>test</scope>
 | 
			
		||||
      </dependency>
 | 
			
		||||
   </dependencies>
 | 
			
		||||
 | 
			
		||||
   <build>
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,7 @@
 | 
			
		||||
 | 
			
		||||
package org.alfresco.module.org_alfresco_module_rm.audit.event;
 | 
			
		||||
 | 
			
		||||
import static org.alfresco.module.org_alfresco_module_rm.audit.event.HoldUtils.HOLD_DELETION_REASON;
 | 
			
		||||
import static org.alfresco.repo.policy.Behaviour.NotificationFrequency.EVERY_EVENT;
 | 
			
		||||
 | 
			
		||||
import java.io.Serializable;
 | 
			
		||||
@@ -77,6 +78,8 @@ public class DeleteHoldAuditEvent extends AuditEvent implements NodeServicePolic
 | 
			
		||||
    public void beforeDeleteNode(NodeRef holdNodeRef)
 | 
			
		||||
    {
 | 
			
		||||
        Map<QName, Serializable> auditProperties = HoldUtils.makePropertiesMap(holdNodeRef, nodeService);
 | 
			
		||||
        auditProperties.put(HOLD_DELETION_REASON, nodeService.getProperty(holdNodeRef, PROP_HOLD_DELETION_REASON));
 | 
			
		||||
 | 
			
		||||
        recordsManagementAuditService.auditEvent(holdNodeRef, getName(), auditProperties, null, true, false);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -47,6 +47,7 @@ class HoldUtils
 | 
			
		||||
{
 | 
			
		||||
    /** A QName to display for the hold name. */
 | 
			
		||||
    public static final QName HOLD_NAME = QName.createQName(RecordsManagementModel.RM_URI, "Hold Name");
 | 
			
		||||
    public static final QName HOLD_DELETION_REASON = QName.createQName(RecordsManagementModel.RM_URI, "Hold deletion reason");
 | 
			
		||||
    /** A QName to display for the hold node ref. */
 | 
			
		||||
    public static final QName HOLD_NODEREF = QName.createQName(RecordsManagementModel.RM_URI, "Hold NodeRef");
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,265 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 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.module.org_alfresco_module_rm.bulk;
 | 
			
		||||
 | 
			
		||||
import static java.util.concurrent.Executors.newFixedThreadPool;
 | 
			
		||||
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
import java.util.concurrent.ExecutorService;
 | 
			
		||||
import java.util.concurrent.atomic.AtomicBoolean;
 | 
			
		||||
import java.util.concurrent.atomic.AtomicInteger;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.repo.batch.BatchProcessWorkProvider;
 | 
			
		||||
import org.alfresco.repo.batch.BatchProcessor;
 | 
			
		||||
import org.alfresco.repo.batch.BatchProcessor.BatchProcessWorker;
 | 
			
		||||
import org.alfresco.rest.api.search.impl.SearchMapper;
 | 
			
		||||
import org.alfresco.rest.api.search.model.Query;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
 | 
			
		||||
import org.alfresco.service.ServiceRegistry;
 | 
			
		||||
import org.alfresco.service.cmr.repository.NodeRef;
 | 
			
		||||
import org.alfresco.service.cmr.search.ResultSet;
 | 
			
		||||
import org.alfresco.service.cmr.search.SearchParameters;
 | 
			
		||||
import org.alfresco.service.cmr.search.SearchService;
 | 
			
		||||
import org.alfresco.service.transaction.TransactionService;
 | 
			
		||||
import org.apache.commons.logging.Log;
 | 
			
		||||
import org.apache.commons.logging.LogFactory;
 | 
			
		||||
import org.springframework.beans.factory.InitializingBean;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A base class for executing bulk operations on nodes based on search query results
 | 
			
		||||
 */
 | 
			
		||||
public abstract class BulkBaseService<T> implements InitializingBean
 | 
			
		||||
{
 | 
			
		||||
    private static final Log LOG = LogFactory.getLog(BulkBaseService.class);
 | 
			
		||||
    protected ExecutorService executorService;
 | 
			
		||||
    protected ServiceRegistry serviceRegistry;
 | 
			
		||||
    protected SearchService searchService;
 | 
			
		||||
    protected TransactionService transactionService;
 | 
			
		||||
    protected SearchMapper searchMapper;
 | 
			
		||||
    protected BulkMonitor<T> bulkMonitor;
 | 
			
		||||
 | 
			
		||||
    protected int threadCount;
 | 
			
		||||
    protected int batchSize;
 | 
			
		||||
    protected int itemsPerTransaction;
 | 
			
		||||
    protected int maxItems;
 | 
			
		||||
    protected int loggingInterval;
 | 
			
		||||
    protected int maxParallelRequests;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void afterPropertiesSet() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        this.searchService = serviceRegistry.getSearchService();
 | 
			
		||||
        this.executorService = newFixedThreadPool(maxParallelRequests);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Execute bulk operation on node based on the search query results
 | 
			
		||||
     *
 | 
			
		||||
     * @param nodeRef       node reference
 | 
			
		||||
     * @param bulkOperation bulk operation
 | 
			
		||||
     * @return bulk status
 | 
			
		||||
     */
 | 
			
		||||
    public T execute(NodeRef nodeRef, BulkOperation bulkOperation)
 | 
			
		||||
    {
 | 
			
		||||
        checkPermissions(nodeRef, bulkOperation);
 | 
			
		||||
 | 
			
		||||
        ResultSet resultSet = getTotalItems(bulkOperation.searchQuery(), maxItems);
 | 
			
		||||
        if (maxItems < resultSet.getNumberFound() || resultSet.hasMore())
 | 
			
		||||
        {
 | 
			
		||||
            throw new InvalidArgumentException("Too many items to process. Please refine your query.");
 | 
			
		||||
        }
 | 
			
		||||
        long totalItems = resultSet.getNumberFound();
 | 
			
		||||
        // Generate a random process id
 | 
			
		||||
        String processId = UUID.randomUUID().toString();
 | 
			
		||||
 | 
			
		||||
        T initBulkStatus = getInitBulkStatus(processId, totalItems);
 | 
			
		||||
        bulkMonitor.updateBulkStatus(initBulkStatus);
 | 
			
		||||
        bulkMonitor.registerProcess(nodeRef, processId, bulkOperation);
 | 
			
		||||
 | 
			
		||||
        BulkProgress bulkProgress = new BulkProgress(totalItems, processId, new AtomicBoolean(false),
 | 
			
		||||
            new AtomicInteger(0));
 | 
			
		||||
        BatchProcessWorker<NodeRef> batchProcessWorker = getWorkerProvider(nodeRef, bulkOperation, bulkProgress);
 | 
			
		||||
        BulkStatusUpdater bulkStatusUpdater = getBulkStatusUpdater();
 | 
			
		||||
 | 
			
		||||
        BatchProcessor<NodeRef> batchProcessor = new BatchProcessor<>(
 | 
			
		||||
            processId,
 | 
			
		||||
            transactionService.getRetryingTransactionHelper(),
 | 
			
		||||
            getWorkProvider(bulkOperation, bulkStatusUpdater, bulkProgress),
 | 
			
		||||
            threadCount,
 | 
			
		||||
            itemsPerTransaction,
 | 
			
		||||
            bulkStatusUpdater,
 | 
			
		||||
            LOG,
 | 
			
		||||
            loggingInterval);
 | 
			
		||||
 | 
			
		||||
        runAsyncBatchProcessor(batchProcessor, batchProcessWorker, bulkStatusUpdater);
 | 
			
		||||
        return initBulkStatus;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Run batch processor
 | 
			
		||||
     */
 | 
			
		||||
    protected void runAsyncBatchProcessor(BatchProcessor<NodeRef> batchProcessor,
 | 
			
		||||
        BatchProcessWorker<NodeRef> batchProcessWorker, BulkStatusUpdater bulkStatusUpdater)
 | 
			
		||||
    {
 | 
			
		||||
        Runnable backgroundLogic = () -> {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                if (LOG.isDebugEnabled())
 | 
			
		||||
                {
 | 
			
		||||
                    LOG.debug("Started processing batch with name: " + batchProcessor.getProcessName());
 | 
			
		||||
                }
 | 
			
		||||
                batchProcessor.processLong(batchProcessWorker, true);
 | 
			
		||||
                if (LOG.isDebugEnabled())
 | 
			
		||||
                {
 | 
			
		||||
                    LOG.debug("Processing batch with name: " + batchProcessor.getProcessName() + " completed");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception exception)
 | 
			
		||||
            {
 | 
			
		||||
                LOG.error("Error processing batch with name: " + batchProcessor.getProcessName(), exception);
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                bulkStatusUpdater.update();
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        executorService.submit(backgroundLogic);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get initial bulk status
 | 
			
		||||
     *
 | 
			
		||||
     * @param processId  process id
 | 
			
		||||
     * @param totalItems total items
 | 
			
		||||
     * @return bulk status
 | 
			
		||||
     */
 | 
			
		||||
    protected abstract T getInitBulkStatus(String processId, long totalItems);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get bulk status updater
 | 
			
		||||
     *
 | 
			
		||||
     * @return bulk status updater
 | 
			
		||||
     */
 | 
			
		||||
    protected abstract BulkStatusUpdater getBulkStatusUpdater();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get work provider
 | 
			
		||||
     *
 | 
			
		||||
     * @param bulkOperation     bulk operation
 | 
			
		||||
     * @param bulkStatusUpdater bulk status updater
 | 
			
		||||
     * @param bulkProgress      bulk progress
 | 
			
		||||
     * @return work provider
 | 
			
		||||
     */
 | 
			
		||||
    protected abstract BatchProcessWorkProvider<NodeRef> getWorkProvider(BulkOperation bulkOperation,
 | 
			
		||||
        BulkStatusUpdater bulkStatusUpdater, BulkProgress bulkProgress);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get worker provider
 | 
			
		||||
     *
 | 
			
		||||
     * @param nodeRef       node reference
 | 
			
		||||
     * @param bulkOperation bulk operation
 | 
			
		||||
     * @param bulkProgress  bulk progress
 | 
			
		||||
     * @return worker provider
 | 
			
		||||
     */
 | 
			
		||||
    protected abstract BatchProcessWorker<NodeRef> getWorkerProvider(NodeRef nodeRef, BulkOperation bulkOperation,
 | 
			
		||||
        BulkProgress bulkProgress);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check permissions
 | 
			
		||||
     *
 | 
			
		||||
     * @param nodeRef       node reference
 | 
			
		||||
     * @param bulkOperation bulk operation
 | 
			
		||||
     */
 | 
			
		||||
    protected abstract void checkPermissions(NodeRef nodeRef, BulkOperation bulkOperation);
 | 
			
		||||
 | 
			
		||||
    protected ResultSet getTotalItems(Query searchQuery, int skipCount)
 | 
			
		||||
    {
 | 
			
		||||
        SearchParameters searchParams = new SearchParameters();
 | 
			
		||||
        searchMapper.setDefaults(searchParams);
 | 
			
		||||
        searchMapper.fromQuery(searchParams, searchQuery);
 | 
			
		||||
        searchParams.setSkipCount(skipCount);
 | 
			
		||||
        searchParams.setMaxItems(1);
 | 
			
		||||
        searchParams.setLimit(1);
 | 
			
		||||
        return searchService.query(searchParams);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setServiceRegistry(ServiceRegistry serviceRegistry)
 | 
			
		||||
    {
 | 
			
		||||
        this.serviceRegistry = serviceRegistry;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setSearchService(SearchService searchService)
 | 
			
		||||
    {
 | 
			
		||||
        this.searchService = searchService;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setTransactionService(TransactionService transactionService)
 | 
			
		||||
    {
 | 
			
		||||
        this.transactionService = transactionService;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setSearchMapper(SearchMapper searchMapper)
 | 
			
		||||
    {
 | 
			
		||||
        this.searchMapper = searchMapper;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setBulkMonitor(BulkMonitor<T> bulkMonitor)
 | 
			
		||||
    {
 | 
			
		||||
        this.bulkMonitor = bulkMonitor;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setThreadCount(int threadCount)
 | 
			
		||||
    {
 | 
			
		||||
        this.threadCount = threadCount;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setBatchSize(int batchSize)
 | 
			
		||||
    {
 | 
			
		||||
        this.batchSize = batchSize;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setMaxItems(int maxItems)
 | 
			
		||||
    {
 | 
			
		||||
        this.maxItems = maxItems;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setLoggingInterval(int loggingInterval)
 | 
			
		||||
    {
 | 
			
		||||
        this.loggingInterval = loggingInterval;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setItemsPerTransaction(int itemsPerTransaction)
 | 
			
		||||
    {
 | 
			
		||||
        this.itemsPerTransaction = itemsPerTransaction;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setMaxParallelRequests(int maxParallelRequests)
 | 
			
		||||
    {
 | 
			
		||||
        this.maxParallelRequests = maxParallelRequests;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,34 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 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.module.org_alfresco_module_rm.bulk;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An immutable POJO to represent a bulk cancellation request
 | 
			
		||||
 */
 | 
			
		||||
public record BulkCancellationRequest(String reason)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,83 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 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.module.org_alfresco_module_rm.bulk;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.service.cmr.repository.NodeRef;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An interface for monitoring the progress of a bulk operation
 | 
			
		||||
 */
 | 
			
		||||
public interface BulkMonitor<T>
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Update the bulk status
 | 
			
		||||
     *
 | 
			
		||||
     * @param bulkStatus the bulk status
 | 
			
		||||
     */
 | 
			
		||||
    void updateBulkStatus(T bulkStatus);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register a process
 | 
			
		||||
     *
 | 
			
		||||
     * @param nodeRef       the node reference
 | 
			
		||||
     * @param processId     the process id
 | 
			
		||||
     * @param bulkOperation the bulk operation
 | 
			
		||||
     */
 | 
			
		||||
    void registerProcess(NodeRef nodeRef, String processId, BulkOperation bulkOperation);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the bulk status
 | 
			
		||||
     *
 | 
			
		||||
     * @param bulkStatusId the bulk status id
 | 
			
		||||
     * @return the bulk status
 | 
			
		||||
     */
 | 
			
		||||
    T getBulkStatus(String bulkStatusId);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Cancel a bulk operation
 | 
			
		||||
     *
 | 
			
		||||
     * @param bulkStatusId
 | 
			
		||||
     * @param bulkCancellationRequest
 | 
			
		||||
     */
 | 
			
		||||
    void cancelBulkOperation(String bulkStatusId, BulkCancellationRequest bulkCancellationRequest);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if a bulk operation is cancelled
 | 
			
		||||
     *
 | 
			
		||||
     * @param bulkStatusId
 | 
			
		||||
     * @return true if the bulk operation is cancelled
 | 
			
		||||
     */
 | 
			
		||||
    boolean isCancelled(String bulkStatusId);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the bulk cancellation request
 | 
			
		||||
     *
 | 
			
		||||
     * @param bulkStatusId
 | 
			
		||||
     * @return cancellation reason
 | 
			
		||||
     */
 | 
			
		||||
    BulkCancellationRequest getBulkCancellationRequest(String bulkStatusId);
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,46 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 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.module.org_alfresco_module_rm.bulk;
 | 
			
		||||
 | 
			
		||||
import java.io.Serializable;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.rest.api.search.model.Query;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An immutable POJO to represent a bulk operation
 | 
			
		||||
 */
 | 
			
		||||
public record BulkOperation(Query searchQuery, String operationType) implements Serializable
 | 
			
		||||
{
 | 
			
		||||
    public BulkOperation
 | 
			
		||||
    {
 | 
			
		||||
        if (operationType == null || searchQuery == null)
 | 
			
		||||
        {
 | 
			
		||||
            throw new IllegalArgumentException("Operation type and search query must not be null");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -0,0 +1,37 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 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.module.org_alfresco_module_rm.bulk;
 | 
			
		||||
 | 
			
		||||
import java.util.concurrent.atomic.AtomicBoolean;
 | 
			
		||||
import java.util.concurrent.atomic.AtomicInteger;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An immutable POJO to represent the progress of a bulk operation
 | 
			
		||||
 */
 | 
			
		||||
public record BulkProgress(long totalItems, String processId, AtomicBoolean cancelled, AtomicInteger currentNodeNumber)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,40 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 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.module.org_alfresco_module_rm.bulk;
 | 
			
		||||
 | 
			
		||||
import org.springframework.context.ApplicationEventPublisher;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An interface for updating the status of a bulk operation
 | 
			
		||||
 */
 | 
			
		||||
public interface BulkStatusUpdater extends ApplicationEventPublisher
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Update the bulk status
 | 
			
		||||
     */
 | 
			
		||||
    void update();
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,165 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 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.module.org_alfresco_module_rm.bulk.hold;
 | 
			
		||||
 | 
			
		||||
import java.util.Comparator;
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkCancellationRequest;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkOperation;
 | 
			
		||||
import org.alfresco.repo.cache.SimpleCache;
 | 
			
		||||
import org.alfresco.service.cmr.repository.NodeRef;
 | 
			
		||||
import org.alfresco.util.Pair;
 | 
			
		||||
import org.springframework.context.ApplicationEvent;
 | 
			
		||||
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Default hold bulk monitor implementation
 | 
			
		||||
 */
 | 
			
		||||
public class DefaultHoldBulkMonitor extends AbstractLifecycleBean implements HoldBulkMonitor
 | 
			
		||||
{
 | 
			
		||||
    protected SimpleCache<String, HoldBulkStatus> holdProgressCache;
 | 
			
		||||
    protected SimpleCache<String, BulkCancellationRequest> bulkCancellationsCache;
 | 
			
		||||
    protected SimpleCache<Pair<String, String>, HoldBulkProcessDetails> holdProcessRegistry;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void updateBulkStatus(HoldBulkStatus holdBulkStatus)
 | 
			
		||||
    {
 | 
			
		||||
        holdProgressCache.put(holdBulkStatus.bulkStatusId(), holdBulkStatus);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void registerProcess(NodeRef holdRef, String processId, BulkOperation bulkOperation)
 | 
			
		||||
    {
 | 
			
		||||
        if (holdRef != null && processId != null)
 | 
			
		||||
        {
 | 
			
		||||
            holdProcessRegistry.put(new Pair<>(holdRef.getId(), processId),
 | 
			
		||||
                new HoldBulkProcessDetails(processId, getCurrentInstanceDetails(), bulkOperation));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public HoldBulkStatus getBulkStatus(String bulkStatusId)
 | 
			
		||||
    {
 | 
			
		||||
        return holdProgressCache.get(bulkStatusId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void cancelBulkOperation(String bulkStatusId, BulkCancellationRequest bulkCancellationRequest)
 | 
			
		||||
    {
 | 
			
		||||
        bulkCancellationsCache.put(bulkStatusId, bulkCancellationRequest);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isCancelled(String bulkStatusId)
 | 
			
		||||
    {
 | 
			
		||||
        return bulkCancellationsCache.contains(bulkStatusId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public BulkCancellationRequest getBulkCancellationRequest(String bulkStatusId)
 | 
			
		||||
    {
 | 
			
		||||
        return bulkCancellationsCache.get(bulkStatusId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<HoldBulkStatusAndProcessDetails> getBulkStatusesWithProcessDetails(String holdId)
 | 
			
		||||
    {
 | 
			
		||||
        return holdProcessRegistry.getKeys().stream()
 | 
			
		||||
            .filter(holdIdAndBulkStatusId -> holdId.equals(holdIdAndBulkStatusId.getFirst()))
 | 
			
		||||
            .map(holdIdAndBulkStatusId -> holdProcessRegistry.get(holdIdAndBulkStatusId))
 | 
			
		||||
            .filter(Objects::nonNull)
 | 
			
		||||
            .map(createHoldBulkStatusAndProcessDetails())
 | 
			
		||||
            .filter(statusAndProcess -> Objects.nonNull(statusAndProcess.holdBulkStatus()))
 | 
			
		||||
            .sorted(sortBulkStatuses())
 | 
			
		||||
            .toList();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public HoldBulkStatusAndProcessDetails getBulkStatusWithProcessDetails(String holdId, String bulkStatusId)
 | 
			
		||||
    {
 | 
			
		||||
        return Optional.ofNullable(holdProcessRegistry.get(new Pair<>(holdId, bulkStatusId)))
 | 
			
		||||
            .map(createHoldBulkStatusAndProcessDetails())
 | 
			
		||||
            .filter(statusAndProcess -> Objects.nonNull(statusAndProcess.holdBulkStatus()))
 | 
			
		||||
            .orElse(null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected String getCurrentInstanceDetails()
 | 
			
		||||
    {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected Function<HoldBulkProcessDetails, HoldBulkStatusAndProcessDetails> createHoldBulkStatusAndProcessDetails()
 | 
			
		||||
    {
 | 
			
		||||
        return bulkProcessDetails -> new HoldBulkStatusAndProcessDetails(
 | 
			
		||||
            getBulkStatus(bulkProcessDetails.bulkStatusId()), bulkProcessDetails);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected static Comparator<HoldBulkStatusAndProcessDetails> sortBulkStatuses()
 | 
			
		||||
    {
 | 
			
		||||
        return Comparator.<HoldBulkStatusAndProcessDetails, Date>comparing(
 | 
			
		||||
                statusAndProcess -> statusAndProcess.holdBulkStatus().endTime(),
 | 
			
		||||
                Comparator.nullsLast(Comparator.naturalOrder()))
 | 
			
		||||
            .thenComparing(statusAndProcess -> statusAndProcess.holdBulkStatus().startTime(),
 | 
			
		||||
                Comparator.nullsLast(Comparator.naturalOrder()))
 | 
			
		||||
            .reversed();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setHoldProgressCache(
 | 
			
		||||
        SimpleCache<String, HoldBulkStatus> holdProgressCache)
 | 
			
		||||
    {
 | 
			
		||||
        this.holdProgressCache = holdProgressCache;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setHoldProcessRegistry(
 | 
			
		||||
        SimpleCache<Pair<String, String>, HoldBulkProcessDetails> holdProcessRegistry)
 | 
			
		||||
    {
 | 
			
		||||
        this.holdProcessRegistry = holdProcessRegistry;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setBulkCancellationsCache(
 | 
			
		||||
        SimpleCache<String, BulkCancellationRequest> bulkCancellationsCache)
 | 
			
		||||
    {
 | 
			
		||||
        this.bulkCancellationsCache = bulkCancellationsCache;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onBootstrap(ApplicationEvent applicationEvent)
 | 
			
		||||
    {
 | 
			
		||||
        // NOOP
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onShutdown(ApplicationEvent applicationEvent)
 | 
			
		||||
    {
 | 
			
		||||
        // NOOP
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,54 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 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.module.org_alfresco_module_rm.bulk.hold;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkMonitor;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An interface for monitoring the progress of a bulk hold operation
 | 
			
		||||
 */
 | 
			
		||||
public interface HoldBulkMonitor extends BulkMonitor<HoldBulkStatus>
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the bulk statuses with process details for a hold
 | 
			
		||||
     *
 | 
			
		||||
     * @param holdId the hold id
 | 
			
		||||
     * @return the bulk statuses with process details
 | 
			
		||||
     */
 | 
			
		||||
    List<HoldBulkStatusAndProcessDetails> getBulkStatusesWithProcessDetails(String holdId);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the bulk status with process details
 | 
			
		||||
     *
 | 
			
		||||
     * @param holdId       the hold id
 | 
			
		||||
     * @param bulkStatusId the bulk status id
 | 
			
		||||
     * @return the bulk status with process details
 | 
			
		||||
     */
 | 
			
		||||
    HoldBulkStatusAndProcessDetails getBulkStatusWithProcessDetails(String holdId, String bulkStatusId);
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,38 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 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.module.org_alfresco_module_rm.bulk.hold;
 | 
			
		||||
 | 
			
		||||
import java.io.Serializable;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkOperation;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A simple immutable POJO to hold the details of a bulk process
 | 
			
		||||
 */
 | 
			
		||||
public record HoldBulkProcessDetails(String bulkStatusId, String creatorInstance, BulkOperation bulkOperation) implements Serializable
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,55 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 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.module.org_alfresco_module_rm.bulk.hold;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkCancellationRequest;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkOperation;
 | 
			
		||||
import org.alfresco.service.cmr.repository.NodeRef;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Interface defining a hold bulk service.
 | 
			
		||||
 */
 | 
			
		||||
public interface HoldBulkService
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Initiates a bulk operation on a hold.
 | 
			
		||||
     *
 | 
			
		||||
     * @param holdRef       The hold reference
 | 
			
		||||
     * @param bulkOperation The bulk operation
 | 
			
		||||
     * @return The initial status of the bulk operation
 | 
			
		||||
     */
 | 
			
		||||
    HoldBulkStatus execute(NodeRef holdRef, BulkOperation bulkOperation);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Cancels a bulk operation.
 | 
			
		||||
     *
 | 
			
		||||
     * @param holdRef                 The hold reference
 | 
			
		||||
     * @param bulkStatusId            The bulk status id
 | 
			
		||||
     * @param bulkCancellationRequest The bulk cancellation request
 | 
			
		||||
     */
 | 
			
		||||
    void cancelBulkOperation(NodeRef holdRef, String bulkStatusId, BulkCancellationRequest bulkCancellationRequest);
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,286 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 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.module.org_alfresco_module_rm.bulk.hold;
 | 
			
		||||
 | 
			
		||||
import static org.alfresco.model.ContentModel.PROP_NAME;
 | 
			
		||||
import static org.alfresco.rm.rest.api.model.HoldBulkOperationType.ADD;
 | 
			
		||||
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.Locale;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.model.ContentModel;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkBaseService;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkCancellationRequest;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkOperation;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkProgress;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkStatusUpdater;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.capability.CapabilityService;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.capability.RMPermissionModel;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.hold.HoldService;
 | 
			
		||||
import org.alfresco.repo.batch.BatchProcessWorkProvider;
 | 
			
		||||
import org.alfresco.repo.batch.BatchProcessor.BatchProcessWorker;
 | 
			
		||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
 | 
			
		||||
import org.alfresco.repo.security.permissions.AccessDeniedException;
 | 
			
		||||
import org.alfresco.rest.api.search.model.Query;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
 | 
			
		||||
import org.alfresco.rm.rest.api.model.HoldBulkOperationType;
 | 
			
		||||
import org.alfresco.service.cmr.repository.NodeRef;
 | 
			
		||||
import org.alfresco.service.cmr.repository.NodeService;
 | 
			
		||||
import org.alfresco.service.cmr.search.ResultSet;
 | 
			
		||||
import org.alfresco.service.cmr.search.SearchParameters;
 | 
			
		||||
import org.alfresco.service.cmr.security.AccessStatus;
 | 
			
		||||
import org.alfresco.service.cmr.security.PermissionService;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
import org.springframework.extensions.surf.util.I18NUtil;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implementation of the {@link HoldBulkService} interface.
 | 
			
		||||
 */
 | 
			
		||||
@SuppressWarnings("PMD.PreserveStackTrace")
 | 
			
		||||
public class HoldBulkServiceImpl extends BulkBaseService<HoldBulkStatus> implements HoldBulkService
 | 
			
		||||
{
 | 
			
		||||
    private static final Logger LOGGER = LoggerFactory.getLogger(HoldBulkServiceImpl.class);
 | 
			
		||||
    private static final String MSG_ERR_ACCESS_DENIED = "permissions.err_access_denied";
 | 
			
		||||
 | 
			
		||||
    private HoldService holdService;
 | 
			
		||||
    private CapabilityService capabilityService;
 | 
			
		||||
    private PermissionService permissionService;
 | 
			
		||||
    private NodeService nodeService;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected HoldBulkStatus getInitBulkStatus(String processId, long totalItems)
 | 
			
		||||
    {
 | 
			
		||||
        return new HoldBulkStatus(processId, null, null, 0, 0, totalItems, null, false, null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected BulkStatusUpdater getBulkStatusUpdater()
 | 
			
		||||
    {
 | 
			
		||||
        return new HoldBulkStatusUpdater((HoldBulkMonitor) bulkMonitor);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected BatchProcessWorkProvider<NodeRef> getWorkProvider(BulkOperation bulkOperation,
 | 
			
		||||
        BulkStatusUpdater bulkStatusUpdater, BulkProgress bulkProgress)
 | 
			
		||||
    {
 | 
			
		||||
        return new AddToHoldWorkerProvider(bulkOperation, bulkStatusUpdater, bulkProgress,
 | 
			
		||||
            (HoldBulkMonitor) bulkMonitor);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected BatchProcessWorker<NodeRef> getWorkerProvider(NodeRef nodeRef, BulkOperation bulkOperation,
 | 
			
		||||
        BulkProgress bulkProgress)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            HoldBulkOperationType holdBulkOperationType = HoldBulkOperationType.valueOf(bulkOperation.operationType()
 | 
			
		||||
                .toUpperCase(Locale.ENGLISH));
 | 
			
		||||
            return switch (holdBulkOperationType)
 | 
			
		||||
            {
 | 
			
		||||
                case ADD -> new AddToHoldWorkerBatch(nodeRef, bulkProgress);
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
        catch (IllegalArgumentException e)
 | 
			
		||||
        {
 | 
			
		||||
            String errorMsg = "Unsupported action type when starting the bulk process: ";
 | 
			
		||||
            if (LOGGER.isDebugEnabled())
 | 
			
		||||
            {
 | 
			
		||||
                LOGGER.debug("{} {}", errorMsg, bulkOperation.operationType(), e);
 | 
			
		||||
            }
 | 
			
		||||
            throw new InvalidArgumentException(errorMsg + bulkOperation.operationType());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void checkPermissions(NodeRef holdRef, BulkOperation bulkOperation)
 | 
			
		||||
    {
 | 
			
		||||
        if (!holdService.isHold(holdRef))
 | 
			
		||||
        {
 | 
			
		||||
            final String holdName = (String) nodeService.getProperty(holdRef, PROP_NAME);
 | 
			
		||||
            throw new InvalidArgumentException(I18NUtil.getMessage("rm.hold.not-hold", holdName), null);
 | 
			
		||||
        }
 | 
			
		||||
        if (ADD.name().equals(bulkOperation.operationType()) && (!AccessStatus.ALLOWED.equals(
 | 
			
		||||
            capabilityService.getCapabilityAccessState(holdRef, RMPermissionModel.ADD_TO_HOLD)) ||
 | 
			
		||||
            permissionService.hasPermission(holdRef, RMPermissionModel.FILING) == AccessStatus.DENIED))
 | 
			
		||||
        {
 | 
			
		||||
            throw new AccessDeniedException(I18NUtil.getMessage(MSG_ERR_ACCESS_DENIED));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void cancelBulkOperation(NodeRef holdRef, String bulkStatusId, BulkCancellationRequest cancellationRequest)
 | 
			
		||||
    {
 | 
			
		||||
        if (bulkMonitor instanceof HoldBulkMonitor holdBulkMonitor)
 | 
			
		||||
        {
 | 
			
		||||
            HoldBulkStatusAndProcessDetails statusAndProcessDetails = holdBulkMonitor.getBulkStatusWithProcessDetails(
 | 
			
		||||
                holdRef.getId(), bulkStatusId);
 | 
			
		||||
 | 
			
		||||
            Optional.ofNullable(statusAndProcessDetails).map(HoldBulkStatusAndProcessDetails::holdBulkProcessDetails)
 | 
			
		||||
                .map(HoldBulkProcessDetails::bulkOperation).ifPresent(bulkOperation -> {
 | 
			
		||||
                    checkPermissions(holdRef, bulkOperation);
 | 
			
		||||
                    holdBulkMonitor.cancelBulkOperation(bulkStatusId, cancellationRequest);
 | 
			
		||||
                });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private class AddToHoldWorkerBatch implements BatchProcessWorker<NodeRef>
 | 
			
		||||
    {
 | 
			
		||||
        private final NodeRef holdRef;
 | 
			
		||||
        private final String currentUser;
 | 
			
		||||
        private final BulkProgress bulkProgress;
 | 
			
		||||
 | 
			
		||||
        public AddToHoldWorkerBatch(NodeRef holdRef, BulkProgress bulkProgress)
 | 
			
		||||
        {
 | 
			
		||||
            this.holdRef = holdRef;
 | 
			
		||||
            this.bulkProgress = bulkProgress;
 | 
			
		||||
            currentUser = AuthenticationUtil.getFullyAuthenticatedUser();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public String getIdentifier(NodeRef entry)
 | 
			
		||||
        {
 | 
			
		||||
            return entry.getId();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void beforeProcess()
 | 
			
		||||
        {
 | 
			
		||||
            AuthenticationUtil.pushAuthentication();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void process(NodeRef entry) throws Throwable
 | 
			
		||||
        {
 | 
			
		||||
            if (!bulkProgress.cancelled().get())
 | 
			
		||||
            {
 | 
			
		||||
                AuthenticationUtil.setFullyAuthenticatedUser(currentUser);
 | 
			
		||||
                holdService.addToHold(holdRef, entry);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void afterProcess()
 | 
			
		||||
        {
 | 
			
		||||
            AuthenticationUtil.popAuthentication();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private class AddToHoldWorkerProvider implements BatchProcessWorkProvider<NodeRef>
 | 
			
		||||
    {
 | 
			
		||||
        private final HoldBulkMonitor holdBulkMonitor;
 | 
			
		||||
        private final Query searchQuery;
 | 
			
		||||
        private final String currentUser;
 | 
			
		||||
        private final BulkProgress bulkProgress;
 | 
			
		||||
        private final BulkStatusUpdater bulkStatusUpdater;
 | 
			
		||||
 | 
			
		||||
        public AddToHoldWorkerProvider(BulkOperation bulkOperation,
 | 
			
		||||
            BulkStatusUpdater bulkStatusUpdater, BulkProgress bulkProgress, HoldBulkMonitor holdBulkMonitor)
 | 
			
		||||
        {
 | 
			
		||||
            this.searchQuery = bulkOperation.searchQuery();
 | 
			
		||||
            this.bulkProgress = bulkProgress;
 | 
			
		||||
            this.bulkStatusUpdater = bulkStatusUpdater;
 | 
			
		||||
            this.holdBulkMonitor = holdBulkMonitor;
 | 
			
		||||
            currentUser = AuthenticationUtil.getFullyAuthenticatedUser();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public int getTotalEstimatedWorkSize()
 | 
			
		||||
        {
 | 
			
		||||
            return (int) bulkProgress.totalItems();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public long getTotalEstimatedWorkSizeLong()
 | 
			
		||||
        {
 | 
			
		||||
            return bulkProgress.totalItems();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public Collection<NodeRef> getNextWork()
 | 
			
		||||
        {
 | 
			
		||||
            AuthenticationUtil.pushAuthentication();
 | 
			
		||||
            AuthenticationUtil.setFullyAuthenticatedUser(currentUser);
 | 
			
		||||
            if (holdBulkMonitor.isCancelled(bulkProgress.processId()))
 | 
			
		||||
            {
 | 
			
		||||
                bulkProgress.cancelled().set(true);
 | 
			
		||||
                return Collections.emptyList();
 | 
			
		||||
            }
 | 
			
		||||
            SearchParameters searchParams = getNextPageParameters();
 | 
			
		||||
            ResultSet result = searchService.query(searchParams);
 | 
			
		||||
            if (result.getNodeRefs().isEmpty())
 | 
			
		||||
            {
 | 
			
		||||
                return Collections.emptyList();
 | 
			
		||||
            }
 | 
			
		||||
            AuthenticationUtil.popAuthentication();
 | 
			
		||||
            if (LOGGER.isDebugEnabled())
 | 
			
		||||
            {
 | 
			
		||||
                LOGGER.debug("Processing the next work for the batch processor, skipCount={}, size={}",
 | 
			
		||||
                    searchParams.getSkipCount(), result.getNumberFound());
 | 
			
		||||
            }
 | 
			
		||||
            bulkProgress.currentNodeNumber().addAndGet(batchSize);
 | 
			
		||||
            bulkStatusUpdater.update();
 | 
			
		||||
            return result.getNodeRefs();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private SearchParameters getNextPageParameters()
 | 
			
		||||
        {
 | 
			
		||||
            SearchParameters searchParams = new SearchParameters();
 | 
			
		||||
            searchMapper.setDefaults(searchParams);
 | 
			
		||||
            searchMapper.fromQuery(searchParams, searchQuery);
 | 
			
		||||
            searchParams.setSkipCount(bulkProgress.currentNodeNumber().get());
 | 
			
		||||
            searchParams.setMaxItems(batchSize);
 | 
			
		||||
            searchParams.setLimit(batchSize);
 | 
			
		||||
            searchParams.addSort("@" + ContentModel.PROP_CREATED, true);
 | 
			
		||||
            return searchParams;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setHoldService(HoldService holdService)
 | 
			
		||||
    {
 | 
			
		||||
        this.holdService = holdService;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setCapabilityService(CapabilityService capabilityService)
 | 
			
		||||
    {
 | 
			
		||||
        this.capabilityService = capabilityService;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setPermissionService(PermissionService permissionService)
 | 
			
		||||
    {
 | 
			
		||||
        this.permissionService = permissionService;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setNodeService(NodeService nodeService)
 | 
			
		||||
    {
 | 
			
		||||
        this.nodeService = nodeService;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,78 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 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.module.org_alfresco_module_rm.bulk.hold;
 | 
			
		||||
 | 
			
		||||
import java.io.Serializable;
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An immutable POJO that contains the status of a hold bulk operation
 | 
			
		||||
 */
 | 
			
		||||
public record HoldBulkStatus(String bulkStatusId, Date startTime, Date endTime, long processedItems, long errorsCount,
 | 
			
		||||
                             long totalItems, String lastError, boolean isCancelled, String cancellationReason)
 | 
			
		||||
    implements Serializable
 | 
			
		||||
{
 | 
			
		||||
    public enum Status
 | 
			
		||||
    {
 | 
			
		||||
        PENDING("PENDING"),
 | 
			
		||||
        IN_PROGRESS("IN PROGRESS"),
 | 
			
		||||
        DONE("DONE"),
 | 
			
		||||
        CANCELLED("CANCELLED");
 | 
			
		||||
 | 
			
		||||
        private final String value;
 | 
			
		||||
 | 
			
		||||
        Status(String value)
 | 
			
		||||
        {
 | 
			
		||||
            this.value = value;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public String getValue()
 | 
			
		||||
        {
 | 
			
		||||
            return value;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getStatus()
 | 
			
		||||
    {
 | 
			
		||||
        if (isCancelled)
 | 
			
		||||
        {
 | 
			
		||||
            return Status.CANCELLED.getValue();
 | 
			
		||||
        }
 | 
			
		||||
        else if (startTime == null && endTime == null)
 | 
			
		||||
        {
 | 
			
		||||
            return Status.PENDING.getValue();
 | 
			
		||||
        }
 | 
			
		||||
        else if (startTime != null && endTime == null)
 | 
			
		||||
        {
 | 
			
		||||
            return Status.IN_PROGRESS.getValue();
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            return Status.DONE.getValue();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,35 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 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.module.org_alfresco_module_rm.bulk.hold;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An immutable POJO that contains the status of a hold bulk operation and the details of the process
 | 
			
		||||
 */
 | 
			
		||||
public record HoldBulkStatusAndProcessDetails(HoldBulkStatus holdBulkStatus,
 | 
			
		||||
                                              HoldBulkProcessDetails holdBulkProcessDetails)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,77 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 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.module.org_alfresco_module_rm.bulk.hold;
 | 
			
		||||
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkCancellationRequest;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkStatusUpdater;
 | 
			
		||||
import org.alfresco.repo.batch.BatchMonitor;
 | 
			
		||||
import org.alfresco.repo.batch.BatchMonitorEvent;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An implementation of {@link BulkStatusUpdater} for the hold bulk operation
 | 
			
		||||
 */
 | 
			
		||||
public class HoldBulkStatusUpdater implements BulkStatusUpdater
 | 
			
		||||
{
 | 
			
		||||
    private final Runnable task;
 | 
			
		||||
    private BatchMonitor batchMonitor;
 | 
			
		||||
 | 
			
		||||
    public HoldBulkStatusUpdater(HoldBulkMonitor holdBulkMonitor)
 | 
			
		||||
    {
 | 
			
		||||
        this.task = () -> holdBulkMonitor.updateBulkStatus(
 | 
			
		||||
            new HoldBulkStatus(batchMonitor.getProcessName(),
 | 
			
		||||
                batchMonitor.getStartTime(),
 | 
			
		||||
                batchMonitor.getEndTime(),
 | 
			
		||||
                batchMonitor.getSuccessfullyProcessedEntriesLong() + batchMonitor.getTotalErrorsLong(),
 | 
			
		||||
                batchMonitor.getTotalErrorsLong(),
 | 
			
		||||
                batchMonitor.getTotalResultsLong(),
 | 
			
		||||
                batchMonitor.getLastError(),
 | 
			
		||||
                holdBulkMonitor.isCancelled(batchMonitor.getProcessName()),
 | 
			
		||||
                Optional.ofNullable(holdBulkMonitor.getBulkCancellationRequest(batchMonitor.getProcessName()))
 | 
			
		||||
                    .map(BulkCancellationRequest::reason)
 | 
			
		||||
                    .orElse(null)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void update()
 | 
			
		||||
    {
 | 
			
		||||
        if (task != null && batchMonitor != null)
 | 
			
		||||
        {
 | 
			
		||||
            task.run();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void publishEvent(Object event)
 | 
			
		||||
    {
 | 
			
		||||
        if (event instanceof BatchMonitorEvent batchMonitorEvent)
 | 
			
		||||
        {
 | 
			
		||||
            batchMonitor = batchMonitorEvent.getBatchMonitor();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,69 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 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.module.org_alfresco_module_rm.bulk.hold;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkOperation;
 | 
			
		||||
import org.alfresco.rest.api.search.model.Query;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
 | 
			
		||||
import org.alfresco.rm.rest.api.model.HoldBulkOperation;
 | 
			
		||||
import org.alfresco.rm.rest.api.model.HoldBulkOperationType;
 | 
			
		||||
import org.alfresco.rm.rest.api.model.HoldBulkStatusEntry;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Utility class for hold bulk operations
 | 
			
		||||
 */
 | 
			
		||||
@SuppressWarnings("PMD.PreserveStackTrace")
 | 
			
		||||
public final class HoldBulkUtils
 | 
			
		||||
{
 | 
			
		||||
    private HoldBulkUtils()
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static HoldBulkStatusEntry toHoldBulkStatusEntry(
 | 
			
		||||
        HoldBulkStatusAndProcessDetails holdBulkStatusAndProcessDetails)
 | 
			
		||||
    {
 | 
			
		||||
        HoldBulkStatus bulkStatus = holdBulkStatusAndProcessDetails.holdBulkStatus();
 | 
			
		||||
        BulkOperation bulkOperation = holdBulkStatusAndProcessDetails.holdBulkProcessDetails().bulkOperation();
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            HoldBulkOperation holdBulkOperation = new HoldBulkOperation(
 | 
			
		||||
                new Query(bulkOperation.searchQuery().getLanguage(),
 | 
			
		||||
                    bulkOperation.searchQuery().getQuery(), bulkOperation.searchQuery().getUserQuery()),
 | 
			
		||||
                HoldBulkOperationType.valueOf(bulkOperation.operationType()));
 | 
			
		||||
            return new HoldBulkStatusEntry(bulkStatus.bulkStatusId(), bulkStatus.startTime(),
 | 
			
		||||
                bulkStatus.endTime(), bulkStatus.processedItems(), bulkStatus.errorsCount(),
 | 
			
		||||
                bulkStatus.totalItems(), bulkStatus.lastError(), bulkStatus.getStatus(),
 | 
			
		||||
                bulkStatus.cancellationReason(), holdBulkOperation);
 | 
			
		||||
        }
 | 
			
		||||
        catch (IllegalArgumentException e)
 | 
			
		||||
        {
 | 
			
		||||
            String errorMsg = "Unsupported action type in the bulk operation: ";
 | 
			
		||||
            throw new InvalidArgumentException(errorMsg + bulkOperation.operationType());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -31,7 +31,6 @@ import java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.model.ContentModel;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.capability.RMPermissionModel;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.capability.declarative.DeclarativeCapability;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.record.RecordService;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.recordfolder.RecordFolderService;
 | 
			
		||||
@@ -76,7 +75,7 @@ public class CreateCapability extends DeclarativeCapability
 | 
			
		||||
    @Override
 | 
			
		||||
    public int evaluate(NodeRef nodeRef)
 | 
			
		||||
    {
 | 
			
		||||
        return evaluate(nodeRef, null, null);
 | 
			
		||||
        return evaluate(nodeRef, null, null, null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -85,9 +84,10 @@ public class CreateCapability extends DeclarativeCapability
 | 
			
		||||
     * @param destination   destination node reference
 | 
			
		||||
     * @param linkee        linkee node reference, can be null
 | 
			
		||||
     * @param assocType     association type, can be null
 | 
			
		||||
     * @param recordType    record type, can be null
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    public int evaluate(NodeRef destination, NodeRef linkee, QName assocType)
 | 
			
		||||
    public int evaluate(NodeRef destination, NodeRef linkee, QName assocType, QName recordType)
 | 
			
		||||
    {
 | 
			
		||||
        if (linkee != null)
 | 
			
		||||
        {
 | 
			
		||||
@@ -105,7 +105,7 @@ public class CreateCapability extends DeclarativeCapability
 | 
			
		||||
                {
 | 
			
		||||
                    if (recordService.isRecord(destination) &&
 | 
			
		||||
                        !recordService.isDeclared(destination) &&
 | 
			
		||||
                        permissionService.hasPermission(destination, RMPermissionModel.FILE_RECORDS) == AccessStatus.ALLOWED)
 | 
			
		||||
                        permissionService.hasPermission(destination, FILE_RECORDS) == AccessStatus.ALLOWED)
 | 
			
		||||
                    {
 | 
			
		||||
                        return AccessDecisionVoter.ACCESS_GRANTED;
 | 
			
		||||
                    }
 | 
			
		||||
@@ -115,7 +115,7 @@ public class CreateCapability extends DeclarativeCapability
 | 
			
		||||
                    if (recordService.isRecord(linkee) &&
 | 
			
		||||
                            recordService.isRecord(destination) &&
 | 
			
		||||
                            !recordService.isDeclared(destination) &&
 | 
			
		||||
                            permissionService.hasPermission(destination, RMPermissionModel.FILE_RECORDS) == AccessStatus.ALLOWED)
 | 
			
		||||
                            permissionService.hasPermission(destination, FILE_RECORDS) == AccessStatus.ALLOWED)
 | 
			
		||||
                    {
 | 
			
		||||
                        return AccessDecisionVoter.ACCESS_GRANTED;
 | 
			
		||||
                    }
 | 
			
		||||
@@ -132,14 +132,15 @@ public class CreateCapability extends DeclarativeCapability
 | 
			
		||||
 | 
			
		||||
            // if the destination folder is not a record folder and the user has filling capability on it, grant access to create the record
 | 
			
		||||
            if (checkConditions(destination, conditions) &&
 | 
			
		||||
                   !recordFolderService.isRecordFolder(destination) )
 | 
			
		||||
                   !recordFolderService.isRecordFolder(destination) &&
 | 
			
		||||
                    permissionService.hasPermission(destination, CREATE_MODIFY_DESTROY_FILEPLAN_METADATA) == AccessStatus.ALLOWED)
 | 
			
		||||
            {
 | 
			
		||||
                return AccessDecisionVoter.ACCESS_GRANTED;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (checkConditions(destination, conditions) &&
 | 
			
		||||
                    recordFolderService.isRecordFolder(destination) &&
 | 
			
		||||
                    permissionService.hasPermission(destination, RMPermissionModel.FILE_RECORDS) == AccessStatus.ALLOWED)
 | 
			
		||||
                    permissionService.hasPermission(destination, FILE_RECORDS) == AccessStatus.ALLOWED)
 | 
			
		||||
            {
 | 
			
		||||
                return AccessDecisionVoter.ACCESS_GRANTED;
 | 
			
		||||
            }
 | 
			
		||||
@@ -147,7 +148,7 @@ public class CreateCapability extends DeclarativeCapability
 | 
			
		||||
            conditions.put("capabilityCondition.closed", Boolean.TRUE);
 | 
			
		||||
            if (checkConditions(destination, conditions) &&
 | 
			
		||||
                    recordFolderService.isRecordFolder(destination) &&
 | 
			
		||||
                    permissionService.hasPermission(getFilePlanService().getFilePlan(destination), RMPermissionModel.DECLARE_RECORDS_IN_CLOSED_FOLDERS) == AccessStatus.ALLOWED)
 | 
			
		||||
                    permissionService.hasPermission(getFilePlanService().getFilePlan(destination), DECLARE_RECORDS_IN_CLOSED_FOLDERS) == AccessStatus.ALLOWED)
 | 
			
		||||
            {
 | 
			
		||||
                return AccessDecisionVoter.ACCESS_GRANTED;
 | 
			
		||||
            }
 | 
			
		||||
@@ -156,32 +157,32 @@ public class CreateCapability extends DeclarativeCapability
 | 
			
		||||
            conditions.put("capabilityCondition.cutoff", Boolean.TRUE);
 | 
			
		||||
            if (checkConditions(destination, conditions) &&
 | 
			
		||||
                    recordFolderService.isRecordFolder(destination) &&
 | 
			
		||||
                    permissionService.hasPermission(getFilePlanService().getFilePlan(destination), RMPermissionModel.CREATE_MODIFY_RECORDS_IN_CUTOFF_FOLDERS) == AccessStatus.ALLOWED)
 | 
			
		||||
                    permissionService.hasPermission(getFilePlanService().getFilePlan(destination), CREATE_MODIFY_RECORDS_IN_CUTOFF_FOLDERS) == AccessStatus.ALLOWED)
 | 
			
		||||
            {
 | 
			
		||||
                return AccessDecisionVoter.ACCESS_GRANTED;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (capabilityService.getCapability(RMPermissionModel.CREATE_MODIFY_DESTROY_FOLDERS).evaluate(destination) == AccessDecisionVoter.ACCESS_GRANTED)
 | 
			
		||||
        if (null != recordType && recordType.equals(TYPE_RECORD_FOLDER) && capabilityService.getCapability(CREATE_MODIFY_DESTROY_FOLDERS).evaluate(destination) == AccessDecisionVoter.ACCESS_GRANTED)
 | 
			
		||||
        {
 | 
			
		||||
            return AccessDecisionVoter.ACCESS_GRANTED;
 | 
			
		||||
        }
 | 
			
		||||
        if (capabilityService.getCapability(RMPermissionModel.DECLARE_RECORDS_IN_CLOSED_FOLDERS).evaluate(destination) == AccessDecisionVoter.ACCESS_GRANTED)
 | 
			
		||||
        if (capabilityService.getCapability(DECLARE_RECORDS_IN_CLOSED_FOLDERS).evaluate(destination) == AccessDecisionVoter.ACCESS_GRANTED)
 | 
			
		||||
        {
 | 
			
		||||
            return AccessDecisionVoter.ACCESS_GRANTED;
 | 
			
		||||
        }
 | 
			
		||||
        if (capabilityService.getCapability(RMPermissionModel.CREATE_MODIFY_RECORDS_IN_CUTOFF_FOLDERS).evaluate(destination) == AccessDecisionVoter.ACCESS_GRANTED)
 | 
			
		||||
        if (capabilityService.getCapability(CREATE_MODIFY_RECORDS_IN_CUTOFF_FOLDERS).evaluate(destination) == AccessDecisionVoter.ACCESS_GRANTED)
 | 
			
		||||
        {
 | 
			
		||||
            return AccessDecisionVoter.ACCESS_GRANTED;
 | 
			
		||||
        }
 | 
			
		||||
        if (capabilityService.getCapability(RMPermissionModel.CREATE_MODIFY_DESTROY_FILEPLAN_METADATA).evaluate(destination) == AccessDecisionVoter.ACCESS_GRANTED)
 | 
			
		||||
        if (capabilityService.getCapability(CREATE_MODIFY_DESTROY_FILEPLAN_METADATA).evaluate(destination) == AccessDecisionVoter.ACCESS_GRANTED)
 | 
			
		||||
        {
 | 
			
		||||
            return AccessDecisionVoter.ACCESS_GRANTED;
 | 
			
		||||
        }
 | 
			
		||||
        if (capabilityService.getCapability(RMPermissionModel.CREATE_HOLD).evaluate(destination) == AccessDecisionVoter.ACCESS_GRANTED)
 | 
			
		||||
        if (capabilityService.getCapability(CREATE_HOLD).evaluate(destination) == AccessDecisionVoter.ACCESS_GRANTED)
 | 
			
		||||
        {
 | 
			
		||||
            return AccessDecisionVoter.ACCESS_GRANTED;
 | 
			
		||||
        }
 | 
			
		||||
        if (((ChangeOrDeleteReferencesCapability)capabilityService.getCapability(RMPermissionModel.CHANGE_OR_DELETE_REFERENCES)).evaluate(destination, linkee) == AccessDecisionVoter.ACCESS_GRANTED)
 | 
			
		||||
        if (((ChangeOrDeleteReferencesCapability)capabilityService.getCapability(CHANGE_OR_DELETE_REFERENCES)).evaluate(destination, linkee) == AccessDecisionVoter.ACCESS_GRANTED)
 | 
			
		||||
        {
 | 
			
		||||
            return AccessDecisionVoter.ACCESS_GRANTED;
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,7 @@
 | 
			
		||||
package org.alfresco.module.org_alfresco_module_rm.capability.policy;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.capability.impl.CreateCapability;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
 | 
			
		||||
import org.alfresco.service.cmr.repository.NodeRef;
 | 
			
		||||
import org.alfresco.service.namespace.QName;
 | 
			
		||||
import org.aopalliance.intercept.MethodInvocation;
 | 
			
		||||
@@ -42,10 +43,18 @@ public class CreatePolicy extends AbstractBasePolicy
 | 
			
		||||
    {
 | 
			
		||||
        NodeRef linkee = null;
 | 
			
		||||
        QName assocType = null;
 | 
			
		||||
        QName recordType = null;
 | 
			
		||||
 | 
			
		||||
        // get the destination node
 | 
			
		||||
        NodeRef destination = getTestNode(invocation, params, cad.getParameters().get(0), cad.isParent());
 | 
			
		||||
 | 
			
		||||
        //get the recordType
 | 
			
		||||
        for (Object qname : invocation.getArguments()) {
 | 
			
		||||
            if (qname != null && (qname.equals(RecordsManagementModel.TYPE_RECORD_FOLDER) || qname.equals(RecordsManagementModel.TYPE_RECORD_CATEGORY))) {
 | 
			
		||||
                recordType = (QName) qname;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (cad.getParameters().size() > 1)
 | 
			
		||||
        {
 | 
			
		||||
            // get the linkee when present
 | 
			
		||||
@@ -58,7 +67,7 @@ public class CreatePolicy extends AbstractBasePolicy
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return ((CreateCapability) getCapabilityService().getCapability("Create")).evaluate(destination, linkee, assocType);
 | 
			
		||||
        return ((CreateCapability) getCapabilityService().getCapability("Create")).evaluate(destination, linkee, assocType, recordType);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -59,6 +59,9 @@ 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.RetryingTransactionCallback;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.ConstraintViolatedException;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
 | 
			
		||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
 | 
			
		||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
 | 
			
		||||
import org.alfresco.service.cmr.repository.NodeRef;
 | 
			
		||||
@@ -198,7 +201,7 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
    /**
 | 
			
		||||
     * Behavior to initialize the disposition schedule of a newly filed record.
 | 
			
		||||
     *
 | 
			
		||||
     * @see org.alfresco.module.org_alfresco_module_rm.RecordsManagementPolicies.OnFileRecord#onFileRecord(org.alfresco.service.cmr.repository.NodeRef)
 | 
			
		||||
     * @see RecordsManagementPolicies.OnFileRecord#onFileRecord(NodeRef)
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    @Behaviour(kind=BehaviourKind.CLASS, type="rma:record")
 | 
			
		||||
@@ -216,7 +219,7 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#refreshDispositionAction(NodeRef)
 | 
			
		||||
     * @see DispositionService#refreshDispositionAction(NodeRef)
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void refreshDispositionAction(NodeRef nodeRef)
 | 
			
		||||
@@ -242,7 +245,7 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
    /** ========= Disposition Property Methods ========= */
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#registerDispositionProperty(org.alfresco.module.org_alfresco_module_rm.disposition.property.DispositionProperty)
 | 
			
		||||
     * @see DispositionService#registerDispositionProperty(DispositionProperty)
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void registerDispositionProperty(DispositionProperty dispositionProperty)
 | 
			
		||||
@@ -251,7 +254,7 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#getDispositionProperties(boolean, java.lang.String)
 | 
			
		||||
     * @see DispositionService#getDispositionProperties(boolean, String)
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public Collection<DispositionProperty> getDispositionProperties(boolean isRecordLevel, String dispositionAction)
 | 
			
		||||
@@ -270,7 +273,7 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#getDispositionProperties()
 | 
			
		||||
     * @see DispositionService#getDispositionProperties()
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public Collection<DispositionProperty> getDispositionProperties()
 | 
			
		||||
@@ -281,12 +284,11 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
    /** ========= Disposition Schedule Methods ========= */
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#getDispositionSchedule(org.alfresco.service.cmr.repository.NodeRef)
 | 
			
		||||
     * @see DispositionService#getDispositionSchedule(NodeRef)
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public DispositionSchedule getDispositionSchedule(final NodeRef nodeRef)
 | 
			
		||||
    {
 | 
			
		||||
        DispositionSchedule ds = null;
 | 
			
		||||
        NodeRef dsNodeRef = null;
 | 
			
		||||
        if (isRecord(nodeRef))
 | 
			
		||||
        {
 | 
			
		||||
@@ -311,36 +313,33 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
            if (dsNextAction != null)
 | 
			
		||||
            {
 | 
			
		||||
                final NodeRef action = dsNextAction.getNextActionNodeRef();
 | 
			
		||||
                if (isNotTrue((Boolean)nodeService.getProperty(action, PROP_MANUALLY_SET_AS_OF)))
 | 
			
		||||
                if (isNotTrue((Boolean)nodeService.getProperty(action, PROP_MANUALLY_SET_AS_OF)) && !dsNextAction.getWriteMode().equals(WriteMode.READ_ONLY))
 | 
			
		||||
                {
 | 
			
		||||
                    if (!dsNextAction.getWriteMode().equals(WriteMode.READ_ONLY))
 | 
			
		||||
                    final String dispositionActionName = dsNextAction.getNextActionName();
 | 
			
		||||
                    final Date dispositionActionDate = dsNextAction.getNextActionDateAsOf();
 | 
			
		||||
 | 
			
		||||
                    RunAsWork<Void> runAsWork = () -> {
 | 
			
		||||
                        nodeService.setProperty(action, PROP_DISPOSITION_AS_OF, dispositionActionDate);
 | 
			
		||||
                        return null;
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    // if the current transaction is READ ONLY set the property on the node
 | 
			
		||||
                    // in a READ WRITE transaction
 | 
			
		||||
                    if (AlfrescoTransactionSupport.getTransactionReadState().equals(TxnReadState.TXN_READ_ONLY))
 | 
			
		||||
                    {
 | 
			
		||||
                        final String dispositionActionName = dsNextAction.getNextActionName();
 | 
			
		||||
                        final Date dispositionActionDate = dsNextAction.getNextActionDateAsOf();
 | 
			
		||||
 | 
			
		||||
                        RunAsWork<Void> runAsWork = () -> {
 | 
			
		||||
                            nodeService.setProperty(action, PROP_DISPOSITION_AS_OF, dispositionActionDate);
 | 
			
		||||
                            return null;
 | 
			
		||||
                        };
 | 
			
		||||
 | 
			
		||||
                        // if the current transaction is READ ONLY set the property on the node
 | 
			
		||||
                        // in a READ WRITE transaction
 | 
			
		||||
                        if (AlfrescoTransactionSupport.getTransactionReadState().equals(TxnReadState.TXN_READ_ONLY))
 | 
			
		||||
                        {
 | 
			
		||||
                            transactionService.getRetryingTransactionHelper().doInTransaction((RetryingTransactionCallback<Void>) () -> {
 | 
			
		||||
                                AuthenticationUtil.runAsSystem(runAsWork);
 | 
			
		||||
                                return null;
 | 
			
		||||
                            }, false, true);
 | 
			
		||||
                        }
 | 
			
		||||
                        else
 | 
			
		||||
                        {
 | 
			
		||||
                        transactionService.getRetryingTransactionHelper().doInTransaction((RetryingTransactionCallback<Void>) () -> {
 | 
			
		||||
                            AuthenticationUtil.runAsSystem(runAsWork);
 | 
			
		||||
                        }
 | 
			
		||||
                            return null;
 | 
			
		||||
                        }, false, true);
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        AuthenticationUtil.runAsSystem(runAsWork);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                        if (dsNextAction.getWriteMode().equals(WriteMode.DATE_AND_NAME))
 | 
			
		||||
                        {
 | 
			
		||||
                            nodeService.setProperty(action, PROP_DISPOSITION_ACTION_NAME, dispositionActionName);
 | 
			
		||||
                        }
 | 
			
		||||
                    if (dsNextAction.getWriteMode().equals(WriteMode.DATE_AND_NAME))
 | 
			
		||||
                    {
 | 
			
		||||
                        nodeService.setProperty(action, PROP_DISPOSITION_ACTION_NAME, dispositionActionName);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
@@ -352,7 +351,7 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
            // Get the disposition instructions for the node reference provided
 | 
			
		||||
            dsNodeRef = getDispositionScheduleImpl(nodeRef);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        DispositionSchedule ds = null;
 | 
			
		||||
        if (dsNodeRef != null)
 | 
			
		||||
        {
 | 
			
		||||
            ds = new DispositionScheduleImpl(serviceRegistry, nodeService, dsNodeRef);
 | 
			
		||||
@@ -382,7 +381,8 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
        }
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public DispositionSchedule getOriginDispositionSchedule(NodeRef nodeRef)
 | 
			
		||||
    {
 | 
			
		||||
        NodeRef parent = this.nodeService.getPrimaryParent(nodeRef).getParentRef();
 | 
			
		||||
@@ -406,7 +406,7 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#getAssociatedDispositionSchedule(org.alfresco.service.cmr.repository.NodeRef)
 | 
			
		||||
     * @see DispositionService#getAssociatedDispositionSchedule(NodeRef)
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public DispositionSchedule getAssociatedDispositionSchedule(NodeRef nodeRef)
 | 
			
		||||
@@ -437,7 +437,6 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
     */
 | 
			
		||||
    private NodeRef getAssociatedDispositionScheduleImpl(NodeRef nodeRef)
 | 
			
		||||
    {
 | 
			
		||||
        NodeRef result = null;
 | 
			
		||||
        ParameterCheck.mandatory("nodeRef", nodeRef);
 | 
			
		||||
 | 
			
		||||
        // Make sure we are dealing with an RM node
 | 
			
		||||
@@ -445,6 +444,7 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
        {
 | 
			
		||||
            throw new AlfrescoRuntimeException("Can not find the associated retention schedule for a non records management component. (nodeRef=" + nodeRef.toString() + ")");
 | 
			
		||||
        }
 | 
			
		||||
        NodeRef result = null;
 | 
			
		||||
        if (getInternalNodeService().hasAspect(nodeRef, ASPECT_SCHEDULED))
 | 
			
		||||
        {
 | 
			
		||||
            List<ChildAssociationRef> childAssocs = getInternalNodeService().getChildAssocs(nodeRef, ASSOC_DISPOSITION_SCHEDULE, RegexQNamePattern.MATCH_ALL);
 | 
			
		||||
@@ -459,7 +459,7 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#getAssociatedRecordsManagementContainer(org.alfresco.module.org_alfresco_module_rm.disposition.DispositionSchedule)
 | 
			
		||||
     * @see DispositionService#getAssociatedRecordsManagementContainer(DispositionSchedule)
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public NodeRef getAssociatedRecordsManagementContainer(DispositionSchedule dispositionSchedule)
 | 
			
		||||
@@ -477,12 +477,9 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
                {
 | 
			
		||||
                    // TODO in the future we should be able to support disposition schedule reuse, but for now just warn that
 | 
			
		||||
                    //      only the first disposition schedule will be considered
 | 
			
		||||
                    if (LOGGER.isWarnEnabled())
 | 
			
		||||
                    {
 | 
			
		||||
                        LOGGER.warn("Retention schedule has more than one associated records management container.  " +
 | 
			
		||||
                                "This is not currently supported so only the first container will be considered. " +
 | 
			
		||||
                                "(dispositionScheduleNodeRef=" + dispositionSchedule.getNodeRef().toString() + ")");
 | 
			
		||||
                    }
 | 
			
		||||
                    LOGGER.atWarn().log("Retention schedule has more than one associated records management container.  " +
 | 
			
		||||
                            "This is not currently supported so only the first container will be considered. " +
 | 
			
		||||
                            "(dispositionScheduleNodeRef={})", dispositionSchedule.getNodeRef());
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Get the container reference
 | 
			
		||||
@@ -495,7 +492,7 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#hasDisposableItems(org.alfresco.module.org_alfresco_module_rm.disposition.DispositionSchedule)
 | 
			
		||||
     * @see DispositionService#hasDisposableItems(DispositionSchedule)
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean hasDisposableItems(DispositionSchedule dispositionSchdule)
 | 
			
		||||
@@ -537,19 +534,16 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else if (filePlanService.isRecordCategory(item) && getAssociatedDispositionScheduleImpl(item) == null)
 | 
			
		||||
            else if (filePlanService.isRecordCategory(item) && getAssociatedDispositionScheduleImpl(item) == null && hasDisposableItemsImpl(isRecordLevelDisposition, item))
 | 
			
		||||
            {
 | 
			
		||||
                if (hasDisposableItemsImpl(isRecordLevelDisposition, item));
 | 
			
		||||
                {
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#getDisposableItems(org.alfresco.module.org_alfresco_module_rm.disposition.DispositionSchedule)
 | 
			
		||||
     * @see DispositionService#getDisposableItems(DispositionSchedule)
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<NodeRef> getDisposableItems(DispositionSchedule dispositionSchedule)
 | 
			
		||||
@@ -564,7 +558,7 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#isDisposableItem(org.alfresco.service.cmr.repository.NodeRef)
 | 
			
		||||
     * @see DispositionService#isDisposableItem(NodeRef)
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isDisposableItem(NodeRef nodeRef)
 | 
			
		||||
@@ -604,20 +598,18 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#createDispositionSchedule(org.alfresco.service.cmr.repository.NodeRef, java.util.Map)
 | 
			
		||||
     * @see DispositionService#createDispositionSchedule(NodeRef, Map)
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public DispositionSchedule createDispositionSchedule(NodeRef nodeRef, Map<QName, Serializable> props)
 | 
			
		||||
    {
 | 
			
		||||
        NodeRef dsNodeRef = null;
 | 
			
		||||
 | 
			
		||||
        // Check mandatory parameters
 | 
			
		||||
        ParameterCheck.mandatory("nodeRef", nodeRef);
 | 
			
		||||
 | 
			
		||||
        // Check exists
 | 
			
		||||
        if (!nodeService.exists(nodeRef))
 | 
			
		||||
        {
 | 
			
		||||
            throw new AlfrescoRuntimeException("Unable to create retention schedule, because node does not exist. (nodeRef=" + nodeRef.toString() + ")");
 | 
			
		||||
            throw new EntityNotFoundException(nodeRef.getId());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Check is sub-type of rm:recordCategory
 | 
			
		||||
@@ -625,10 +617,12 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
        if (!TYPE_RECORD_CATEGORY.equals(nodeRefType) &&
 | 
			
		||||
            !dictionaryService.isSubClass(nodeRefType, TYPE_RECORD_CATEGORY))
 | 
			
		||||
        {
 | 
			
		||||
            throw new AlfrescoRuntimeException("Unable to create retention schedule on a node that is not a records management container.");
 | 
			
		||||
            throw new InvalidArgumentException("The given id:'" + nodeRef.getId() + "' (nodeType:" + nodeRef
 | 
			
		||||
                    + ") is not valid. Expected nodeType is:" + TYPE_RECORD_CATEGORY);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        behaviourFilter.disableBehaviour(nodeRef, ASPECT_SCHEDULED);
 | 
			
		||||
        NodeRef dsNodeRef = null;
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            // Add the schedules aspect if required
 | 
			
		||||
@@ -662,7 +656,7 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                // Error since the node already has a disposition schedule set
 | 
			
		||||
                throw new AlfrescoRuntimeException("Unable to create retention schedule on node that already has a retention schedule.");
 | 
			
		||||
                throw new ConstraintViolatedException("Unable to create retention schedule on node that already has a retention schedule.");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
@@ -686,7 +680,7 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
    {
 | 
			
		||||
        // make sure at least a name has been defined
 | 
			
		||||
        String name = (String)actionDefinitionParams.get(PROP_DISPOSITION_ACTION_NAME);
 | 
			
		||||
        if (name == null || name.length() == 0)
 | 
			
		||||
        if (name == null || name.isEmpty())
 | 
			
		||||
        {
 | 
			
		||||
            throw new IllegalArgumentException("'name' parameter is mandatory when creating a disposition action definition");
 | 
			
		||||
        }
 | 
			
		||||
@@ -695,10 +689,10 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
 | 
			
		||||
        // create the child association from the schedule to the action definition
 | 
			
		||||
        NodeRef actionNodeRef = this.nodeService.createNode(schedule.getNodeRef(),
 | 
			
		||||
                    RecordsManagementModel.ASSOC_DISPOSITION_ACTION_DEFINITIONS,
 | 
			
		||||
                    ASSOC_DISPOSITION_ACTION_DEFINITIONS,
 | 
			
		||||
                    QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI,
 | 
			
		||||
                    QName.createValidLocalName(name)),
 | 
			
		||||
                    RecordsManagementModel.TYPE_DISPOSITION_ACTION_DEFINITION, actionDefinitionParams).getChildRef();
 | 
			
		||||
                    TYPE_DISPOSITION_ACTION_DEFINITION, actionDefinitionParams).getChildRef();
 | 
			
		||||
 | 
			
		||||
        // get the updated disposition schedule and retrieve the new action definition
 | 
			
		||||
        NodeRef scheduleParent = this.nodeService.getPrimaryParent(schedule.getNodeRef()).getParentRef();
 | 
			
		||||
@@ -707,7 +701,7 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#removeDispositionActionDefinition(org.alfresco.module.org_alfresco_module_rm.disposition.DispositionSchedule, org.alfresco.module.org_alfresco_module_rm.disposition.DispositionActionDefinition)
 | 
			
		||||
     * @see DispositionService#removeDispositionActionDefinition(DispositionSchedule, DispositionActionDefinition)
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void removeDispositionActionDefinition(DispositionSchedule schedule, DispositionActionDefinition actionDefinition)
 | 
			
		||||
@@ -777,16 +771,12 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
 | 
			
		||||
        DispositionAction da;
 | 
			
		||||
        // check if current transaction is a READ ONLY one and if true create the node in a READ WRITE transaction
 | 
			
		||||
        if (AlfrescoTransactionSupport.getTransactionReadState().equals(TxnReadState.TXN_READ_ONLY))
 | 
			
		||||
        {
 | 
			
		||||
            da =
 | 
			
		||||
                    transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback<DispositionAction>()
 | 
			
		||||
                    {
 | 
			
		||||
                        public DispositionAction execute() throws Throwable
 | 
			
		||||
                        {
 | 
			
		||||
                            return createDispositionAction(nodeRef, props);
 | 
			
		||||
                        }
 | 
			
		||||
                    }, false, true);
 | 
			
		||||
        if (AlfrescoTransactionSupport.getTransactionReadState().equals(TxnReadState.TXN_READ_ONLY)) {
 | 
			
		||||
            da = transactionService.getRetryingTransactionHelper().doInTransaction(
 | 
			
		||||
                    () -> createDispositionAction(nodeRef, props),
 | 
			
		||||
                    false,
 | 
			
		||||
                    true
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
@@ -836,13 +826,13 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
        Period period = dispositionActionDefinition.getPeriod();
 | 
			
		||||
        if (period != null)
 | 
			
		||||
        {
 | 
			
		||||
            Date contextDate = null;
 | 
			
		||||
            Date contextDate;
 | 
			
		||||
 | 
			
		||||
            // Get the period properties value
 | 
			
		||||
            QName periodProperty = dispositionActionDefinition.getPeriodProperty();
 | 
			
		||||
            if (periodProperty != null)
 | 
			
		||||
            {
 | 
			
		||||
                if (RecordsManagementModel.PROP_DISPOSITION_AS_OF.equals(periodProperty))
 | 
			
		||||
                if (PROP_DISPOSITION_AS_OF.equals(periodProperty))
 | 
			
		||||
                {
 | 
			
		||||
                    DispositionAction lastCompletedDispositionAction = getLastCompletedDispostionAction(nodeRef);
 | 
			
		||||
                    if (lastCompletedDispositionAction != null)
 | 
			
		||||
@@ -886,7 +876,7 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#isNextDispositionActionEligible(org.alfresco.service.cmr.repository.NodeRef)
 | 
			
		||||
     * @see DispositionService#isNextDispositionActionEligible(NodeRef)
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isNextDispositionActionEligible(NodeRef nodeRef)
 | 
			
		||||
@@ -940,7 +930,7 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
                {
 | 
			
		||||
                    NodeRef eventExecution = assoc.getChildRef();
 | 
			
		||||
                    Boolean isCompleteValue = (Boolean) getInternalNodeService().getProperty(eventExecution, PROP_EVENT_EXECUTION_COMPLETE);
 | 
			
		||||
                    boolean isComplete = false;
 | 
			
		||||
                    boolean isComplete;
 | 
			
		||||
                    if (isCompleteValue != null)
 | 
			
		||||
                    {
 | 
			
		||||
                        isComplete = isCompleteValue.booleanValue();
 | 
			
		||||
@@ -987,7 +977,7 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#getNextDispositionAction(org.alfresco.service.cmr.repository.NodeRef)
 | 
			
		||||
     * @see DispositionService#getNextDispositionAction(NodeRef)
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public DispositionAction getNextDispositionAction(NodeRef nodeRef)
 | 
			
		||||
@@ -1006,7 +996,7 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
    /** ========= Disposition Action History Methods ========= */
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#getCompletedDispositionActions(org.alfresco.service.cmr.repository.NodeRef)
 | 
			
		||||
     * @see DispositionService#getCompletedDispositionActions(NodeRef)
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<DispositionAction> getCompletedDispositionActions(NodeRef nodeRef)
 | 
			
		||||
@@ -1022,7 +1012,7 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#getLastCompletedDispostionAction(org.alfresco.service.cmr.repository.NodeRef)
 | 
			
		||||
     * @see DispositionService#getLastCompletedDispostionAction(NodeRef)
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public DispositionAction getLastCompletedDispostionAction(NodeRef nodeRef)
 | 
			
		||||
@@ -1038,7 +1028,7 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#isDisposableItemCutoff(NodeRef)
 | 
			
		||||
     * @see DispositionService#isDisposableItemCutoff(NodeRef)
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isDisposableItemCutoff(NodeRef nodeRef)
 | 
			
		||||
@@ -1048,7 +1038,7 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#updateNextDispositionAction(NodeRef)
 | 
			
		||||
     * @see DispositionService#updateNextDispositionAction(NodeRef)
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void updateNextDispositionAction(final NodeRef nodeRef)
 | 
			
		||||
@@ -1058,7 +1048,7 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
        RunAsWork<Void> runAsWork = new RunAsWork<Void>()
 | 
			
		||||
        {
 | 
			
		||||
            /**
 | 
			
		||||
             * @see org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork#doWork()
 | 
			
		||||
             * @see RunAsWork#doWork()
 | 
			
		||||
             */
 | 
			
		||||
            @Override
 | 
			
		||||
            public Void doWork()
 | 
			
		||||
@@ -1077,7 +1067,7 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#updateNextDispositionAction(NodeRef)
 | 
			
		||||
     * @see DispositionService#updateNextDispositionAction(NodeRef)
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void updateNextDispositionAction(final NodeRef nodeRef, final DispositionSchedule dispositionSchedule)
 | 
			
		||||
@@ -1087,7 +1077,7 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
        RunAsWork<Void> runAsWork = new RunAsWork<Void>()
 | 
			
		||||
        {
 | 
			
		||||
            /**
 | 
			
		||||
             * @see org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork#doWork()
 | 
			
		||||
             * @see RunAsWork#doWork()
 | 
			
		||||
             */
 | 
			
		||||
            @Override
 | 
			
		||||
            public Void doWork()
 | 
			
		||||
@@ -1113,16 +1103,13 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    List<DispositionActionDefinition> dispositionActionDefinitions = dispositionSchedule.getDispositionActionDefinitions();
 | 
			
		||||
                    DispositionActionDefinition currentDispositionActionDefinition = null;
 | 
			
		||||
                    DispositionActionDefinition currentDispositionActionDefinition;
 | 
			
		||||
                    DispositionActionDefinition nextDispositionActionDefinition = null;
 | 
			
		||||
 | 
			
		||||
                    if (currentDispositionAction == null)
 | 
			
		||||
                    if (currentDispositionAction == null && !dispositionActionDefinitions.isEmpty())
 | 
			
		||||
                    {
 | 
			
		||||
                        if (!dispositionActionDefinitions.isEmpty())
 | 
			
		||||
                        {
 | 
			
		||||
                            // The next disposition action is the first action
 | 
			
		||||
                            nextDispositionActionDefinition = dispositionActionDefinitions.get(0);
 | 
			
		||||
                        }
 | 
			
		||||
                        // The next disposition action is the first action
 | 
			
		||||
                        nextDispositionActionDefinition = dispositionActionDefinitions.get(0);
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
@@ -1167,7 +1154,7 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#cutoffDisposableItem(NodeRef)
 | 
			
		||||
     * @see DispositionService#cutoffDisposableItem(NodeRef)
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void cutoffDisposableItem(final NodeRef nodeRef)
 | 
			
		||||
@@ -1205,6 +1192,7 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
                    // runAs system so that we can close a record that has already been cutoff
 | 
			
		||||
                    authenticationUtil.runAsSystem(new RunAsWork<Void>()
 | 
			
		||||
                    {
 | 
			
		||||
                        @Override
 | 
			
		||||
                        public Void doWork() throws Exception
 | 
			
		||||
                        {
 | 
			
		||||
                            recordFolderService.closeRecordFolder(nodeRef);
 | 
			
		||||
@@ -1224,6 +1212,7 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Date getDispositionActionDate(NodeRef record, NodeRef dispositionSchedule, String dispositionActionName)
 | 
			
		||||
    {
 | 
			
		||||
        DispositionSchedule ds = new DispositionScheduleImpl(serviceRegistry, nodeService, dispositionSchedule);
 | 
			
		||||
@@ -1243,7 +1232,8 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void recalculateNextDispositionStep(NodeRef record)
 | 
			
		||||
    {
 | 
			
		||||
        List<NodeRef> recordFolders = recordFolderService.getRecordFolders(record);
 | 
			
		||||
@@ -1384,14 +1374,7 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
        Date calculatedDate = (nextDispositionActionDate != null ? nextDispositionActionDate : maxDate);
 | 
			
		||||
 | 
			
		||||
        // We only need to update the date if the current one is too early.
 | 
			
		||||
        if (recordDate.before(calculatedDate))
 | 
			
		||||
        {
 | 
			
		||||
            return WriteMode.DATE_ONLY;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            return WriteMode.READ_ONLY;
 | 
			
		||||
        }
 | 
			
		||||
        return recordDate.before(calculatedDate) ? WriteMode.DATE_ONLY : WriteMode.READ_ONLY;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -1414,7 +1397,7 @@ public class DispositionServiceImpl extends    ServiceBaseImpl
 | 
			
		||||
                DispositionSchedule ds = new DispositionScheduleImpl(serviceRegistry, nodeService, folderDS);
 | 
			
		||||
                List<DispositionActionDefinition> dispositionActionDefinitions = ds.getDispositionActionDefinitions();
 | 
			
		||||
 | 
			
		||||
                if (dispositionActionDefinitions != null && dispositionActionDefinitions.size() > 0)
 | 
			
		||||
                if (dispositionActionDefinitions != null && !dispositionActionDefinitions.isEmpty())
 | 
			
		||||
                {
 | 
			
		||||
                    DispositionActionDefinition firstDispositionActionDef = dispositionActionDefinitions.get(0);
 | 
			
		||||
                    dispositionNodeRef = folderDS;
 | 
			
		||||
 
 | 
			
		||||
@@ -219,7 +219,7 @@ public class FilePlanServiceImpl extends ServiceBaseImpl
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            return rmContainerCacheManager.get(storeRef);
 | 
			
		||||
            return new HashSet<>(rmContainerCacheManager.get(storeRef));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return results;
 | 
			
		||||
 
 | 
			
		||||
@@ -111,6 +111,24 @@ public interface HoldService
 | 
			
		||||
     */
 | 
			
		||||
    void setHoldReason(NodeRef hold, String reason);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the reason for the hold deletion
 | 
			
		||||
     *
 | 
			
		||||
     * @param hold The {@link NodeRef} of the hold
 | 
			
		||||
     * @param reason {@link String} The reason for the hold
 | 
			
		||||
     */
 | 
			
		||||
    void setHoldDeletionReason(NodeRef hold, String reason);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates a hold with the given name, reason and description
 | 
			
		||||
     *
 | 
			
		||||
     * @param hold The {@link NodeRef} of the hold
 | 
			
		||||
     * @param name {@link String} The name of the hold
 | 
			
		||||
     * @param reason {@link String} The reason of the hold
 | 
			
		||||
     * @param description {@link String} The description of the hold
 | 
			
		||||
     */
 | 
			
		||||
    void updateHold(NodeRef hold, String name, String reason, String description);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Deletes the hold
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -29,6 +29,7 @@ package org.alfresco.module.org_alfresco_module_rm.hold;
 | 
			
		||||
 | 
			
		||||
import static org.alfresco.model.ContentModel.ASPECT_LOCKABLE;
 | 
			
		||||
import static org.alfresco.model.ContentModel.ASSOC_CONTAINS;
 | 
			
		||||
import static org.alfresco.model.ContentModel.PROP_DESCRIPTION;
 | 
			
		||||
import static org.alfresco.model.ContentModel.PROP_NAME;
 | 
			
		||||
 | 
			
		||||
import java.io.Serializable;
 | 
			
		||||
@@ -458,11 +459,11 @@ public class HoldServiceImpl extends ServiceBaseImpl
 | 
			
		||||
 | 
			
		||||
        // create map of properties
 | 
			
		||||
        Map<QName, Serializable> properties = new HashMap<>(3);
 | 
			
		||||
        properties.put(ContentModel.PROP_NAME, name);
 | 
			
		||||
        properties.put(PROP_NAME, name);
 | 
			
		||||
        properties.put(PROP_HOLD_REASON, reason);
 | 
			
		||||
        if (description != null && !description.isEmpty())
 | 
			
		||||
        {
 | 
			
		||||
            properties.put(ContentModel.PROP_DESCRIPTION, description);
 | 
			
		||||
            properties.put(PROP_DESCRIPTION, description);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // create assoc name
 | 
			
		||||
@@ -512,6 +513,39 @@ public class HoldServiceImpl extends ServiceBaseImpl
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @see org.alfresco.module.org_alfresco_module_rm.hold.HoldService#setHoldDeletionReason(org.alfresco.service.cmr.repository.NodeRef, java.lang.String)
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setHoldDeletionReason(NodeRef hold, String reason)
 | 
			
		||||
    {
 | 
			
		||||
        ParameterCheck.mandatory("hold", hold);
 | 
			
		||||
        ParameterCheck.mandatory("reason", reason);
 | 
			
		||||
 | 
			
		||||
        if (nodeService.exists(hold) && isHold(hold))
 | 
			
		||||
        {
 | 
			
		||||
            nodeService.setProperty(hold, PROP_HOLD_DELETION_REASON, reason);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @see org.alfresco.module.org_alfresco_module_rm.hold.HoldService#updateHold(org.alfresco.service.cmr.repository.NodeRef, java.lang.String, java.lang.String, java.lang.String) (org.alfresco.service.cmr.repository.NodeRef, java.lang.String, java.lang.String, java.lang.String)
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void updateHold(NodeRef hold, String name, String reason, String description)
 | 
			
		||||
    {
 | 
			
		||||
        ParameterCheck.mandatory("hold", hold);
 | 
			
		||||
        ParameterCheck.mandatory("name", name);
 | 
			
		||||
        ParameterCheck.mandatory("reason", reason);
 | 
			
		||||
 | 
			
		||||
        if (nodeService.exists(hold) && isHold(hold))
 | 
			
		||||
        {
 | 
			
		||||
            nodeService.setProperty(hold, PROP_NAME, name);
 | 
			
		||||
            nodeService.setProperty(hold, PROP_HOLD_REASON, reason);
 | 
			
		||||
            nodeService.setProperty(hold, PROP_DESCRIPTION, description);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @see org.alfresco.module.org_alfresco_module_rm.hold.HoldService#deleteHold(org.alfresco.service.cmr.repository.NodeRef)
 | 
			
		||||
     */
 | 
			
		||||
@@ -563,7 +597,7 @@ public class HoldServiceImpl extends ServiceBaseImpl
 | 
			
		||||
 | 
			
		||||
                if (permissionService.hasPermission(nodeRef, permission) == AccessStatus.DENIED)
 | 
			
		||||
                {
 | 
			
		||||
                    heldNames.add((String) nodeService.getProperty(nodeRef, ContentModel.PROP_NAME));
 | 
			
		||||
                    heldNames.add((String) nodeService.getProperty(nodeRef, PROP_NAME));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            catch (AccessDeniedException ade)
 | 
			
		||||
@@ -630,7 +664,7 @@ public class HoldServiceImpl extends ServiceBaseImpl
 | 
			
		||||
        {
 | 
			
		||||
            if (!isHold(hold))
 | 
			
		||||
            {
 | 
			
		||||
                final String holdName = (String) nodeService.getProperty(hold, ContentModel.PROP_NAME);
 | 
			
		||||
                final String holdName = (String) nodeService.getProperty(hold, PROP_NAME);
 | 
			
		||||
                throw new IntegrityException(I18NUtil.getMessage("rm.hold.not-hold", holdName), null);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -688,7 +722,7 @@ public class HoldServiceImpl extends ServiceBaseImpl
 | 
			
		||||
    {
 | 
			
		||||
        if (!isRecordFolder(nodeRef) && !instanceOf(nodeRef, ContentModel.TYPE_CONTENT))
 | 
			
		||||
        {
 | 
			
		||||
            final String nodeName = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_NAME);
 | 
			
		||||
            final String nodeName = (String) nodeService.getProperty(nodeRef, PROP_NAME);
 | 
			
		||||
            throw new IntegrityException(I18NUtil.getMessage("rm.hold.add-to-hold-invalid-type", nodeName), null);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -795,7 +829,7 @@ public class HoldServiceImpl extends ServiceBaseImpl
 | 
			
		||||
            {
 | 
			
		||||
                if (!isHold(hold))
 | 
			
		||||
                {
 | 
			
		||||
                    final String holdName = (String) nodeService.getProperty(hold, ContentModel.PROP_NAME);
 | 
			
		||||
                    final String holdName = (String) nodeService.getProperty(hold, PROP_NAME);
 | 
			
		||||
                    throw new IntegrityException(I18NUtil.getMessage("rm.hold.not-hold", holdName), null);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -35,6 +35,7 @@ import org.alfresco.service.namespace.QName;
 | 
			
		||||
 *
 | 
			
		||||
 * @author Roy Wetherall
 | 
			
		||||
 */
 | 
			
		||||
@SuppressWarnings("PMD.ConstantsInInterface")
 | 
			
		||||
@AlfrescoPublicApi
 | 
			
		||||
public interface RecordsManagementModel extends RecordsManagementCustomModel
 | 
			
		||||
{
 | 
			
		||||
@@ -200,6 +201,7 @@ public interface RecordsManagementModel extends RecordsManagementCustomModel
 | 
			
		||||
    // Hold type
 | 
			
		||||
    QName TYPE_HOLD = QName.createQName(RM_URI, "hold");
 | 
			
		||||
    QName PROP_HOLD_REASON = QName.createQName(RM_URI, "holdReason");
 | 
			
		||||
    QName PROP_HOLD_DELETION_REASON = QName.createQName(RM_URI, "holdDeletionReason");
 | 
			
		||||
    //since 3.2
 | 
			
		||||
    @Deprecated
 | 
			
		||||
    QName ASSOC_FROZEN_RECORDS = QName.createQName(RM_URI, "frozenRecords");
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,155 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rm.rest.api.fileplans;
 | 
			
		||||
 | 
			
		||||
import static org.alfresco.module.org_alfresco_module_rm.util.RMParameterCheck.checkNotBlank;
 | 
			
		||||
import static org.alfresco.util.ParameterCheck.mandatory;
 | 
			
		||||
 | 
			
		||||
import java.util.LinkedList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.hold.HoldService;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
 | 
			
		||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
 | 
			
		||||
import org.alfresco.rest.framework.WebApiDescription;
 | 
			
		||||
import org.alfresco.rest.framework.resource.RelationshipResource;
 | 
			
		||||
import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction;
 | 
			
		||||
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
 | 
			
		||||
import org.alfresco.rest.framework.resource.parameters.Parameters;
 | 
			
		||||
import org.alfresco.rm.rest.api.impl.ApiNodesModelFactory;
 | 
			
		||||
import org.alfresco.rm.rest.api.impl.FilePlanComponentsApiUtils;
 | 
			
		||||
import org.alfresco.rm.rest.api.model.HoldModel;
 | 
			
		||||
import org.alfresco.service.cmr.model.FileFolderService;
 | 
			
		||||
import org.alfresco.service.cmr.repository.NodeRef;
 | 
			
		||||
import org.alfresco.service.transaction.TransactionService;
 | 
			
		||||
import org.springframework.beans.factory.InitializingBean;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * File plan holds relation
 | 
			
		||||
 *
 | 
			
		||||
 * @author Damian Ujma
 | 
			
		||||
 */
 | 
			
		||||
@RelationshipResource(name = "holds", entityResource = FilePlanEntityResource.class, title = "Holds in a file plan")
 | 
			
		||||
public class FilePlanHoldsRelation implements
 | 
			
		||||
    RelationshipResourceAction.Create<HoldModel>,
 | 
			
		||||
    RelationshipResourceAction.Read<HoldModel>,
 | 
			
		||||
    InitializingBean
 | 
			
		||||
{
 | 
			
		||||
    private FilePlanComponentsApiUtils apiUtils;
 | 
			
		||||
    private ApiNodesModelFactory nodesModelFactory;
 | 
			
		||||
    private HoldService holdService;
 | 
			
		||||
    private FileFolderService fileFolderService;
 | 
			
		||||
    private TransactionService transactionService;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void afterPropertiesSet() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        mandatory("apiUtils", this.apiUtils);
 | 
			
		||||
        mandatory("nodesModelFactory", this.nodesModelFactory);
 | 
			
		||||
        mandatory("holdService", this.holdService);
 | 
			
		||||
        mandatory("fileFolderService", this.fileFolderService);
 | 
			
		||||
        mandatory("transactionService", this.transactionService);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    @WebApiDescription(title = "Return a paged list of holds for the file plan identified by 'filePlanId'")
 | 
			
		||||
    public CollectionWithPagingInfo<HoldModel> readAll(String filePlanId, Parameters parameters)
 | 
			
		||||
    {
 | 
			
		||||
        checkNotBlank("filePlanId", filePlanId);
 | 
			
		||||
        mandatory("parameters", parameters);
 | 
			
		||||
 | 
			
		||||
        NodeRef parentNodeRef = apiUtils.lookupAndValidateNodeType(filePlanId, RecordsManagementModel.TYPE_FILE_PLAN);
 | 
			
		||||
        List<NodeRef> holds = holdService.getHolds(parentNodeRef);
 | 
			
		||||
 | 
			
		||||
        List<HoldModel> page = holds.stream()
 | 
			
		||||
            .map(hold -> fileFolderService.getFileInfo(hold))
 | 
			
		||||
            .map(nodesModelFactory::createHoldModel)
 | 
			
		||||
            .skip(parameters.getPaging().getSkipCount())
 | 
			
		||||
            .limit(parameters.getPaging().getMaxItems())
 | 
			
		||||
            .collect(Collectors.toCollection(LinkedList::new));
 | 
			
		||||
 | 
			
		||||
        int totalItems = holds.size();
 | 
			
		||||
        boolean hasMore = parameters.getPaging().getSkipCount() + parameters.getPaging().getMaxItems() < totalItems;
 | 
			
		||||
        return CollectionWithPagingInfo.asPaged(parameters.getPaging(), page, hasMore, totalItems);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    @WebApiDescription(title = "Create one (or more) holds in a file plan identified by 'filePlanId'")
 | 
			
		||||
    public List<HoldModel> create(String filePlanId, List<HoldModel> holds, Parameters parameters)
 | 
			
		||||
    {
 | 
			
		||||
        checkNotBlank("filePlanId", filePlanId);
 | 
			
		||||
        mandatory("holds", holds);
 | 
			
		||||
        mandatory("parameters", parameters);
 | 
			
		||||
 | 
			
		||||
        NodeRef parentNodeRef = apiUtils.lookupAndValidateNodeType(filePlanId, RecordsManagementModel.TYPE_FILE_PLAN);
 | 
			
		||||
 | 
			
		||||
        RetryingTransactionCallback<List<NodeRef>> callback = () -> {
 | 
			
		||||
            List<NodeRef> createdNodes = new LinkedList<>();
 | 
			
		||||
            for (HoldModel nodeInfo : holds)
 | 
			
		||||
            {
 | 
			
		||||
                NodeRef newNodeRef = holdService.createHold(parentNodeRef, nodeInfo.name(), nodeInfo.reason(),
 | 
			
		||||
                    nodeInfo.description());
 | 
			
		||||
                createdNodes.add(newNodeRef);
 | 
			
		||||
            }
 | 
			
		||||
            return createdNodes;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        List<NodeRef> createdNodes = transactionService.getRetryingTransactionHelper()
 | 
			
		||||
            .doInTransaction(callback, false, true);
 | 
			
		||||
 | 
			
		||||
        return createdNodes.stream()
 | 
			
		||||
            .map(hold -> fileFolderService.getFileInfo(hold))
 | 
			
		||||
            .map(nodesModelFactory::createHoldModel)
 | 
			
		||||
            .collect(Collectors.toCollection(LinkedList::new));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setApiUtils(FilePlanComponentsApiUtils apiUtils)
 | 
			
		||||
    {
 | 
			
		||||
        this.apiUtils = apiUtils;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setNodesModelFactory(ApiNodesModelFactory nodesModelFactory)
 | 
			
		||||
    {
 | 
			
		||||
        this.nodesModelFactory = nodesModelFactory;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setHoldService(HoldService holdService)
 | 
			
		||||
    {
 | 
			
		||||
        this.holdService = holdService;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setFileFolderService(FileFolderService fileFolderService)
 | 
			
		||||
    {
 | 
			
		||||
        this.fileFolderService = fileFolderService;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setTransactionService(TransactionService transactionService)
 | 
			
		||||
    {
 | 
			
		||||
        this.transactionService = transactionService;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,163 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rm.rest.api.holds;
 | 
			
		||||
 | 
			
		||||
import static org.alfresco.module.org_alfresco_module_rm.util.RMParameterCheck.checkNotBlank;
 | 
			
		||||
import static org.alfresco.util.ParameterCheck.mandatory;
 | 
			
		||||
 | 
			
		||||
import java.util.LinkedList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
import jakarta.servlet.http.HttpServletResponse;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkCancellationRequest;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.bulk.hold.HoldBulkMonitor;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.bulk.hold.HoldBulkService;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.bulk.hold.HoldBulkStatusAndProcessDetails;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.bulk.hold.HoldBulkUtils;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
 | 
			
		||||
import org.alfresco.rest.framework.Operation;
 | 
			
		||||
import org.alfresco.rest.framework.WebApiDescription;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.NotFoundException;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.RelationshipResourceNotFoundException;
 | 
			
		||||
import org.alfresco.rest.framework.resource.RelationshipResource;
 | 
			
		||||
import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction;
 | 
			
		||||
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
 | 
			
		||||
import org.alfresco.rest.framework.resource.parameters.Parameters;
 | 
			
		||||
import org.alfresco.rest.framework.webscripts.WithResponse;
 | 
			
		||||
import org.alfresco.rm.rest.api.impl.FilePlanComponentsApiUtils;
 | 
			
		||||
import org.alfresco.rm.rest.api.model.BulkCancellationEntry;
 | 
			
		||||
import org.alfresco.rm.rest.api.model.HoldBulkStatusEntry;
 | 
			
		||||
import org.alfresco.service.cmr.repository.NodeRef;
 | 
			
		||||
import org.alfresco.service.cmr.security.AccessStatus;
 | 
			
		||||
import org.alfresco.service.cmr.security.PermissionService;
 | 
			
		||||
import org.springframework.extensions.surf.util.I18NUtil;
 | 
			
		||||
 | 
			
		||||
@RelationshipResource(name = "bulk-statuses", entityResource = HoldsEntityResource.class, title = "Bulk statuses of a hold")
 | 
			
		||||
public class HoldsBulkStatusesRelation
 | 
			
		||||
    implements RelationshipResourceAction.Read<HoldBulkStatusEntry>,
 | 
			
		||||
    RelationshipResourceAction.ReadById<HoldBulkStatusEntry>
 | 
			
		||||
{
 | 
			
		||||
    private HoldBulkMonitor holdBulkMonitor;
 | 
			
		||||
    private HoldBulkService holdBulkService;
 | 
			
		||||
    private FilePlanComponentsApiUtils apiUtils;
 | 
			
		||||
    private PermissionService permissionService;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public CollectionWithPagingInfo<HoldBulkStatusEntry> readAll(String holdId, Parameters parameters)
 | 
			
		||||
    {
 | 
			
		||||
        // validate parameters
 | 
			
		||||
        checkNotBlank("holdId", holdId);
 | 
			
		||||
        mandatory("parameters", parameters);
 | 
			
		||||
 | 
			
		||||
        NodeRef holdRef = apiUtils.lookupAndValidateNodeType(holdId, RecordsManagementModel.TYPE_HOLD);
 | 
			
		||||
 | 
			
		||||
        checkReadPermissions(holdRef);
 | 
			
		||||
 | 
			
		||||
        List<HoldBulkStatusAndProcessDetails> statuses = holdBulkMonitor.getBulkStatusesWithProcessDetails(holdId);
 | 
			
		||||
        List<HoldBulkStatusEntry> page = statuses.stream()
 | 
			
		||||
            .map(HoldBulkUtils::toHoldBulkStatusEntry)
 | 
			
		||||
            .skip(parameters.getPaging().getSkipCount())
 | 
			
		||||
            .limit(parameters.getPaging().getMaxItems())
 | 
			
		||||
            .collect(Collectors.toCollection(LinkedList::new));
 | 
			
		||||
 | 
			
		||||
        int totalItems = statuses.size();
 | 
			
		||||
        boolean hasMore = parameters.getPaging().getSkipCount() + parameters.getPaging().getMaxItems() < totalItems;
 | 
			
		||||
        return CollectionWithPagingInfo.asPaged(parameters.getPaging(), page, hasMore, totalItems);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public HoldBulkStatusEntry readById(String holdId, String bulkStatusId, Parameters parameters)
 | 
			
		||||
        throws RelationshipResourceNotFoundException
 | 
			
		||||
    {
 | 
			
		||||
        checkNotBlank("holdId", holdId);
 | 
			
		||||
        checkNotBlank("bulkStatusId", bulkStatusId);
 | 
			
		||||
        mandatory("parameters", parameters);
 | 
			
		||||
 | 
			
		||||
        NodeRef holdRef = apiUtils.lookupAndValidateNodeType(holdId, RecordsManagementModel.TYPE_HOLD);
 | 
			
		||||
 | 
			
		||||
        checkReadPermissions(holdRef);
 | 
			
		||||
 | 
			
		||||
        return Optional.ofNullable(holdBulkMonitor.getBulkStatusWithProcessDetails(holdId, bulkStatusId))
 | 
			
		||||
            .map(HoldBulkUtils::toHoldBulkStatusEntry)
 | 
			
		||||
            .orElseThrow(() -> new EntityNotFoundException(bulkStatusId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Operation("cancel")
 | 
			
		||||
    @WebApiDescription(title = "Cancel a bulk operation",
 | 
			
		||||
        successStatus = HttpServletResponse.SC_OK)
 | 
			
		||||
    public void cancelBulkOperation(String holdId, String bulkStatusId, BulkCancellationEntry bulkCancellationEntry,
 | 
			
		||||
        Parameters parameters,
 | 
			
		||||
        WithResponse withResponse)
 | 
			
		||||
    {
 | 
			
		||||
        checkNotBlank("holdId", holdId);
 | 
			
		||||
        checkNotBlank("bulkStatusId", bulkStatusId);
 | 
			
		||||
        mandatory("parameters", parameters);
 | 
			
		||||
 | 
			
		||||
        NodeRef holdRef = apiUtils.lookupAndValidateNodeType(holdId, RecordsManagementModel.TYPE_HOLD);
 | 
			
		||||
 | 
			
		||||
        checkReadPermissions(holdRef);
 | 
			
		||||
 | 
			
		||||
        if (holdBulkMonitor.getBulkStatus(bulkStatusId) == null)
 | 
			
		||||
        {
 | 
			
		||||
            throw new NotFoundException("Bulk status not found");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        holdBulkService.cancelBulkOperation(holdRef, bulkStatusId, new BulkCancellationRequest(bulkCancellationEntry.reason()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void checkReadPermissions(NodeRef holdRef)
 | 
			
		||||
    {
 | 
			
		||||
        if (permissionService.hasReadPermission(holdRef) == AccessStatus.DENIED)
 | 
			
		||||
        {
 | 
			
		||||
            throw new PermissionDeniedException(I18NUtil.getMessage("permissions.err_access_denied"));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setHoldBulkMonitor(HoldBulkMonitor holdBulkMonitor)
 | 
			
		||||
    {
 | 
			
		||||
        this.holdBulkMonitor = holdBulkMonitor;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setApiUtils(FilePlanComponentsApiUtils apiUtils)
 | 
			
		||||
    {
 | 
			
		||||
        this.apiUtils = apiUtils;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setPermissionService(PermissionService permissionService)
 | 
			
		||||
    {
 | 
			
		||||
        this.permissionService = permissionService;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setHoldBulkService(HoldBulkService holdBulkService)
 | 
			
		||||
    {
 | 
			
		||||
        this.holdBulkService = holdBulkService;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,207 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rm.rest.api.holds;
 | 
			
		||||
 | 
			
		||||
import static org.alfresco.module.org_alfresco_module_rm.util.RMParameterCheck.checkNotBlank;
 | 
			
		||||
import static org.alfresco.util.ParameterCheck.mandatory;
 | 
			
		||||
 | 
			
		||||
import java.util.LinkedList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.hold.HoldService;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
 | 
			
		||||
import org.alfresco.repo.node.integrity.IntegrityException;
 | 
			
		||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
 | 
			
		||||
import org.alfresco.rest.framework.WebApiDescription;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
 | 
			
		||||
import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException;
 | 
			
		||||
import org.alfresco.rest.framework.resource.RelationshipResource;
 | 
			
		||||
import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction;
 | 
			
		||||
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
 | 
			
		||||
import org.alfresco.rest.framework.resource.parameters.Parameters;
 | 
			
		||||
import org.alfresco.rm.rest.api.impl.ApiNodesModelFactory;
 | 
			
		||||
import org.alfresco.rm.rest.api.impl.FilePlanComponentsApiUtils;
 | 
			
		||||
import org.alfresco.rm.rest.api.model.HoldChild;
 | 
			
		||||
import org.alfresco.service.cmr.model.FileFolderService;
 | 
			
		||||
import org.alfresco.service.cmr.repository.NodeRef;
 | 
			
		||||
import org.alfresco.service.cmr.repository.StoreRef;
 | 
			
		||||
import org.alfresco.service.cmr.security.AccessStatus;
 | 
			
		||||
import org.alfresco.service.cmr.security.PermissionService;
 | 
			
		||||
import org.alfresco.service.transaction.TransactionService;
 | 
			
		||||
import org.springframework.beans.factory.InitializingBean;
 | 
			
		||||
import org.springframework.extensions.surf.util.I18NUtil;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Hold children relation
 | 
			
		||||
 *
 | 
			
		||||
 * @author Damian Ujma
 | 
			
		||||
 */
 | 
			
		||||
@RelationshipResource(name = "children", entityResource = HoldsEntityResource.class, title = "Children of a hold")
 | 
			
		||||
public class HoldsChildrenRelation implements
 | 
			
		||||
    RelationshipResourceAction.Create<HoldChild>,
 | 
			
		||||
    RelationshipResourceAction.Read<HoldChild>,
 | 
			
		||||
    RelationshipResourceAction.Delete,
 | 
			
		||||
    InitializingBean
 | 
			
		||||
{
 | 
			
		||||
    private HoldService holdService;
 | 
			
		||||
    private FilePlanComponentsApiUtils apiUtils;
 | 
			
		||||
    private ApiNodesModelFactory nodesModelFactory;
 | 
			
		||||
    private TransactionService transactionService;
 | 
			
		||||
    private FileFolderService fileFolderService;
 | 
			
		||||
    private PermissionService permissionService;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void afterPropertiesSet() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        mandatory("holdService", holdService);
 | 
			
		||||
        mandatory("apiUtils", apiUtils);
 | 
			
		||||
        mandatory("nodesModelFactory", nodesModelFactory);
 | 
			
		||||
        mandatory("transactionService", transactionService);
 | 
			
		||||
        mandatory("fileFolderService", fileFolderService);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    @WebApiDescription(title = "Add one (or more) children as children of a hold identified by 'holdId'")
 | 
			
		||||
    public List<HoldChild> create(String holdId, List<HoldChild> children, Parameters parameters)
 | 
			
		||||
    {
 | 
			
		||||
        // validate parameters
 | 
			
		||||
        checkNotBlank("holdId", holdId);
 | 
			
		||||
        mandatory("children", children);
 | 
			
		||||
        mandatory("parameters", parameters);
 | 
			
		||||
 | 
			
		||||
        NodeRef parentNodeRef = apiUtils.lookupAndValidateNodeType(holdId, RecordsManagementModel.TYPE_HOLD);
 | 
			
		||||
 | 
			
		||||
        RetryingTransactionCallback<List<NodeRef>> callback = () -> {
 | 
			
		||||
            List<NodeRef> createdNodes = children.stream()
 | 
			
		||||
                .map(holdChild -> new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, holdChild.id()))
 | 
			
		||||
                .collect(Collectors.toList());
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                holdService.addToHold(parentNodeRef, createdNodes);
 | 
			
		||||
            }
 | 
			
		||||
            catch (IntegrityException exception)
 | 
			
		||||
            {
 | 
			
		||||
                // Throw 400 Bad Request when a node with id 'holdId' is not a hold or a child cannot be added to a hold
 | 
			
		||||
                throw new InvalidArgumentException(exception.getMsgId()).initCause(exception);
 | 
			
		||||
            }
 | 
			
		||||
            return createdNodes;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        List<NodeRef> nodeInfos = transactionService.getRetryingTransactionHelper()
 | 
			
		||||
            .doInTransaction(callback, false, true);
 | 
			
		||||
 | 
			
		||||
        return nodeInfos.stream()
 | 
			
		||||
            .map(nodeRef -> new HoldChild(nodeRef.getId()))
 | 
			
		||||
            .collect(Collectors.toCollection(LinkedList::new));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    @WebApiDescription(title = "Return a paged list of hold children for the hold identified by 'holdId'")
 | 
			
		||||
    public CollectionWithPagingInfo<HoldChild> readAll(String holdId, Parameters parameters)
 | 
			
		||||
    {
 | 
			
		||||
        checkNotBlank("holdId", holdId);
 | 
			
		||||
        mandatory("parameters", parameters);
 | 
			
		||||
 | 
			
		||||
        NodeRef parentNodeRef = apiUtils.lookupAndValidateNodeType(holdId, RecordsManagementModel.TYPE_HOLD);
 | 
			
		||||
        List<NodeRef> children = holdService.getHeld(parentNodeRef);
 | 
			
		||||
 | 
			
		||||
        List<HoldChild> page = children.stream()
 | 
			
		||||
            .map(NodeRef::getId)
 | 
			
		||||
            .map(HoldChild::new)
 | 
			
		||||
            .skip(parameters.getPaging().getSkipCount())
 | 
			
		||||
            .limit(parameters.getPaging().getMaxItems())
 | 
			
		||||
            .collect(Collectors.toCollection(LinkedList::new));
 | 
			
		||||
 | 
			
		||||
        int totalItems = children.size();
 | 
			
		||||
        boolean hasMore = parameters.getPaging().getSkipCount() + parameters.getPaging().getMaxItems() < totalItems;
 | 
			
		||||
        return CollectionWithPagingInfo.asPaged(parameters.getPaging(), page, hasMore, totalItems);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    @WebApiDescription(title = "Remove a child from a hold", description = "Remove a child with id 'childId' from a hold with id 'holdId'")
 | 
			
		||||
    public void delete(String holdId, String childId, Parameters parameters)
 | 
			
		||||
    {
 | 
			
		||||
        checkNotBlank("holdId", holdId);
 | 
			
		||||
        checkNotBlank("childId", childId);
 | 
			
		||||
        mandatory("parameters", parameters);
 | 
			
		||||
 | 
			
		||||
        NodeRef nodeRef = apiUtils.lookupAndValidateNodeType(holdId, RecordsManagementModel.TYPE_HOLD);
 | 
			
		||||
        NodeRef childRef = apiUtils.lookupByPlaceholder(childId);
 | 
			
		||||
 | 
			
		||||
        if (permissionService.hasReadPermission(childRef) == AccessStatus.DENIED)
 | 
			
		||||
        {
 | 
			
		||||
            throw new PermissionDeniedException(I18NUtil.getMessage("permissions.err_access_denied"));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        RetryingTransactionCallback<List<NodeRef>> callback = () -> {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                holdService.removeFromHold(nodeRef, childRef);
 | 
			
		||||
            }
 | 
			
		||||
            catch (IntegrityException exception)
 | 
			
		||||
            {
 | 
			
		||||
                // Throw 400 Bad Request when a node with id 'holdId' is not a hold
 | 
			
		||||
                throw new InvalidArgumentException(exception.getMsgId()).initCause(exception);
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        transactionService.getRetryingTransactionHelper().doInTransaction(callback, false, true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setHoldService(HoldService holdService)
 | 
			
		||||
    {
 | 
			
		||||
        this.holdService = holdService;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setApiUtils(FilePlanComponentsApiUtils apiUtils)
 | 
			
		||||
    {
 | 
			
		||||
        this.apiUtils = apiUtils;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setTransactionService(TransactionService transactionService)
 | 
			
		||||
    {
 | 
			
		||||
        this.transactionService = transactionService;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setNodesModelFactory(ApiNodesModelFactory nodesModelFactory)
 | 
			
		||||
    {
 | 
			
		||||
        this.nodesModelFactory = nodesModelFactory;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setFileFolderService(FileFolderService fileFolderService)
 | 
			
		||||
    {
 | 
			
		||||
        this.fileFolderService = fileFolderService;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setPermissionService(PermissionService permissionService)
 | 
			
		||||
    {
 | 
			
		||||
        this.permissionService = permissionService;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,212 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rm.rest.api.holds;
 | 
			
		||||
 | 
			
		||||
import static org.alfresco.module.org_alfresco_module_rm.util.RMParameterCheck.checkNotBlank;
 | 
			
		||||
import static org.alfresco.util.ParameterCheck.mandatory;
 | 
			
		||||
 | 
			
		||||
import jakarta.servlet.http.HttpServletResponse;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkOperation;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.bulk.hold.HoldBulkService;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.hold.HoldService;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
 | 
			
		||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
 | 
			
		||||
import org.alfresco.rest.framework.Operation;
 | 
			
		||||
import org.alfresco.rest.framework.WebApiDescription;
 | 
			
		||||
import org.alfresco.rest.framework.WebApiParam;
 | 
			
		||||
import org.alfresco.rest.framework.resource.EntityResource;
 | 
			
		||||
import org.alfresco.rest.framework.resource.actions.interfaces.EntityResourceAction;
 | 
			
		||||
import org.alfresco.rest.framework.resource.parameters.Parameters;
 | 
			
		||||
import org.alfresco.rest.framework.webscripts.WithResponse;
 | 
			
		||||
import org.alfresco.rm.rest.api.impl.ApiNodesModelFactory;
 | 
			
		||||
import org.alfresco.rm.rest.api.impl.FilePlanComponentsApiUtils;
 | 
			
		||||
import org.alfresco.rm.rest.api.model.HoldBulkOperation;
 | 
			
		||||
import org.alfresco.rm.rest.api.model.HoldBulkOperationEntry;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.bulk.hold.HoldBulkStatus;
 | 
			
		||||
import org.alfresco.rm.rest.api.model.HoldDeletionReason;
 | 
			
		||||
import org.alfresco.rm.rest.api.model.HoldModel;
 | 
			
		||||
import org.alfresco.service.cmr.model.FileFolderService;
 | 
			
		||||
import org.alfresco.service.cmr.model.FileInfo;
 | 
			
		||||
import org.alfresco.service.cmr.repository.NodeRef;
 | 
			
		||||
import org.alfresco.service.transaction.TransactionService;
 | 
			
		||||
import org.apache.commons.lang3.StringUtils;
 | 
			
		||||
import org.springframework.beans.factory.InitializingBean;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Hold entity resource
 | 
			
		||||
 *
 | 
			
		||||
 * @author Damian Ujma
 | 
			
		||||
 */
 | 
			
		||||
@EntityResource(name = "holds", title = "Holds")
 | 
			
		||||
public class HoldsEntityResource implements
 | 
			
		||||
    EntityResourceAction.ReadById<HoldModel>,
 | 
			
		||||
    EntityResourceAction.Update<HoldModel>,
 | 
			
		||||
    EntityResourceAction.Delete,
 | 
			
		||||
    InitializingBean
 | 
			
		||||
{
 | 
			
		||||
    private FilePlanComponentsApiUtils apiUtils;
 | 
			
		||||
    private FileFolderService fileFolderService;
 | 
			
		||||
    private ApiNodesModelFactory nodesModelFactory;
 | 
			
		||||
    private HoldService holdService;
 | 
			
		||||
    private TransactionService transactionService;
 | 
			
		||||
    private HoldBulkService holdBulkService;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void afterPropertiesSet() throws Exception
 | 
			
		||||
    {
 | 
			
		||||
        mandatory("nodesModelFactory", nodesModelFactory);
 | 
			
		||||
        mandatory("apiUtils", apiUtils);
 | 
			
		||||
        mandatory("fileFolderService", fileFolderService);
 | 
			
		||||
        mandatory("holdService", holdService);
 | 
			
		||||
        mandatory("transactionService", transactionService);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    @WebApiDescription(title = "Get hold information", description = "Get information for a hold with id 'holdId'")
 | 
			
		||||
    @WebApiParam(name = "holdId", title = "The hold id")
 | 
			
		||||
    public HoldModel readById(String holdId, Parameters parameters)
 | 
			
		||||
    {
 | 
			
		||||
        checkNotBlank("holdId", holdId);
 | 
			
		||||
        mandatory("parameters", parameters);
 | 
			
		||||
 | 
			
		||||
        NodeRef hold = apiUtils.lookupAndValidateNodeType(holdId, RecordsManagementModel.TYPE_HOLD);
 | 
			
		||||
        FileInfo info = fileFolderService.getFileInfo(hold);
 | 
			
		||||
        return nodesModelFactory.createHoldModel(info);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    @WebApiDescription(title = "Update a hold", description = "Updates a hold with id 'holdId'")
 | 
			
		||||
    public HoldModel update(String holdId, HoldModel holdModel, Parameters parameters)
 | 
			
		||||
    {
 | 
			
		||||
        checkNotBlank("holdId", holdId);
 | 
			
		||||
        mandatory("holdModel", holdModel);
 | 
			
		||||
        mandatory("holdModel.name", holdModel.name());
 | 
			
		||||
        mandatory("holdModel.reason", holdModel.reason());
 | 
			
		||||
        mandatory("parameters", parameters);
 | 
			
		||||
 | 
			
		||||
        NodeRef nodeRef = apiUtils.lookupAndValidateNodeType(holdId, RecordsManagementModel.TYPE_HOLD);
 | 
			
		||||
 | 
			
		||||
        RetryingTransactionCallback<Void> callback = () -> {
 | 
			
		||||
            holdService.updateHold(nodeRef, holdModel.name(), holdModel.reason(), holdModel.description());
 | 
			
		||||
            return null;
 | 
			
		||||
        };
 | 
			
		||||
        transactionService.getRetryingTransactionHelper().doInTransaction(callback, false, true);
 | 
			
		||||
 | 
			
		||||
        RetryingTransactionCallback<FileInfo> readCallback = () -> fileFolderService.getFileInfo(nodeRef);
 | 
			
		||||
        FileInfo info = transactionService.getRetryingTransactionHelper().doInTransaction(readCallback, false, true);
 | 
			
		||||
 | 
			
		||||
        return nodesModelFactory.createHoldModel(info);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    @WebApiDescription(title = "Delete hold", description = "Deletes a hold with id 'holdId'")
 | 
			
		||||
    public void delete(String holdId, Parameters parameters)
 | 
			
		||||
    {
 | 
			
		||||
        checkNotBlank("holdId", holdId);
 | 
			
		||||
        mandatory("parameters", parameters);
 | 
			
		||||
 | 
			
		||||
        NodeRef hold = apiUtils.lookupAndValidateNodeType(holdId, RecordsManagementModel.TYPE_HOLD);
 | 
			
		||||
        RetryingTransactionCallback<Void> callback = () -> {
 | 
			
		||||
            holdService.deleteHold(hold);
 | 
			
		||||
            return null;
 | 
			
		||||
        };
 | 
			
		||||
        transactionService.getRetryingTransactionHelper().doInTransaction(callback, false, true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Operation("delete")
 | 
			
		||||
    @WebApiDescription(title = "Delete hold with a reason",
 | 
			
		||||
        successStatus = HttpServletResponse.SC_OK)
 | 
			
		||||
    public HoldDeletionReason deleteHoldWithReason(String holdId, HoldDeletionReason reason, Parameters parameters,
 | 
			
		||||
        WithResponse withResponse)
 | 
			
		||||
    {
 | 
			
		||||
        checkNotBlank("holdId", holdId);
 | 
			
		||||
        mandatory("reason", reason);
 | 
			
		||||
        mandatory("parameters", parameters);
 | 
			
		||||
 | 
			
		||||
        NodeRef hold = apiUtils.lookupAndValidateNodeType(holdId, RecordsManagementModel.TYPE_HOLD);
 | 
			
		||||
        String deletionReason = reason.reason();
 | 
			
		||||
 | 
			
		||||
        RetryingTransactionCallback<Void> callback = () -> {
 | 
			
		||||
            if (StringUtils.isNotBlank(deletionReason))
 | 
			
		||||
            {
 | 
			
		||||
                holdService.setHoldDeletionReason(hold, deletionReason);
 | 
			
		||||
            }
 | 
			
		||||
            holdService.deleteHold(hold);
 | 
			
		||||
            return null;
 | 
			
		||||
        };
 | 
			
		||||
        transactionService.getRetryingTransactionHelper().doInTransaction(callback, false, true);
 | 
			
		||||
 | 
			
		||||
        return reason;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Operation("bulk")
 | 
			
		||||
    @WebApiDescription(title = "Start the hold bulk operation",
 | 
			
		||||
        successStatus = HttpServletResponse.SC_ACCEPTED)
 | 
			
		||||
    public HoldBulkOperationEntry bulk(String holdId, HoldBulkOperation holdBulkOperation, Parameters parameters,
 | 
			
		||||
        WithResponse withResponse)
 | 
			
		||||
    {
 | 
			
		||||
        // validate parameters
 | 
			
		||||
        checkNotBlank("holdId", holdId);
 | 
			
		||||
        mandatory("parameters", parameters);
 | 
			
		||||
 | 
			
		||||
        NodeRef parentNodeRef = apiUtils.lookupAndValidateNodeType(holdId, RecordsManagementModel.TYPE_HOLD);
 | 
			
		||||
 | 
			
		||||
        HoldBulkStatus holdBulkStatus = holdBulkService.execute(parentNodeRef,
 | 
			
		||||
            new BulkOperation(holdBulkOperation.query(), holdBulkOperation.op().name()));
 | 
			
		||||
        return new HoldBulkOperationEntry(holdBulkStatus.bulkStatusId(), holdBulkStatus.totalItems());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setApiUtils(FilePlanComponentsApiUtils apiUtils)
 | 
			
		||||
    {
 | 
			
		||||
        this.apiUtils = apiUtils;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setFileFolderService(FileFolderService fileFolderService)
 | 
			
		||||
    {
 | 
			
		||||
        this.fileFolderService = fileFolderService;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setNodesModelFactory(ApiNodesModelFactory nodesModelFactory)
 | 
			
		||||
    {
 | 
			
		||||
        this.nodesModelFactory = nodesModelFactory;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setHoldService(HoldService holdService)
 | 
			
		||||
    {
 | 
			
		||||
        this.holdService = holdService;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setTransactionService(TransactionService transactionService)
 | 
			
		||||
    {
 | 
			
		||||
        this.transactionService = transactionService;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setHoldBulkService(HoldBulkService holdBulkService)
 | 
			
		||||
    {
 | 
			
		||||
        this.holdBulkService = holdBulkService;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,36 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 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 info that defines the Information Governance Holds REST API
 | 
			
		||||
 *
 | 
			
		||||
 * @author Damian Ujma
 | 
			
		||||
 */
 | 
			
		||||
@WebApi(name="gs", scope=Api.SCOPE.PUBLIC, version=1)
 | 
			
		||||
package org.alfresco.rm.rest.api.holds;
 | 
			
		||||
import org.alfresco.rest.framework.Api;
 | 
			
		||||
import org.alfresco.rest.framework.WebApi;
 | 
			
		||||
@@ -34,10 +34,16 @@ import java.util.HashMap;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
import java.util.stream.IntStream;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.model.ContentModel;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.RecordsManagementServiceRegistry;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionActionDefinition;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionActionDefinitionImpl;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionSchedule;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.event.RecordsManagementEvent;
 | 
			
		||||
import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
 | 
			
		||||
import org.alfresco.rest.api.Nodes;
 | 
			
		||||
import org.alfresco.rest.api.model.AssocChild;
 | 
			
		||||
@@ -47,11 +53,14 @@ import org.alfresco.rest.api.model.UserInfo;
 | 
			
		||||
import org.alfresco.rest.framework.jacksonextensions.BeanPropertiesFilter;
 | 
			
		||||
import org.alfresco.rest.framework.resource.parameters.Parameters;
 | 
			
		||||
import org.alfresco.rm.rest.api.model.FilePlan;
 | 
			
		||||
import org.alfresco.rm.rest.api.model.HoldModel;
 | 
			
		||||
import org.alfresco.rm.rest.api.model.RMNode;
 | 
			
		||||
import org.alfresco.rm.rest.api.model.Record;
 | 
			
		||||
import org.alfresco.rm.rest.api.model.RecordCategory;
 | 
			
		||||
import org.alfresco.rm.rest.api.model.RecordCategoryChild;
 | 
			
		||||
import org.alfresco.rm.rest.api.model.RecordFolder;
 | 
			
		||||
import org.alfresco.rm.rest.api.model.RetentionPeriod;
 | 
			
		||||
import org.alfresco.rm.rest.api.model.RetentionSteps;
 | 
			
		||||
import org.alfresco.rm.rest.api.model.Transfer;
 | 
			
		||||
import org.alfresco.rm.rest.api.model.TransferChild;
 | 
			
		||||
import org.alfresco.rm.rest.api.model.TransferContainer;
 | 
			
		||||
@@ -60,6 +69,8 @@ import org.alfresco.rm.rest.api.model.UnfiledContainer;
 | 
			
		||||
import org.alfresco.rm.rest.api.model.UnfiledContainerChild;
 | 
			
		||||
import org.alfresco.rm.rest.api.model.UnfiledRecordFolder;
 | 
			
		||||
import org.alfresco.rm.rest.api.model.UnfiledRecordFolderChild;
 | 
			
		||||
import org.alfresco.rm.rest.api.model.RetentionSchedule;
 | 
			
		||||
import org.alfresco.rm.rest.api.model.RetentionScheduleActionDefinition;
 | 
			
		||||
import org.alfresco.service.ServiceRegistry;
 | 
			
		||||
import org.alfresco.service.cmr.model.FileInfo;
 | 
			
		||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
 | 
			
		||||
@@ -69,6 +80,9 @@ import org.alfresco.service.cmr.repository.NodeService;
 | 
			
		||||
import org.alfresco.service.cmr.security.PersonService;
 | 
			
		||||
import org.alfresco.service.namespace.NamespaceService;
 | 
			
		||||
import org.alfresco.service.namespace.QName;
 | 
			
		||||
import org.alfresco.service.namespace.RegexQNamePattern;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Utility class containing Alfresco and RM java services required by the API
 | 
			
		||||
@@ -80,6 +94,9 @@ import org.alfresco.service.namespace.QName;
 | 
			
		||||
public class ApiNodesModelFactory
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    /** Logger */
 | 
			
		||||
    private static final Logger LOGGER = LoggerFactory.getLogger(ApiNodesModelFactory.class);
 | 
			
		||||
 | 
			
		||||
    // excluded namespaces (aspects, properties, assoc types)
 | 
			
		||||
    public static final List<String> EXCLUDED_NS = Arrays.asList(NamespaceService.SYSTEM_MODEL_1_0_URI);
 | 
			
		||||
 | 
			
		||||
@@ -101,6 +118,7 @@ public class ApiNodesModelFactory
 | 
			
		||||
    private PersonService personService;
 | 
			
		||||
    private DispositionService dispositionService;
 | 
			
		||||
    private ServiceRegistry serviceRegistry;
 | 
			
		||||
    private RecordsManagementServiceRegistry services;
 | 
			
		||||
 | 
			
		||||
    public NodeService getNodeService()
 | 
			
		||||
    {
 | 
			
		||||
@@ -152,6 +170,11 @@ public class ApiNodesModelFactory
 | 
			
		||||
        this.serviceRegistry = serviceRegistry;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setRecordsManagementServiceRegistry(RecordsManagementServiceRegistry services)
 | 
			
		||||
    {
 | 
			
		||||
        this.services = services;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Helper method that sets the basic information for most of the node types.
 | 
			
		||||
     *
 | 
			
		||||
@@ -503,15 +526,15 @@ public class ApiNodesModelFactory
 | 
			
		||||
        }
 | 
			
		||||
        if(RecordsManagementModel.TYPE_RECORD_FOLDER.equals(info.getType()))
 | 
			
		||||
        {
 | 
			
		||||
            if((!isMinimalInfo && propertyFilter.isAllowed(RecordCategoryChild.PARAM_IS_RECORD_FOLDER)) || (isMinimalInfo && includeParam.contains(RecordCategoryChild.PARAM_IS_RECORD_FOLDER)))
 | 
			
		||||
            if (isRecordFolder(isMinimalInfo, propertyFilter, includeParam))
 | 
			
		||||
            {
 | 
			
		||||
                recordCategoryChild.setIsRecordFolder(true);
 | 
			
		||||
            }
 | 
			
		||||
            if((!isMinimalInfo && propertyFilter.isAllowed(RecordCategoryChild.PARAM_IS_RECORD_CATEGORY)) || (isMinimalInfo && includeParam.contains(RecordCategoryChild.PARAM_IS_RECORD_CATEGORY)))
 | 
			
		||||
            if (isRecordCategory(isMinimalInfo, propertyFilter, includeParam))
 | 
			
		||||
            {
 | 
			
		||||
                recordCategoryChild.setIsRecordCategory(false);
 | 
			
		||||
            }
 | 
			
		||||
            if((!isMinimalInfo && propertyFilter.isAllowed(RMNode.PARAM_IS_CLOSED)) || (isMinimalInfo && includeParam.contains(RMNode.PARAM_IS_CLOSED)))
 | 
			
		||||
            if (isRecordCategoryChildClosed(isMinimalInfo, propertyFilter, includeParam))
 | 
			
		||||
            {
 | 
			
		||||
                recordCategoryChild.setIsClosed((Boolean) nodeService.getProperty(info.getNodeRef(), RecordsManagementModel.PROP_IS_CLOSED));
 | 
			
		||||
            }
 | 
			
		||||
@@ -522,11 +545,11 @@ public class ApiNodesModelFactory
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            if((!isMinimalInfo && propertyFilter.isAllowed(RecordCategoryChild.PARAM_IS_RECORD_FOLDER)) || (isMinimalInfo && includeParam.contains(RecordCategoryChild.PARAM_IS_RECORD_FOLDER)))
 | 
			
		||||
            if (isRecordFolder(isMinimalInfo, propertyFilter, includeParam))
 | 
			
		||||
            {
 | 
			
		||||
                recordCategoryChild.setIsRecordFolder(false);
 | 
			
		||||
            }
 | 
			
		||||
            if((!isMinimalInfo && propertyFilter.isAllowed(RecordCategoryChild.PARAM_IS_RECORD_CATEGORY)) || (isMinimalInfo && includeParam.contains(RecordCategoryChild.PARAM_IS_RECORD_CATEGORY)))
 | 
			
		||||
            if (isRecordCategory(isMinimalInfo, propertyFilter, includeParam))
 | 
			
		||||
            {
 | 
			
		||||
                recordCategoryChild.setIsRecordCategory(true);
 | 
			
		||||
            }
 | 
			
		||||
@@ -535,13 +558,28 @@ public class ApiNodesModelFactory
 | 
			
		||||
                DispositionSchedule ds = dispositionService.getDispositionSchedule(info.getNodeRef());
 | 
			
		||||
                recordCategoryChild.setHasRetentionSchedule(ds != null);
 | 
			
		||||
            }
 | 
			
		||||
            if((!isMinimalInfo && propertyFilter.isAllowed(RMNode.PARAM_IS_CLOSED)) || (isMinimalInfo && includeParam.contains(RMNode.PARAM_IS_CLOSED)))
 | 
			
		||||
            if (isRecordCategoryChildClosed(isMinimalInfo, propertyFilter, includeParam))
 | 
			
		||||
            {
 | 
			
		||||
                recordCategoryChild.setIsClosed(null);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private boolean isRecordCategoryChildClosed(boolean isMinimalInfo, BeanPropertiesFilter propertyFilter, List<String> includeParam)
 | 
			
		||||
    {
 | 
			
		||||
        return (!isMinimalInfo && propertyFilter.isAllowed(RMNode.PARAM_IS_CLOSED)) || (isMinimalInfo && includeParam.contains(RMNode.PARAM_IS_CLOSED));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private boolean isRecordCategory(boolean isMinimalInfo, BeanPropertiesFilter propertyFilter, List<String> includeParam)
 | 
			
		||||
    {
 | 
			
		||||
        return (!isMinimalInfo && propertyFilter.isAllowed(RecordCategoryChild.PARAM_IS_RECORD_CATEGORY)) || (isMinimalInfo && includeParam.contains(RecordCategoryChild.PARAM_IS_RECORD_CATEGORY));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private boolean isRecordFolder(boolean isMinimalInfo, BeanPropertiesFilter propertyFilter, List<String> includeParam)
 | 
			
		||||
    {
 | 
			
		||||
        return (!isMinimalInfo && propertyFilter.isAllowed(RecordCategoryChild.PARAM_IS_RECORD_FOLDER)) || (isMinimalInfo && includeParam.contains(RecordCategoryChild.PARAM_IS_RECORD_FOLDER));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Utility method that maps record specific fields
 | 
			
		||||
@@ -564,7 +602,8 @@ public class ApiNodesModelFactory
 | 
			
		||||
        {
 | 
			
		||||
            Serializable val = info.getProperties().get(ContentModel.PROP_CONTENT);
 | 
			
		||||
 | 
			
		||||
            if ((val != null) && (val instanceof ContentData)) {
 | 
			
		||||
            if (val instanceof ContentData)
 | 
			
		||||
            {
 | 
			
		||||
                ContentData cd = (ContentData)val;
 | 
			
		||||
                String mimeType = cd.getMimetype();
 | 
			
		||||
                String mimeTypeName = serviceRegistry.getMimetypeService().getDisplaysByMimetype().get(mimeType);
 | 
			
		||||
@@ -637,6 +676,21 @@ public class ApiNodesModelFactory
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates an object of type HoldModel
 | 
			
		||||
     *
 | 
			
		||||
     * @param info info of the hold
 | 
			
		||||
     * @return HoldModel object
 | 
			
		||||
     */
 | 
			
		||||
    public HoldModel createHoldModel(FileInfo info)
 | 
			
		||||
    {
 | 
			
		||||
        return new HoldModel(info.getNodeRef().getId(),
 | 
			
		||||
            (String) info.getProperties().get(ContentModel.PROP_NAME),
 | 
			
		||||
            (String) info.getProperties().get(ContentModel.PROP_DESCRIPTION),
 | 
			
		||||
            (String)  info.getProperties().get(RecordsManagementModel.PROP_HOLD_REASON));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates an object of type FilePlan
 | 
			
		||||
     *
 | 
			
		||||
@@ -875,4 +929,238 @@ public class ApiNodesModelFactory
 | 
			
		||||
        mapAssociations(record, info, parameters.getInclude());
 | 
			
		||||
        return record;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Helper method that sets the information for the retention schedule type.
 | 
			
		||||
     * @param dispositionSchedule
 | 
			
		||||
     * @return RetentionSchedule
 | 
			
		||||
     */
 | 
			
		||||
    public RetentionSchedule mapRetentionScheduleData(DispositionSchedule dispositionSchedule)
 | 
			
		||||
    {
 | 
			
		||||
        RetentionSchedule retentionSchedule = new RetentionSchedule();
 | 
			
		||||
        retentionSchedule.setId(dispositionSchedule.getNodeRef().getId());
 | 
			
		||||
        if (dispositionSchedule.getNodeRef() != null) {
 | 
			
		||||
            NodeRef parent = this.nodeService.getPrimaryParent(dispositionSchedule.getNodeRef()).getParentRef();
 | 
			
		||||
            retentionSchedule.setParentId(parent.getId());
 | 
			
		||||
        }
 | 
			
		||||
        retentionSchedule.setInstructions(dispositionSchedule.getDispositionInstructions());
 | 
			
		||||
        retentionSchedule.setAuthority(dispositionSchedule.getDispositionAuthority());
 | 
			
		||||
        retentionSchedule.setIsRecordLevel(dispositionSchedule.isRecordLevelDisposition());
 | 
			
		||||
 | 
			
		||||
        boolean unpublishedUpdates = dispositionSchedule.getDispositionActionDefinitions().stream()
 | 
			
		||||
                .map(DispositionActionDefinition::getNodeRef)
 | 
			
		||||
                .anyMatch(actionDefNodeRef -> nodeService.hasAspect(actionDefNodeRef, RecordsManagementModel.ASPECT_UNPUBLISHED_UPDATE));
 | 
			
		||||
        retentionSchedule.setUnpublishedUpdates(unpublishedUpdates);
 | 
			
		||||
        return retentionSchedule;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Helper method that sets the information for the retention schedule action definition type.
 | 
			
		||||
     * @param dispositionActionDefinition
 | 
			
		||||
     * @return RetentionScheduleActionDefinition
 | 
			
		||||
     */
 | 
			
		||||
    public RetentionScheduleActionDefinition mapRetentionScheduleActionDefData(DispositionActionDefinition dispositionActionDefinition)
 | 
			
		||||
    {
 | 
			
		||||
        RetentionScheduleActionDefinition retentionScheduleActionDefinition = new RetentionScheduleActionDefinition();
 | 
			
		||||
        // Mapping basic properties
 | 
			
		||||
        mapRetentionActionProperties(dispositionActionDefinition, retentionScheduleActionDefinition);
 | 
			
		||||
        // Mapping period and period amount
 | 
			
		||||
        mapPeriodProperties(dispositionActionDefinition, retentionScheduleActionDefinition);
 | 
			
		||||
        // Mapping events properties
 | 
			
		||||
        mapEventsProperties(dispositionActionDefinition, retentionScheduleActionDefinition);
 | 
			
		||||
        return retentionScheduleActionDefinition;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Helper method that sets core information for the retention schedule action definition type.
 | 
			
		||||
     * @param dispositionActionDefinition
 | 
			
		||||
     * @param retentionScheduleActionDefinition
 | 
			
		||||
     */
 | 
			
		||||
    private void mapRetentionActionProperties(DispositionActionDefinition dispositionActionDefinition, RetentionScheduleActionDefinition retentionScheduleActionDefinition)
 | 
			
		||||
    {
 | 
			
		||||
        retentionScheduleActionDefinition.setId(dispositionActionDefinition.getId());
 | 
			
		||||
        retentionScheduleActionDefinition.setName(dispositionActionDefinition.getName());
 | 
			
		||||
        retentionScheduleActionDefinition.setDescription(dispositionActionDefinition.getDescription());
 | 
			
		||||
        retentionScheduleActionDefinition.setEligibleOnFirstCompleteEvent(dispositionActionDefinition.eligibleOnFirstCompleteEvent());
 | 
			
		||||
        if (nodeService.getProperty(dispositionActionDefinition.getNodeRef(), RecordsManagementModel.PROP_COMBINE_DISPOSITION_STEP_CONDITIONS) != null)
 | 
			
		||||
        {
 | 
			
		||||
            retentionScheduleActionDefinition.setCombineRetentionStepConditions((Boolean) nodeService.getProperty(dispositionActionDefinition.getNodeRef(), RecordsManagementModel.PROP_COMBINE_DISPOSITION_STEP_CONDITIONS));
 | 
			
		||||
        }
 | 
			
		||||
        retentionScheduleActionDefinition.setLocation(dispositionActionDefinition.getLocation());
 | 
			
		||||
        if (dispositionActionDefinition.getGhostOnDestroy() != null)
 | 
			
		||||
        {
 | 
			
		||||
            retentionScheduleActionDefinition.setRetainRecordMetadataAfterDestruction(dispositionActionDefinition.getGhostOnDestroy().equals("ghost"));
 | 
			
		||||
        }
 | 
			
		||||
        retentionScheduleActionDefinition.setIndex(dispositionActionDefinition.getIndex());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Helper method that sets the period-related information for the retention schedule action definition type.
 | 
			
		||||
     * @param dispositionActionDefinition
 | 
			
		||||
     * @param retentionScheduleActionDefinition
 | 
			
		||||
     */
 | 
			
		||||
    private void mapPeriodProperties(DispositionActionDefinition dispositionActionDefinition, RetentionScheduleActionDefinition retentionScheduleActionDefinition)
 | 
			
		||||
    {
 | 
			
		||||
        if (dispositionActionDefinition.getPeriodProperty() != null)
 | 
			
		||||
        {
 | 
			
		||||
            retentionScheduleActionDefinition.setPeriodProperty(dispositionActionDefinition.getPeriodProperty().toPrefixString(namespaceService));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        String period = dispositionActionDefinition.getPeriod().toString();
 | 
			
		||||
        if (!period.isEmpty())
 | 
			
		||||
        {
 | 
			
		||||
            // In rest api we are splitting `period` property into `period` and `periodAmount`.
 | 
			
		||||
            // so we need to split the period into two properties.
 | 
			
		||||
            // ex. period -> 'month|10' so the split properties would be like below
 | 
			
		||||
            // period -> 'month'
 | 
			
		||||
            // periodAmount -> 10
 | 
			
		||||
            String[] periodArray = period.split("\\|");
 | 
			
		||||
 | 
			
		||||
            if (periodArray.length > 0)
 | 
			
		||||
            {
 | 
			
		||||
                retentionScheduleActionDefinition.setPeriod(periodArray[0]);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (periodArray.length > 1)
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    retentionScheduleActionDefinition.setPeriodAmount(Integer.parseInt(periodArray[1]));
 | 
			
		||||
                }
 | 
			
		||||
                catch (NumberFormatException numberFormatException)
 | 
			
		||||
                {
 | 
			
		||||
                    LOGGER.error("Error parsing period amount: {}{}", numberFormatException.getMessage(), periodArray[1], numberFormatException);
 | 
			
		||||
                    throw numberFormatException;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Helper method that sets the events information for the retention schedule action definition type.
 | 
			
		||||
     * @param dispositionActionDefinition
 | 
			
		||||
     * @param retentionScheduleActionDefinition
 | 
			
		||||
     */
 | 
			
		||||
    private void mapEventsProperties(DispositionActionDefinition dispositionActionDefinition, RetentionScheduleActionDefinition retentionScheduleActionDefinition)
 | 
			
		||||
    {
 | 
			
		||||
        List<RecordsManagementEvent> events = dispositionActionDefinition.getEvents();
 | 
			
		||||
        if (events != null && !events.isEmpty())
 | 
			
		||||
        {
 | 
			
		||||
            List<String> eventNames = events.stream()
 | 
			
		||||
                    .map(RecordsManagementEvent::getName)
 | 
			
		||||
                    .collect(Collectors.toList());
 | 
			
		||||
            retentionScheduleActionDefinition.setEvents(eventNames);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Helper method that sets the optional information for the retention schedule type.
 | 
			
		||||
     * @param retentionSchedule
 | 
			
		||||
     * @param schedule
 | 
			
		||||
     * @param includeParam
 | 
			
		||||
     */
 | 
			
		||||
    public void mapRetentionScheduleOptionalInfo(RetentionSchedule retentionSchedule, DispositionSchedule schedule, List<String> includeParam)
 | 
			
		||||
    {
 | 
			
		||||
        if (includeParam != null && !includeParam.isEmpty() && includeParam.contains("actions"))
 | 
			
		||||
        {
 | 
			
		||||
            List<RetentionScheduleActionDefinition> actions = schedule.getDispositionActionDefinitions().stream()
 | 
			
		||||
                    .map(this::mapRetentionScheduleActionDefData)
 | 
			
		||||
                    .collect(Collectors.toList());
 | 
			
		||||
            retentionSchedule.setActions(actions);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * this method is used for creation of retention schedule action definition params
 | 
			
		||||
     * @param nodeInfo retention schedule action definition
 | 
			
		||||
     * @return Map<QName, Serializable>
 | 
			
		||||
     */
 | 
			
		||||
    public Map<QName, Serializable> createRetentionActionDefinitionParams(RetentionScheduleActionDefinition nodeInfo)
 | 
			
		||||
    {
 | 
			
		||||
        Map<QName, Serializable>  actionDefinitionParams= new HashMap<>();
 | 
			
		||||
 | 
			
		||||
        String retentionActionName = nodeInfo.getName();
 | 
			
		||||
 | 
			
		||||
        if (nodeInfo.getName().equals(RetentionSteps.DESTROY_NODE.stepName) ||
 | 
			
		||||
                nodeInfo.getName().equals(RetentionSteps.DESTROY_CONTENT.stepName))
 | 
			
		||||
        {
 | 
			
		||||
            retentionActionName = "destroy";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        actionDefinitionParams.put(RecordsManagementModel.PROP_DISPOSITION_ACTION_NAME, retentionActionName);
 | 
			
		||||
        actionDefinitionParams.put(RecordsManagementModel.PROP_DISPOSITION_DESCRIPTION, nodeInfo.getDescription());
 | 
			
		||||
        StringBuilder retentionPeriod = new StringBuilder(nodeInfo.getPeriod()).append("|");
 | 
			
		||||
 | 
			
		||||
        if (isPeriodAmountApplicable(nodeInfo.getPeriod()))
 | 
			
		||||
        {
 | 
			
		||||
            retentionPeriod.append(nodeInfo.getPeriodAmount());
 | 
			
		||||
        }
 | 
			
		||||
        actionDefinitionParams.put(RecordsManagementModel.PROP_DISPOSITION_PERIOD, retentionPeriod.toString());
 | 
			
		||||
        QName periodProperty = QName.createQName(nodeInfo.getPeriodProperty(), namespaceService);
 | 
			
		||||
        actionDefinitionParams.put(RecordsManagementModel.PROP_DISPOSITION_PERIOD_PROPERTY, periodProperty);
 | 
			
		||||
        actionDefinitionParams.put(RecordsManagementModel.PROP_DISPOSITION_EVENT_COMBINATION,
 | 
			
		||||
                nodeInfo.isEligibleOnFirstCompleteEvent());
 | 
			
		||||
        boolean combineConditions = nodeInfo.getName().equals(RetentionSteps.ACCESSION.stepName) && nodeInfo.isCombineRetentionStepConditions();
 | 
			
		||||
        actionDefinitionParams.put(RecordsManagementModel.PROP_COMBINE_DISPOSITION_STEP_CONDITIONS, combineConditions);
 | 
			
		||||
 | 
			
		||||
        if(nodeInfo.getLocation() != null && nodeInfo.getName().equals(RetentionSteps.TRANSFER.stepName))
 | 
			
		||||
        {
 | 
			
		||||
            actionDefinitionParams.put(RecordsManagementModel.PROP_DISPOSITION_LOCATION,
 | 
			
		||||
                    nodeInfo.getLocation());
 | 
			
		||||
        }
 | 
			
		||||
        List<String> inputEvents = nodeInfo.getEvents();
 | 
			
		||||
        actionDefinitionParams.put(RecordsManagementModel.PROP_DISPOSITION_EVENT, (Serializable) inputEvents);
 | 
			
		||||
 | 
			
		||||
        if (RetentionSteps.DESTROY_CONTENT.stepName.equals(nodeInfo.getName()))
 | 
			
		||||
        {
 | 
			
		||||
            actionDefinitionParams.put(RecordsManagementModel.PROP_DISPOSITION_ACTION_GHOST_ON_DESTROY, "ghost");
 | 
			
		||||
        }
 | 
			
		||||
        else if (RetentionSteps.DESTROY_NODE.stepName.equals(nodeInfo.getName()))
 | 
			
		||||
        {
 | 
			
		||||
            actionDefinitionParams.put(RecordsManagementModel.PROP_DISPOSITION_ACTION_GHOST_ON_DESTROY, "delete");
 | 
			
		||||
        }
 | 
			
		||||
        return actionDefinitionParams;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * this method is used retrieve retention schedule action details
 | 
			
		||||
     * @param retentionScheduleNodeRef nodeRef
 | 
			
		||||
     * @return List<DispositionActionDefinition>
 | 
			
		||||
     */
 | 
			
		||||
    public List<DispositionActionDefinition> getRetentionActions(NodeRef retentionScheduleNodeRef)
 | 
			
		||||
    {
 | 
			
		||||
        List<ChildAssociationRef> assocs = nodeService.getChildAssocs(
 | 
			
		||||
                retentionScheduleNodeRef,
 | 
			
		||||
                RecordsManagementModel.ASSOC_DISPOSITION_ACTION_DEFINITIONS,
 | 
			
		||||
                RegexQNamePattern.MATCH_ALL);
 | 
			
		||||
        // we are getting disposition action definitions based on retention schedule child association.
 | 
			
		||||
        // setting the index value for each action.
 | 
			
		||||
        List<DispositionActionDefinition> actions;
 | 
			
		||||
        actions = IntStream.range(0, assocs.size())
 | 
			
		||||
                .mapToObj(index ->
 | 
			
		||||
                {
 | 
			
		||||
                    ChildAssociationRef assoc = assocs.get(index);
 | 
			
		||||
                    return new DispositionActionDefinitionImpl(
 | 
			
		||||
                            services.getRecordsManagementEventService(),
 | 
			
		||||
                            services.getRecordsManagementActionService(),
 | 
			
		||||
                            nodeService,
 | 
			
		||||
                            assoc.getChildRef(),
 | 
			
		||||
                            index);
 | 
			
		||||
                })
 | 
			
		||||
                .collect(Collectors.toList());
 | 
			
		||||
        return actions;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * this method is used to check period amount applicable or not for particular period
 | 
			
		||||
     * @param period period
 | 
			
		||||
     * @return boolean
 | 
			
		||||
     */
 | 
			
		||||
    private boolean isPeriodAmountApplicable(String period)
 | 
			
		||||
    {
 | 
			
		||||
        // periodAmount property only applicable for following periods
 | 
			
		||||
        // day, week, month, quarter, year and duration
 | 
			
		||||
        return period.equals(RetentionPeriod.DAY.periodName) || period.equals(RetentionPeriod.MONTH.periodName) || period.equals(RetentionPeriod.QUARTER.periodName)
 | 
			
		||||
                || period.equals(RetentionPeriod.WEEK.periodName) || period.equals(RetentionPeriod.XML_DURATION.periodName) || period.equals(RetentionPeriod.YEAR.periodName);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -92,7 +92,7 @@ public class SearchTypesFactory
 | 
			
		||||
        boolean includeRecords = false;
 | 
			
		||||
        boolean includeSubTypes = false;
 | 
			
		||||
 | 
			
		||||
        if (q != null)
 | 
			
		||||
        if (q != null && q.getTree() != null)
 | 
			
		||||
        {
 | 
			
		||||
            // filtering via "where" clause
 | 
			
		||||
            MapBasedQueryWalker propertyWalker = new MapBasedQueryWalker(listFolderChildrenEqualsQueryProperties, null);
 | 
			
		||||
@@ -101,11 +101,11 @@ public class SearchTypesFactory
 | 
			
		||||
            Boolean isUnfiledRecordFolder = propertyWalker.getProperty(UnfiledChild.PARAM_IS_UNFILED_RECORD_FOLDER,
 | 
			
		||||
                    WhereClauseParser.EQUALS, Boolean.class);
 | 
			
		||||
            Boolean isRecord = propertyWalker.getProperty(UnfiledChild.PARAM_IS_RECORD, WhereClauseParser.EQUALS, Boolean.class);
 | 
			
		||||
            if ((isUnfiledRecordFolder != null && isUnfiledRecordFolder.booleanValue()) || (isRecord != null && !isRecord.booleanValue()))
 | 
			
		||||
            if (checkIncludeUnfiledRecordFolders(isUnfiledRecordFolder, isRecord))
 | 
			
		||||
            {
 | 
			
		||||
                includeUnfiledRecordFolders = true;
 | 
			
		||||
            }
 | 
			
		||||
            else if ((isUnfiledRecordFolder != null && !isUnfiledRecordFolder.booleanValue()) || (isRecord != null && isRecord.booleanValue()))
 | 
			
		||||
            else if (checkIncludeRecords(isUnfiledRecordFolder, isRecord))
 | 
			
		||||
            {
 | 
			
		||||
                includeRecords = true;
 | 
			
		||||
            }
 | 
			
		||||
@@ -199,11 +199,11 @@ public class SearchTypesFactory
 | 
			
		||||
                    WhereClauseParser.EQUALS, Boolean.class);
 | 
			
		||||
            Boolean isRecordCategory = propertyWalker.getProperty(RecordCategoryChild.PARAM_IS_RECORD_CATEGORY, WhereClauseParser.EQUALS, Boolean.class);
 | 
			
		||||
 | 
			
		||||
            if ((isRecordFolder != null && isRecordFolder.booleanValue()) || (isRecordCategory != null && !isRecordCategory.booleanValue()))
 | 
			
		||||
            if (checkIncludeUnfiledRecordFolders(isRecordFolder, isRecordCategory))
 | 
			
		||||
            {
 | 
			
		||||
                includeRecordFolders = true;
 | 
			
		||||
            }
 | 
			
		||||
            else if ((isRecordFolder != null && !isRecordFolder.booleanValue()) || (isRecordCategory != null && isRecordCategory.booleanValue()))
 | 
			
		||||
            else if (checkIncludeRecords(isRecordFolder, isRecordCategory))
 | 
			
		||||
            {
 | 
			
		||||
                includeRecordCategories = true;
 | 
			
		||||
            }
 | 
			
		||||
@@ -291,4 +291,16 @@ public class SearchTypesFactory
 | 
			
		||||
 | 
			
		||||
        return new Pair<>(filterNodeTypeQName, filterIncludeSubTypes);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static boolean checkIncludeRecords(Boolean isUnfiledRecordFolder, Boolean isRecord)
 | 
			
		||||
    {
 | 
			
		||||
        return (isUnfiledRecordFolder != null && !isUnfiledRecordFolder.booleanValue()) || (isRecord != null
 | 
			
		||||
                    && isRecord.booleanValue());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static boolean checkIncludeUnfiledRecordFolders(Boolean isUnfiledRecordFolder, Boolean isRecord)
 | 
			
		||||
    {
 | 
			
		||||
        return (isUnfiledRecordFolder != null && isUnfiledRecordFolder.booleanValue()) || (isRecord != null
 | 
			
		||||
                    && !isRecord.booleanValue());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,29 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rm.rest.api.model;
 | 
			
		||||
 | 
			
		||||
public record BulkCancellationEntry(String reason) {}
 | 
			
		||||
@@ -0,0 +1,33 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rm.rest.api.model;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonProperty;
 | 
			
		||||
 | 
			
		||||
import org.alfresco.rest.api.search.model.Query;
 | 
			
		||||
 | 
			
		||||
public record HoldBulkOperation(@JsonProperty(required = true) Query query, @JsonProperty(required = true) HoldBulkOperationType op) {}
 | 
			
		||||
@@ -0,0 +1,29 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rm.rest.api.model;
 | 
			
		||||
 | 
			
		||||
public record HoldBulkOperationEntry(String bulkStatusId, long totalItems){}
 | 
			
		||||
@@ -0,0 +1,38 @@
 | 
			
		||||
/*
 | 
			
		||||
 * #%L
 | 
			
		||||
 * Alfresco Records Management Module
 | 
			
		||||
 * %%
 | 
			
		||||
 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
			
		||||
 * %%
 | 
			
		||||
 * This file is part of the Alfresco software.
 | 
			
		||||
 * -
 | 
			
		||||
 * If the software was purchased under a paid Alfresco license, the terms of
 | 
			
		||||
 * the paid license agreement will prevail.  Otherwise, the software is
 | 
			
		||||
 * provided under the following open source license terms:
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 * -
 | 
			
		||||
 * Alfresco is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 * -
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * #L%
 | 
			
		||||
 */
 | 
			
		||||
package org.alfresco.rm.rest.api.model;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This enum represents the types of bulk operations that can be performed on holds
 | 
			
		||||
 */
 | 
			
		||||
public enum HoldBulkOperationType
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * The ADD operation represents adding items to a hold in bulk.
 | 
			
		||||
     */
 | 
			
		||||
    ADD
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user