mirror of
				https://github.com/Alfresco/alfresco-community-repo.git
				synced 2025-10-29 15:21:53 +00:00 
			
		
		
		
	Compare commits
	
		
			408 Commits
		
	
	
		
			23.3.0.20
			...
			fix/MNT-24
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | fe3329b228 | ||
|  | 7550799a26 | ||
|  | 6238485d2b | ||
|  | 8059ccad53 | ||
|  | 3896979b6c | ||
|  | 98a978a4d1 | ||
|  | cb333d1c20 | ||
|  | ee1d33cb41 | ||
|  | 1e16f74292 | ||
|  | 8d72d662a0 | ||
|  | f346992500 | ||
|  | 5399fed53b | ||
|  | 1766ac749d | ||
|  | 5b31b22840 | ||
|  | b4c18c9902 | ||
|  | 7491ba5156 | ||
|  | 57d8f4bab7 | ||
|  | 6c0b29ec3d | ||
|  | 91adfe22d7 | ||
|  | 40036caae4 | ||
|  | 5a0aadb425 | ||
|  | f32979e84d | ||
|  | d4a8ef2442 | ||
|  | 5555ee6871 | ||
|  | 1ba62b2a4c | ||
|  | 8abf1c3d36 | ||
|  | 22b6131f93 | ||
|  | 8a731dce40 | ||
|  | a18861a4f6 | ||
|  | 87f31c5a3a | ||
|  | ef878fe20f | ||
|  | 168aae1a3d | ||
|  | ed706aa5d3 | ||
|  | fe5a1d8436 | ||
|  | 54d122ccc1 | ||
|  | 945fe52df9 | ||
|  | f1d9203430 | ||
|  | 1dc0720582 | ||
|  | 8dcc19cc63 | ||
|  | d4883af0d0 | ||
|  | 4cb220de66 | ||
|  | 97bcd725ee | ||
|  | 38259f9b39 | ||
|  | 9b1f0d78a5 | ||
|  | 95d7dbc5fc | ||
|  | 920366d287 | ||
|  | 14e26c46fc | ||
|  | c934f556cd | ||
|  | 73901303c5 | ||
|  | bbb22a79eb | ||
|  | 099b072a47 | ||
|  | 0bf3c078cf | ||
|  | 9b45f75b48 | ||
|  | 52914459ea | ||
|  | 446b08aa03 | ||
|  | 08fbc569fe | ||
|  | a8f50416a3 | ||
|  | c5122ddca0 | ||
|  | 1d93bcbb0d | ||
|  | c34f9af62d | ||
|  | 112875fbfd | ||
|  | 2d21456342 | ||
|  | 0363cab870 | ||
|  | 1f708ad712 | ||
|  | 475894525d | ||
|  | 58bad96724 | ||
|  | 02486a432a | ||
|  | 6de21cca78 | ||
|  | 185f7ebeb6 | ||
|  | 1b55aa9691 | ||
|  | 51aa490439 | ||
|  | 4cf2e76430 | ||
|  | 9e34ae3df8 | ||
|  | 6a4f82deae | ||
|  | 0e3173a573 | ||
|  | 2744561346 | ||
|  | 9b8ae96c2e | ||
|  | adb8913b56 | ||
|  | 02237bd280 | ||
|  | 78001ed22c | ||
|  | a568aeda17 | ||
|  | f890e9f995 | ||
|  | 0ddeac79bb | ||
|  | 67ca73820b | ||
|  | 1df8702e16 | ||
|  | 12c4481ac2 | ||
|  | 0f572ec21a | ||
|  | 9c121743d9 | ||
|  | c754eaeb93 | ||
|  | 1f3e08f439 | ||
|  | 5efa236cfe | ||
|  | df658371cd | ||
|  | 65f6d0ee45 | ||
|  | 275c42014e | ||
|  | b9e76970f9 | ||
|  | d0135e4b76 | ||
|  | d405be6273 | ||
|  | b7862932e6 | ||
|  | 970bca464f | ||
|  | f76d43eced | ||
|  | e07c452cf0 | ||
|  | 64899ca358 | ||
|  | de498664b6 | ||
|  | 46031feb52 | ||
|  | a93686acd5 | ||
|  | 0a46ec0ab2 | ||
|  | aa86d07738 | ||
|  | 553a8aae1c | ||
|  | f1bf73c269 | ||
|  | ce39a66934 | ||
|  | 64fa671f33 | ||
|  | 70c1da0213 | ||
|  | 283fffef00 | ||
|  | e99e3d69ba | ||
|  | f826b08b2b | ||
|  | 3bd57adeef | ||
|  | a4f8b8d4b9 | ||
|  | 6363c9e17a | ||
|  | b8a94ff310 | ||
|  | fd9e279715 | ||
|  | 6210ca95f9 | ||
|  | 7edba79f69 | ||
|  | 1eb24d2d85 | ||
|  | 3305eb91e5 | ||
|  | 0994545c65 | ||
|  | 94377f1e30 | ||
|  | 9c9d3dbbd5 | ||
|  | 7b4c210773 | ||
|  | 06d500311c | ||
|  | 8d95ffc9ff | ||
|  | 6e05d5d157 | ||
|  | d098508e53 | ||
|  | dfff72849d | ||
|  | d60172e860 | ||
|  | 3cac4e8206 | ||
|  | b35a64d14f | ||
|  | 738a77301a | ||
|  | be0ea5b247 | ||
|  | d33dc8e1d4 | ||
|  | d72b8c411a | ||
|  | 10e78191dd | ||
|  | 6290c46d62 | ||
|  | 8a61badabc | ||
|  | f9946827c4 | ||
|  | b812c7856e | ||
|  | a3f6e13a7c | ||
|  | afffc7e870 | ||
|  | fe5a01e2bd | ||
|  | 03625565e9 | ||
|  | 8d1d2b4f1b | ||
|  | 1342c6a7bb | ||
|  | 17152b69fc | ||
|  | 1a7027327e | ||
|  | 7921969222 | ||
|  | 9cc93de7b2 | ||
|  | 55c9cf3407 | ||
|  | 120f45ba92 | ||
|  | eacdbd3770 | ||
|  | 01c347673d | ||
|  | 93d4701d80 | ||
|  | 64baf03818 | ||
|  | e39606aec5 | ||
|  | 7581e07c3c | ||
|  | a01d375e6f | ||
|  | a98f44803a | ||
|  | cabc38b386 | ||
|  | 9ed29967b7 | ||
|  | b63a3eae9b | ||
|  | 1ce46c2039 | ||
|  | 278aa59302 | ||
|  | 51a51ecd6b | ||
|  | dcc6f23548 | ||
|  | 10f4b10ae8 | ||
|  | 24575c436e | ||
|  | 25c4b677de | ||
|  | 6f13f36c5a | ||
|  | db8b353fb1 | ||
|  | 2ccdee122a | ||
|  | 14c1b91a9b | ||
|  | 5b562edad1 | ||
|  | 19e577383a | ||
|  | 115997d367 | ||
|  | de2effcefb | ||
|  | 79b78448e0 | ||
|  | 05c4f2282e | ||
|  | 228944c59c | ||
|  | ec279bcd5d | ||
|  | 97770fd831 | ||
|  | 22c94284ec | ||
|  | 1a99c54074 | ||
|  | b30b7bd252 | ||
|  | 7ce9183360 | ||
|  | 06af664a16 | ||
|  | 7adf58d35f | ||
|  | 08e42ed877 | ||
|  | aa2fb35b41 | ||
|  | ab1e762a65 | ||
|  | c6201fa2fa | ||
|  | 0ab31fcc93 | ||
|  | 7616781ce9 | ||
|  | a7c83b9acc | ||
|  | a09649c40a | ||
|  | 0bb0e5b5b0 | ||
|  | 9f1956b632 | ||
|  | 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 | ||
|  | 740bce8c80 | ||
|  | 801b526b4a | ||
|  | fde025ad88 | ||
|  | 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 | ||
|  | ba61253c94 | ||
|  | 90b91b87ef | ||
|  | bfce7cf3ad | ||
|  | 03a261c4c5 | ||
|  | 4b3558ff12 | ||
|  | 4355add778 | ||
|  | 92fbaf29d4 | ||
|  | 8e15dba3eb | ||
|  | 2206479e06 | ||
|  | abeaff52e8 | ||
|  | 76be8d2bec | ||
|  | ce27304eae | ||
|  | 060cf3aa3c | ||
|  | f1fdf72c5b | ||
|  | b580a52459 | ||
|  | d13da0cdfa | ||
|  | ca236d9814 | ||
|  | bf5032675f | ||
|  | 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 | ||
|  | cce8ef50ef | ||
|  | 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 | ||
|  | 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" | ||||
|   | ||||
							
								
								
									
										454
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										454
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										20
									
								
								.github/workflows/master_release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								.github/workflows/master_release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -31,15 +31,15 @@ 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 | ||||
|       - 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/alfresco-build-tools/.github/actions/get-build-info@v7.0.0 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v7.0.0 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v7.0.0 | ||||
|       - name: "Init" | ||||
|         run: bash ./scripts/ci/init.sh | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/configure-git-author@v1.35.2 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/configure-git-author@v7.0.0 | ||||
|         with: | ||||
|           username: ${{ env.GIT_USERNAME }} | ||||
|           email: ${{ env.GIT_EMAIL }} | ||||
| @@ -60,15 +60,15 @@ 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 | ||||
|       - 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/alfresco-build-tools/.github/actions/get-build-info@v7.0.0 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v7.0.0 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v7.0.0 | ||||
|       - name: "Init" | ||||
|         run: bash ./scripts/ci/init.sh | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/configure-git-author@v1.35.2 | ||||
|       - uses: Alfresco/alfresco-build-tools/.github/actions/configure-git-author@v7.0.0 | ||||
|         with: | ||||
|           username: ${{ env.GIT_USERNAME }} | ||||
|           email: ${{ env.GIT_EMAIL }} | ||||
|   | ||||
							
								
								
									
										75
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										75
									
								
								README.md
									
									
									
									
									
								
							| @@ -2,38 +2,57 @@ | ||||
|  | ||||
| [](https://github.com/Alfresco/alfresco-community-repo/actions/workflows/master_release.yml) | ||||
|  | ||||
| #### Alfresco Core | ||||
| ## Table of Contents | ||||
| 1. [Content](#content) | ||||
| 2. [Artifacts](#artifacts) | ||||
| 3. [Setup](#setting-up-and-building-your-development-environment) | ||||
| 4. [Branches](#branches) | ||||
| 5. [Contributing](#contributing-guide) | ||||
| 6. [Helpful links](#helpful-links) | ||||
|  | ||||
|  | ||||
| ## Content | ||||
| Alfresco Community Repository contains following libraries: | ||||
|  | ||||
| ### Alfresco Core | ||||
| Core is a library packaged as a jar file which contains the following: | ||||
|  | ||||
| Alfresco Core is a library packaged as a jar file which contains the following: | ||||
| * Various helpers and utils | ||||
| * Canned queries interface and supporting classes | ||||
| * Generic encryption supporting classes | ||||
|  | ||||
| #### Alfresco Data Model | ||||
| Data model is a library packaged as a jar file which  contains the following: | ||||
| ### Alfresco Data Model | ||||
|  | ||||
| Data Model is a library packaged as a jar file which  contains the following: | ||||
|  | ||||
| * Dictionary, Repository and Search Services interfaces | ||||
| * Models for data types and Dictionary implementation | ||||
| * Parsers | ||||
|  | ||||
| #### Alfresco Repository | ||||
| ### Alfresco Repository | ||||
|  | ||||
| Repository is a library packaged as a jar file which contains the following: | ||||
|  | ||||
| * DAOs and SQL scripts | ||||
| * Various Service implementations | ||||
| * Utility classes | ||||
|  | ||||
| #### Alfresco Remote API | ||||
| ### Alfresco Remote API | ||||
|  | ||||
| Remote API is a library packaged as a jar file which contains the following: | ||||
|  | ||||
| * REST API framework | ||||
| * WebScript implementations including [V1 REST APIs](https://hub.alfresco.com/t5/alfresco-content-services-blog/v1-rest-api-10-things-you-should-know/ba-p/287692) | ||||
| * [OpenCMIS](https://chemistry.apache.org/java/opencmis.html) implementations | ||||
|  | ||||
| #### Artifacts | ||||
| ## Artifacts | ||||
|  | ||||
| The artifacts can be obtained by: | ||||
| * downloading from [Alfresco maven repository](https://artifacts.alfresco.com/nexus/content/groups/public) | ||||
| * downloading from [Alfresco maven repository](https://artifacts.alfresco.com/nexus/#browse/browse:public) | ||||
| * as Maven dependency by adding the dependency to your pom file: | ||||
| ~~~ | ||||
|  | ||||
| ~~~xml | ||||
|  | ||||
| <dependency> | ||||
|   <groupId>org.alfresco</groupId> | ||||
|   <artifactId>alfresco-core</artifactId> | ||||
| @@ -64,34 +83,46 @@ The artifacts can be obtained by: | ||||
|     <version>version</version> | ||||
|     <type>war</type> | ||||
| </dependency> | ||||
|  | ||||
| ~~~ | ||||
|  | ||||
| and Alfresco maven repository: | ||||
| ~~~ | ||||
|  | ||||
| ~~~xml | ||||
|  | ||||
| <repository> | ||||
|   <id>alfresco-maven-repo</id> | ||||
|   <url>https://artifacts.alfresco.com/nexus/content/groups/public</url> | ||||
| </repository> | ||||
|  | ||||
| ~~~ | ||||
|  | ||||
| The SNAPSHOT versions of the artifact are not published. | ||||
|  | ||||
| ## Setting up and building your development environment | ||||
| See the [Development Tomcat Environment](https://github.com/Alfresco/acs-community-packaging/tree/master/dev/README.md) | ||||
| page which will show you how to try out your repository changes in a local tomcat instance. | ||||
| If you wish to use Docker images, take a look at the aliases ending in `D` and the docker-compose files in this | ||||
| project's test modules.     | ||||
|  | ||||
| See the [**Development Tomcat Environment**](https://github.com/Alfresco/acs-community-packaging/tree/master/dev/README.md) | ||||
| page which will show you how to try out your repository changes in a local Tomcat instance or using Docker containers.  | ||||
|  | ||||
| ## Branches | ||||
| This project has a branch for each ACS release. For example the code in ACS 6.2.1 is a | ||||
| branch called `releases/6.2.2`. In addition to the original 6.2.2 release it will also contain Hot Fixes | ||||
| added later. The latest unreleased code is on the `master` branch. There are also `.N` branches, such as  | ||||
| `releases/7.1.N` on which we gather unreleased fixes for future service pack releases. They do not indicate | ||||
|  | ||||
| This project has a branch for each ACS release. For example the code in ACS 6.2.2 is a | ||||
| branch called **`release/6.2.2`**. In addition to the original 6.2.2 release it will also contain Hot Fixes | ||||
| added later. The latest unreleased code is on the **`master`** branch. There are also **`.N`** branches, such as  | ||||
| **`release/7.1.N`** on which we gather unreleased fixes for future service pack releases. They do not indicate | ||||
| that one is planned. | ||||
|  | ||||
| For historic reasons the version of artifacts created on each branch do not match the ACS version. | ||||
| For example artifact in ACS 7.2.0 will be `14.<something>`. | ||||
| For example artifact in ACS 7.2.0 will be **`14.<something>`**. | ||||
|  | ||||
| The enterprise projects which extend the `alfresco-community-repo` use the same branch names and leading | ||||
| The enterprise projects which extend the **`alfresco-community-repo`** use the same branch names and leading | ||||
| artifact version number. | ||||
|  | ||||
| ### Contributing guide | ||||
| Please use [this guide](CONTRIBUTING.md) to make a contribution to the project. | ||||
| ## Contributing guide | ||||
|  | ||||
| Please use [**this guide**](CONTRIBUTING.md) to make a contribution to the project. | ||||
|  | ||||
| ## Helpful links | ||||
|  | ||||
| - [Alfresco Content Services Documentation](https://docs.alfresco.com/content-services/latest/) | ||||
| - [Alfresco Platform](https://www.hyland.com/en/products/alfresco-platform) | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|    <parent> | ||||
|       <groupId>org.alfresco</groupId> | ||||
|       <artifactId>alfresco-community-repo-amps</artifactId> | ||||
|       <version>23.3.0.20</version> | ||||
|       <version>23.4.0.16-SNAPSHOT</version> | ||||
|    </parent> | ||||
|  | ||||
|    <modules> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|    <parent> | ||||
|       <groupId>org.alfresco</groupId> | ||||
|       <artifactId>alfresco-governance-services-community-parent</artifactId> | ||||
|       <version>23.3.0.20</version> | ||||
|       <version>23.4.0.16-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.20</version> | ||||
|       <version>23.4.0.16-SNAPSHOT</version> | ||||
|    </parent> | ||||
|  | ||||
|    <build> | ||||
| @@ -84,6 +84,12 @@ | ||||
|          <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> | ||||
| @@ -92,7 +98,7 @@ | ||||
|       <dependency> | ||||
|          <groupId>com.github.docker-java</groupId> | ||||
|          <artifactId>docker-java</artifactId> | ||||
|          <version>3.3.2</version> | ||||
|          <version>3.4.0</version> | ||||
|          <exclusions> | ||||
|             <exclusion> | ||||
|                <groupId>org.bouncycastle</groupId> | ||||
|   | ||||
| @@ -49,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; | ||||
| @@ -254,4 +255,14 @@ public class RestAPIFactory | ||||
|     { | ||||
|         return getGSCoreAPI(userModel).usingHoldsAPI(); | ||||
|     } | ||||
|  | ||||
|     public RetentionScheduleAPI getRetentionScheduleAPI() | ||||
|     { | ||||
|         return getGSCoreAPI(null).usingRetentionScheduleAPI(); | ||||
|     } | ||||
|  | ||||
|     public RetentionScheduleAPI getRetentionScheduleAPI(UserModel userModel) | ||||
|     { | ||||
|         return getGSCoreAPI(userModel).usingRetentionScheduleAPI(); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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,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; | ||||
| } | ||||
| @@ -48,5 +48,5 @@ import org.alfresco.rest.core.RestModels; | ||||
| public class HoldChildEntry extends RestModels<Hold, HoldChildEntry> | ||||
| { | ||||
|     @JsonProperty | ||||
|     private HoldChildEntry entry; | ||||
|     private HoldChild entry; | ||||
| } | ||||
| @@ -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; | ||||
| } | ||||
| @@ -47,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 | ||||
| @@ -193,4 +194,9 @@ public class GSCoreAPI extends RMModelRequest | ||||
|     } | ||||
|  | ||||
|     public HoldsAPI usingHoldsAPI() { return new HoldsAPI(getRmRestWrapper()); } | ||||
|  | ||||
|     public RetentionScheduleAPI usingRetentionScheduleAPI() | ||||
|     { | ||||
|         return new RetentionScheduleAPI(getRmRestWrapper()); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -38,7 +38,12 @@ 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; | ||||
| @@ -287,4 +292,155 @@ public class HoldsAPI extends RMModelRequest | ||||
|     { | ||||
|         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); | ||||
|     } | ||||
| } | ||||
| @@ -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())); | ||||
|     } | ||||
| } | ||||
| @@ -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); | ||||
|     } | ||||
| } | ||||
| @@ -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.20</version> | ||||
|       <version>23.4.0.16-SNAPSHOT</version> | ||||
|    </parent> | ||||
|  | ||||
|    <modules> | ||||
|   | ||||
| @@ -1,3 +1,3 @@ | ||||
| SOLR6_TAG=2.0.10-A1 | ||||
| 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> | ||||
|   | ||||
| @@ -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"> | ||||
| @@ -83,6 +84,14 @@ | ||||
|       <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"> | ||||
| @@ -139,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" /> | ||||
|   | ||||
| @@ -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.20</version> | ||||
|       <version>23.4.0.16-SNAPSHOT</version> | ||||
|    </parent> | ||||
|  | ||||
|    <properties> | ||||
| @@ -155,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> | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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; | ||||
|     } | ||||
| } | ||||
| @@ -30,6 +30,8 @@ import static org.alfresco.module.org_alfresco_module_rm.util.RMParameterCheck.c | ||||
| 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; | ||||
| @@ -42,6 +44,9 @@ 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; | ||||
| @@ -68,6 +73,7 @@ public class HoldsEntityResource implements | ||||
|     private ApiNodesModelFactory nodesModelFactory; | ||||
|     private HoldService holdService; | ||||
|     private TransactionService transactionService; | ||||
|     private HoldBulkService holdBulkService; | ||||
|  | ||||
|     @Override | ||||
|     public void afterPropertiesSet() throws Exception | ||||
| @@ -157,6 +163,23 @@ public class HoldsEntityResource implements | ||||
|         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; | ||||
| @@ -181,4 +204,9 @@ public class HoldsEntityResource implements | ||||
|     { | ||||
|         this.transactionService = transactionService; | ||||
|     } | ||||
|  | ||||
|     public void setHoldBulkService(HoldBulkService holdBulkService) | ||||
|     { | ||||
|         this.holdBulkService = holdBulkService; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
| @@ -53,6 +59,8 @@ 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; | ||||
| @@ -61,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; | ||||
| @@ -70,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 | ||||
| @@ -81,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); | ||||
|  | ||||
| @@ -102,6 +118,7 @@ public class ApiNodesModelFactory | ||||
|     private PersonService personService; | ||||
|     private DispositionService dispositionService; | ||||
|     private ServiceRegistry serviceRegistry; | ||||
|     private RecordsManagementServiceRegistry services; | ||||
|  | ||||
|     public NodeService getNodeService() | ||||
|     { | ||||
| @@ -153,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. | ||||
|      * | ||||
| @@ -504,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)); | ||||
|             } | ||||
| @@ -523,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); | ||||
|             } | ||||
| @@ -536,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 | ||||
| @@ -565,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); | ||||
| @@ -891,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 | ||||
| } | ||||
| @@ -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 java.util.Date; | ||||
|  | ||||
| public record HoldBulkStatusEntry(String bulkStatusId, Date startTime, Date endTime, long processedItems, long errorsCount, | ||||
|                                   long totalItems, String lastError, String status, String cancellationReason, HoldBulkOperation holdBulkOperation) { | ||||
| } | ||||
| @@ -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.rm.rest.api.model; | ||||
|  | ||||
| /** | ||||
|  * Retention event values | ||||
|  */ | ||||
| public enum RetentionEvents | ||||
| { | ||||
|     CASE_CLOSED("case_closed"), | ||||
|     ABOLISHED("abolished"), | ||||
|     RE_DESIGNATED("re_designated"), | ||||
|     NO_LONGER_NEEDED("no_longer_needed"), | ||||
|     SUPERSEDED("superseded"), | ||||
|     VERSIONED("versioned"), | ||||
|     STUDY_COMPLETE("study_complete"), | ||||
|     TRAINING_COMPLETE("training_complete"), | ||||
|     TRANSFERRED_INACTIVE_STORAGE("related_record_trasfered_inactive_storage"), | ||||
|     OBSOLETE("obsolete"), | ||||
|     ALLOWANCES_GRANTED_TERMINATED("all_allowances_granted_are_terminated"), | ||||
|     WGI_ACTION_COMPLETE("WGI_action_complete"), | ||||
|     SEPARATION("separation"), | ||||
|     CASE_COMPLETE("case_complete"), | ||||
|     DECLASSIFICATION_REVIEW("declassification_review"); | ||||
|  | ||||
|     public final String eventName; | ||||
|  | ||||
|     RetentionEvents(String eventName) | ||||
|     { | ||||
|         this.eventName = eventName; | ||||
|     } | ||||
| } | ||||
| @@ -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.rm.rest.api.model; | ||||
|  | ||||
| /** | ||||
|  * Retention period values | ||||
|  */ | ||||
| public enum RetentionPeriod | ||||
| { | ||||
|     DAY("day"), | ||||
|     END_OF_FINANCIAL_MONTH("fmend"), | ||||
|     END_OF_FINANCIAL_QUARTER("fqend"), | ||||
|     END_OF_FINANCIAL_YEAR("fyend"), | ||||
|     IMMEDIATELY("immediately"), | ||||
|     END_OF_MONTH("monthend"), | ||||
|     END_OF_QUARTER("quarterend"), | ||||
|     END_OF_YEAR("yearend"), | ||||
|     MONTH("month"), | ||||
|     NONE("none"), | ||||
|     QUARTER("quarter"), | ||||
|     WEEK("week"), | ||||
|     XML_DURATION("duration"), | ||||
|     YEAR("year"); | ||||
|  | ||||
|     public final String periodName; | ||||
|  | ||||
|     RetentionPeriod(String periodName) | ||||
|     { | ||||
|         this.periodName = periodName; | ||||
|     } | ||||
| } | ||||
| @@ -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.rm.rest.api.model; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * retention schedule | ||||
|  */ | ||||
| @Data | ||||
| public class RetentionSchedule | ||||
| { | ||||
|     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,51 @@ | ||||
| /* | ||||
|  * #%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 java.util.List; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| /** | ||||
|  * retention schedule action definition | ||||
|  */ | ||||
| @Data | ||||
| public class RetentionScheduleActionDefinition | ||||
| { | ||||
|     private String id; | ||||
|     private String name; | ||||
|     private String description; | ||||
|     private int periodAmount; | ||||
|     private String period; | ||||
|     private String periodProperty; | ||||
|     private boolean combineRetentionStepConditions; | ||||
|     private List<String> events; | ||||
|     private boolean eligibleOnFirstCompleteEvent; | ||||
|     private boolean retainRecordMetadataAfterDestruction; | ||||
|     private String location; | ||||
|     private int index; | ||||
| } | ||||
| @@ -0,0 +1,47 @@ | ||||
| /* | ||||
|  * #%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; | ||||
|  | ||||
| /** | ||||
|  * Retention steps values | ||||
|  */ | ||||
| public enum RetentionSteps | ||||
| { | ||||
|     RETAIN("retain"), | ||||
|     CUTOFF("cutoff"), | ||||
|     TRANSFER("transfer"), | ||||
|     ACCESSION("accession"), | ||||
|     DESTROY_CONTENT("destroyContent"), | ||||
|     DESTROY_NODE("destroyNode"); | ||||
|  | ||||
|     public final String stepName; | ||||
|  | ||||
|     RetentionSteps(String stepName) | ||||
|     { | ||||
|         this.stepName = stepName; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,284 @@ | ||||
| /* | ||||
|  * #%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.retentionschedule; | ||||
|  | ||||
| import org.alfresco.module.org_alfresco_module_rm.RecordsManagementServiceRegistry; | ||||
| import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionActionDefinition; | ||||
| import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionSchedule; | ||||
| import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionScheduleImpl; | ||||
| import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; | ||||
| import org.alfresco.rest.framework.WebApiDescription; | ||||
| import org.alfresco.rest.framework.core.exceptions.ConstraintViolatedException; | ||||
| import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException; | ||||
| import org.alfresco.rest.framework.core.exceptions.UnprocessableContentException; | ||||
| 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.RetentionEvents; | ||||
| import org.alfresco.rm.rest.api.model.RetentionPeriod; | ||||
| import org.alfresco.rm.rest.api.model.RetentionScheduleActionDefinition; | ||||
| import org.alfresco.rm.rest.api.model.RetentionSteps; | ||||
| import org.alfresco.service.cmr.repository.NodeRef; | ||||
| import org.alfresco.service.cmr.repository.NodeService; | ||||
| import org.alfresco.service.namespace.NamespaceService; | ||||
| import org.alfresco.service.namespace.QName; | ||||
|  | ||||
| import java.io.Serializable; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.HashSet; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.Set; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| import static org.alfresco.module.org_alfresco_module_rm.util.RMParameterCheck.checkNotBlank; | ||||
| import static org.alfresco.util.ParameterCheck.mandatory; | ||||
|  | ||||
| /** | ||||
|  * Retention schedule action relation is used to perform the retention schedule step operations. | ||||
|  */ | ||||
| @RelationshipResource(name = "retention-steps", entityResource = RetentionScheduleEntityResource.class, title = "Retention Schedule Action") | ||||
| public class RetentionScheduleActionRelation implements RelationshipResourceAction.Read<RetentionScheduleActionDefinition>, | ||||
|         RelationshipResourceAction.Create<RetentionScheduleActionDefinition> | ||||
| { | ||||
|     private FilePlanComponentsApiUtils apiUtils; | ||||
|     protected NodeService nodeService; | ||||
|     private RecordsManagementServiceRegistry service; | ||||
|     private ApiNodesModelFactory nodesModelFactory; | ||||
|  | ||||
|     public void setApiUtils(FilePlanComponentsApiUtils apiUtils) | ||||
|     { | ||||
|         this.apiUtils = apiUtils; | ||||
|     } | ||||
|  | ||||
|     public void setNodeService(NodeService nodeService) | ||||
|     { | ||||
|         this.nodeService = nodeService; | ||||
|     } | ||||
|  | ||||
|     public void setNodesModelFactory(ApiNodesModelFactory nodesModelFactory) | ||||
|     { | ||||
|         this.nodesModelFactory = nodesModelFactory; | ||||
|     } | ||||
|  | ||||
|     public void setRecordsManagementServiceRegistry(RecordsManagementServiceRegistry service) | ||||
|     { | ||||
|         this.service = service; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     @WebApiDescription(title="Create a retention schedule step for the particular retention schedule using the 'retentionScheduleId'") | ||||
|     public List<RetentionScheduleActionDefinition> create(String retentionScheduleId, List<RetentionScheduleActionDefinition> nodeInfos, Parameters parameters) | ||||
|     { | ||||
|         checkNotBlank("retentionScheduleId", retentionScheduleId); | ||||
|         mandatory("entity", nodeInfos); | ||||
|         mandatory("parameters", parameters); | ||||
|         NodeRef retentionScheduleNodeRef = apiUtils.lookupAndValidateNodeType(retentionScheduleId, RecordsManagementModel.TYPE_DISPOSITION_SCHEDULE); | ||||
|         // validation for the order of the step | ||||
|         retentionScheduleStepValidation(retentionScheduleNodeRef, nodeInfos.get(0)); | ||||
|         // request property validation | ||||
|         retentionScheduleRequestValidation(nodeInfos.get(0)); | ||||
|         // create the parameters for the action definition | ||||
|         Map<QName, Serializable> actionDefinitionParams = nodesModelFactory.createRetentionActionDefinitionParams(nodeInfos.get(0)); | ||||
|         // create the child association from the schedule to the action definition | ||||
|         NodeRef actionNodeRef = this.nodeService.createNode(retentionScheduleNodeRef, | ||||
|                 RecordsManagementModel.ASSOC_DISPOSITION_ACTION_DEFINITIONS, | ||||
|                 QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, | ||||
|                         QName.createValidLocalName(nodeInfos.get(0).getName())), | ||||
|                 RecordsManagementModel.TYPE_DISPOSITION_ACTION_DEFINITION, actionDefinitionParams).getChildRef(); | ||||
|         DispositionSchedule dispositionSchedule = new DispositionScheduleImpl(service, nodeService, retentionScheduleNodeRef); | ||||
|         DispositionActionDefinition dispositionActionDefinition = dispositionSchedule.getDispositionActionDefinition(actionNodeRef.getId()); | ||||
|         List<RetentionScheduleActionDefinition> responseActions = new ArrayList<>(); | ||||
|         if (dispositionActionDefinition != null) | ||||
|         { | ||||
|             responseActions.add(nodesModelFactory.mapRetentionScheduleActionDefData(dispositionActionDefinition)); | ||||
|         } | ||||
|         return responseActions; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     @WebApiDescription(title = "Return a paged list of retention schedule action definition based on the 'retentionScheduleId'") | ||||
|     public CollectionWithPagingInfo<RetentionScheduleActionDefinition> readAll(String retentionScheduleId, Parameters parameters) | ||||
|     { | ||||
|         checkNotBlank("retentionScheduleId", retentionScheduleId); | ||||
|         mandatory("parameters", parameters); | ||||
|         NodeRef retentionScheduleNodeRef = apiUtils.lookupAndValidateNodeType(retentionScheduleId, RecordsManagementModel.TYPE_DISPOSITION_SCHEDULE); | ||||
|         List<DispositionActionDefinition> actions = nodesModelFactory.getRetentionActions(retentionScheduleNodeRef); | ||||
|         List<RetentionScheduleActionDefinition> actionDefinitionList = actions.stream() | ||||
|                 .map(nodesModelFactory::mapRetentionScheduleActionDefData) | ||||
|                 .collect(Collectors.toList()); | ||||
|         return CollectionWithPagingInfo.asPaged(parameters.getPaging(), actionDefinitionList, false, | ||||
|                 actionDefinitionList.size()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * this method is used to validate the order of the retention schedule step | ||||
|      * @param retentionScheduleNodeRef nodeRef | ||||
|      * @param retentionScheduleActionDefinition retention schedule action definition | ||||
|      */ | ||||
|     private void retentionScheduleStepValidation(NodeRef retentionScheduleNodeRef, RetentionScheduleActionDefinition retentionScheduleActionDefinition) | ||||
|     { | ||||
|         if (checkStepNameIsEmpty(retentionScheduleActionDefinition.getName())) | ||||
|         { | ||||
|             throw new IllegalArgumentException("'name' parameter is mandatory when creating a disposition action definition"); | ||||
|         } | ||||
|  | ||||
|         List<DispositionActionDefinition> actions = nodesModelFactory.getRetentionActions(retentionScheduleNodeRef); | ||||
|         Set<String> completedActions = new HashSet<>(); | ||||
|         if (!actions.isEmpty()) | ||||
|         { | ||||
|             completedActions = actions.stream() | ||||
|                     .map(DispositionActionDefinition::getName) | ||||
|                     .collect(Collectors.toSet()); | ||||
|         } | ||||
|  | ||||
|         if (completedActions.contains("destroy")) | ||||
|         { | ||||
|             throw new ConstraintViolatedException("Invalid Step - destroy action is already added. No other action is allowed after Destroy."); | ||||
|         } | ||||
|  | ||||
|         if (checkStepAlreadyExists(completedActions, retentionScheduleActionDefinition.getName())) | ||||
|         { | ||||
|             throw new ConstraintViolatedException("Invalid Step - This step already exists. You can’t create it again. Only transfer action is allowed multiple times."); | ||||
|         } | ||||
|  | ||||
|         if (firstStepValidation(actions, retentionScheduleActionDefinition.getName())) | ||||
|         { | ||||
|             throw new UnprocessableContentException("Invalid Step - cutoff or retain should be the first step"); | ||||
|         } | ||||
|  | ||||
|         if (isCutOffStepAllowed(completedActions, retentionScheduleActionDefinition.getName())) | ||||
|         { | ||||
|             throw new ConstraintViolatedException("Invalid Step - Can't use cutoff after transfer or accession"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private boolean checkStepNameIsEmpty(String name) | ||||
|     { | ||||
|         return name == null || name.isEmpty(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * this method is used to validate the request of the retention schedule | ||||
|      * @param retentionScheduleActionDefinition retention schedule action definition | ||||
|      */ | ||||
|     private void retentionScheduleRequestValidation(RetentionScheduleActionDefinition retentionScheduleActionDefinition) | ||||
|     { | ||||
|         // step name validation | ||||
|         if (invalidStepNameCheck(retentionScheduleActionDefinition.getName())) | ||||
|         { | ||||
|             throw new InvalidArgumentException("name value is invalid : " +retentionScheduleActionDefinition.getName()); | ||||
|         } | ||||
|  | ||||
|         validatePeriodAndPeriodProperty(retentionScheduleActionDefinition); | ||||
|  | ||||
|         // event name validation | ||||
|         if (invalidEventNameCheck(retentionScheduleActionDefinition.getEvents())) | ||||
|         { | ||||
|             throw new InvalidArgumentException("event value is invalid: " + retentionScheduleActionDefinition.getEvents()); | ||||
|         } | ||||
|  | ||||
|         if (validateCombineRetentionStepConditionsForNonAccessionStep(retentionScheduleActionDefinition)) | ||||
|         { | ||||
|             throw new IllegalArgumentException("combineRetentionStepConditions property is only valid for accession step. Not valid for :" + retentionScheduleActionDefinition.getName()); | ||||
|         } | ||||
|  | ||||
|         if (validateLocationForNonTransferStep(retentionScheduleActionDefinition)) | ||||
|         { | ||||
|             throw new IllegalArgumentException("location property is only valid for transfer step. Not valid for :" + retentionScheduleActionDefinition.getName()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void validatePeriodAndPeriodProperty(RetentionScheduleActionDefinition retentionScheduleActionDefinition) | ||||
|     { | ||||
|         // period value validation | ||||
|         if (invalidPeriodCheck(retentionScheduleActionDefinition.getPeriod())) | ||||
|         { | ||||
|             throw new InvalidArgumentException("period value is invalid : " +retentionScheduleActionDefinition.getPeriod()); | ||||
|         } | ||||
|  | ||||
|         // periodProperty validation | ||||
|         List<String> validPeriodProperties = Arrays.asList("cm:created", "rma:cutOffDate", "rma:dispositionAsOf"); | ||||
|         if (validPeriodProperties.stream().noneMatch(retentionScheduleActionDefinition.getPeriodProperty()::equals)) | ||||
|         { | ||||
|             throw new InvalidArgumentException("periodProperty value is invalid: " + retentionScheduleActionDefinition.getPeriodProperty()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private boolean validateCombineRetentionStepConditionsForNonAccessionStep(RetentionScheduleActionDefinition retentionScheduleActionDefinition) | ||||
|     { | ||||
|         return !retentionScheduleActionDefinition.getName().equals(RetentionSteps.ACCESSION.stepName) | ||||
|                 && retentionScheduleActionDefinition.isCombineRetentionStepConditions(); | ||||
|     } | ||||
|  | ||||
|     private boolean validateLocationForNonTransferStep(RetentionScheduleActionDefinition retentionScheduleActionDefinition) | ||||
|     { | ||||
|         return retentionScheduleActionDefinition.getLocation() != null | ||||
|                 && !retentionScheduleActionDefinition.getName().equals(RetentionSteps.TRANSFER.stepName) | ||||
|                     && !retentionScheduleActionDefinition.getLocation().isEmpty(); | ||||
|     } | ||||
|  | ||||
|     private boolean checkStepAlreadyExists(Set<String> completedActions, String stepName) | ||||
|     { | ||||
|         return completedActions.contains(stepName) && !stepName.equals(RetentionSteps.TRANSFER.stepName); | ||||
|     } | ||||
|  | ||||
|     private boolean firstStepValidation(List<DispositionActionDefinition> actions, String stepName) | ||||
|     { | ||||
|         return actions.isEmpty() | ||||
|                 && !stepName.equals(RetentionSteps.CUTOFF.stepName) && (!stepName.equals(RetentionSteps.RETAIN.stepName)); | ||||
|     } | ||||
|  | ||||
|     private boolean isCutOffStepAllowed(Set<String> completedActions, String stepName) | ||||
|     { | ||||
|         return (completedActions.contains(RetentionSteps.TRANSFER.stepName) || completedActions.contains(RetentionSteps.ACCESSION.stepName)) | ||||
|                 && stepName.equals(RetentionSteps.CUTOFF.stepName); | ||||
|     } | ||||
|  | ||||
|     private boolean invalidStepNameCheck(String stepName) | ||||
|     { | ||||
|         return stepName != null && Arrays.stream(RetentionSteps.values()) | ||||
|                 .noneMatch(retentionStep -> retentionStep.stepName.equals(stepName)); | ||||
|     } | ||||
|  | ||||
|     private boolean invalidPeriodCheck(String period) | ||||
|     { | ||||
|         return period != null && Arrays.stream(RetentionPeriod.values()) | ||||
|                 .noneMatch(retentionPeriod -> retentionPeriod.periodName.equals(period)); | ||||
|     } | ||||
|  | ||||
|     private boolean invalidEventNameCheck(List<String> events) | ||||
|     { | ||||
|         return !events.isEmpty() && events.stream() | ||||
|                 .anyMatch(event -> Arrays.stream(RetentionEvents.values()) | ||||
|                         .noneMatch(retentionEvent -> retentionEvent.eventName.equals(event))); | ||||
|     } | ||||
| } | ||||
| @@ -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.retentionschedule; | ||||
|  | ||||
| import org.alfresco.rest.framework.resource.EntityResource; | ||||
|  | ||||
| /** | ||||
|  * Retention schedule entity resource | ||||
|  */ | ||||
| @EntityResource(name="retention-schedules", title = "Retention Schedule") | ||||
| public class RetentionScheduleEntityResource | ||||
| { | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,144 @@ | ||||
| /* | ||||
|  * #%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.retentionschedule; | ||||
|  | ||||
| import org.alfresco.model.ContentModel; | ||||
| 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.model.RecordsManagementModel; | ||||
| import org.alfresco.rest.framework.WebApiDescription; | ||||
| import org.alfresco.rest.framework.core.exceptions.UnprocessableContentException; | ||||
| 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.RetentionSchedule; | ||||
| import org.alfresco.rm.rest.api.recordcategories.RecordCategoriesEntityResource; | ||||
| import org.alfresco.service.cmr.repository.ChildAssociationRef; | ||||
| import org.alfresco.service.cmr.repository.NodeRef; | ||||
| import org.alfresco.service.cmr.repository.NodeService; | ||||
| import org.alfresco.service.cmr.repository.StoreRef; | ||||
| import org.alfresco.service.namespace.QName; | ||||
| import org.alfresco.service.namespace.RegexQNamePattern; | ||||
|  | ||||
| import java.io.Serializable; | ||||
| import java.util.ArrayList; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
|  | ||||
| import static org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel.PROP_DISPOSITION_AUTHORITY; | ||||
| import static org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel.PROP_DISPOSITION_INSTRUCTIONS; | ||||
| import static org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel.PROP_RECORD_LEVEL_DISPOSITION; | ||||
| import static org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel.TYPE_RECORD_CATEGORY; | ||||
| import static org.alfresco.module.org_alfresco_module_rm.util.RMParameterCheck.checkNotBlank; | ||||
| import static org.alfresco.util.ParameterCheck.mandatory; | ||||
|  | ||||
| /** | ||||
|  * Retention schedule relation is used perform retention schedule operation for a record category. | ||||
|  */ | ||||
| @RelationshipResource(name = "retention-schedules", entityResource = RecordCategoriesEntityResource.class, title = "Retention Schedule") | ||||
| public class RetentionScheduleRelation implements RelationshipResourceAction.Read<RetentionSchedule>, | ||||
|         RelationshipResourceAction.Create<RetentionSchedule> | ||||
| { | ||||
|  | ||||
|     private FilePlanComponentsApiUtils apiUtils; | ||||
|     private ApiNodesModelFactory nodesModelFactory; | ||||
|     private DispositionService dispositionService; | ||||
|     protected NodeService nodeService; | ||||
|  | ||||
|     public void setApiUtils(FilePlanComponentsApiUtils apiUtils) | ||||
|     { | ||||
|         this.apiUtils = apiUtils; | ||||
|     } | ||||
|  | ||||
|     public void setNodesModelFactory(ApiNodesModelFactory nodesModelFactory) | ||||
|     { | ||||
|         this.nodesModelFactory = nodesModelFactory; | ||||
|     } | ||||
|  | ||||
|     public void setDispositionService(DispositionService dispositionService) | ||||
|     { | ||||
|         this.dispositionService = dispositionService; | ||||
|     } | ||||
|  | ||||
|     public void setNodeService(NodeService nodeService) | ||||
|     { | ||||
|         this.nodeService = nodeService; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     @WebApiDescription(title="Create a retention schedule for the particular record category using the 'recordCategoryId'") | ||||
|     public List<RetentionSchedule> create(String recordCategoryId, List<RetentionSchedule> nodeInfos, Parameters parameters) | ||||
|     { | ||||
|         checkNotBlank("recordCategoryId", recordCategoryId); | ||||
|         mandatory("entity", nodeInfos); | ||||
|         mandatory("parameters", parameters); | ||||
|         NodeRef parentNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, recordCategoryId); | ||||
|  | ||||
|         if (checkCategoryHasAssocFolder(parentNodeRef) && nodeInfos.get(0).getIsRecordLevel()) | ||||
|         { | ||||
|             throw new UnprocessableContentException("Record level retention schedule cannot be created for a record category having folder associated."); | ||||
|         } | ||||
|         List<RetentionSchedule> result = new ArrayList<>(); | ||||
|         // Create the disposition schedule | ||||
|         Map<QName, Serializable> dsProps = new HashMap<>(); | ||||
|         dsProps.put(PROP_DISPOSITION_AUTHORITY, nodeInfos.get(0).getAuthority()); | ||||
|         dsProps.put(PROP_DISPOSITION_INSTRUCTIONS, nodeInfos.get(0).getInstructions()); | ||||
|         dsProps.put(PROP_RECORD_LEVEL_DISPOSITION, nodeInfos.get(0).getIsRecordLevel()); | ||||
|         DispositionSchedule dispositionSchedule = dispositionService.createDispositionSchedule(parentNodeRef, dsProps); | ||||
|         RetentionSchedule retentionSchedule = nodesModelFactory.mapRetentionScheduleData(dispositionSchedule); | ||||
|         result.add(retentionSchedule); | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     private boolean checkCategoryHasAssocFolder(NodeRef nodeRef) | ||||
|     { | ||||
|         List<ChildAssociationRef> assocs = nodeService.getChildAssocs(nodeRef, ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL); | ||||
|         return assocs.stream() | ||||
|                 .map(assoc -> nodeService.getType(assoc.getChildRef())) | ||||
|                 .anyMatch(nodeType -> nodeType.equals(RecordsManagementModel.TYPE_RECORD_FOLDER)); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     @WebApiDescription(title = "Return a paged list of retention schedule based on the 'recordCategoryId'") | ||||
|     public CollectionWithPagingInfo<RetentionSchedule> readAll(String recordCategoryId, Parameters parameters) | ||||
|     { | ||||
|         checkNotBlank("recordCategoryId", recordCategoryId); | ||||
|         mandatory("parameters", parameters); | ||||
|         NodeRef parentNodeRef = apiUtils.lookupAndValidateNodeType(recordCategoryId, TYPE_RECORD_CATEGORY); | ||||
|         DispositionSchedule schedule = dispositionService.getDispositionSchedule(parentNodeRef); | ||||
|         RetentionSchedule retentionSchedule = nodesModelFactory.mapRetentionScheduleData(schedule); | ||||
|         List<RetentionSchedule> retentionScheduleList = new ArrayList<>(); | ||||
|         nodesModelFactory.mapRetentionScheduleOptionalInfo(retentionSchedule, schedule, parameters.getInclude()); | ||||
|         retentionScheduleList.add(retentionSchedule); | ||||
|         return CollectionWithPagingInfo.asPaged(parameters.getPaging(), retentionScheduleList, false, | ||||
|                 retentionScheduleList.size()); | ||||
|     } | ||||
| } | ||||
| @@ -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 info that defines the Information Governance Retention Schedule REST API | ||||
|  */ | ||||
| @WebApi(name="gs", scope=Api.SCOPE.PUBLIC, version=1) | ||||
| package org.alfresco.rm.rest.api.retentionschedule; | ||||
| import org.alfresco.rest.framework.Api; | ||||
| import org.alfresco.rest.framework.WebApi; | ||||
| @@ -0,0 +1,306 @@ | ||||
| /* | ||||
|  * #%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.test.integration.bulk.hold; | ||||
|  | ||||
| import static java.util.concurrent.TimeUnit.SECONDS; | ||||
|  | ||||
| import static org.awaitility.Awaitility.await; | ||||
| import static org.mockito.ArgumentMatchers.any; | ||||
| import static org.mockito.Mockito.mock; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.Objects; | ||||
|  | ||||
| import org.alfresco.model.ContentModel; | ||||
| 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.hold.HoldBulkMonitor; | ||||
| import org.alfresco.module.org_alfresco_module_rm.bulk.hold.HoldBulkServiceImpl; | ||||
| import org.alfresco.module.org_alfresco_module_rm.bulk.hold.HoldBulkStatus; | ||||
| import org.alfresco.module.org_alfresco_module_rm.bulk.hold.HoldBulkStatus.Status; | ||||
| import org.alfresco.module.org_alfresco_module_rm.test.util.BaseRMTestCase; | ||||
| import org.alfresco.rest.api.search.model.Query; | ||||
| import org.alfresco.service.cmr.repository.NodeRef; | ||||
| import org.alfresco.service.cmr.repository.StoreRef; | ||||
| import org.alfresco.service.cmr.search.ResultSet; | ||||
| import org.alfresco.service.cmr.search.SearchParameters; | ||||
| import org.alfresco.service.cmr.search.SearchService; | ||||
| import org.mockito.Mockito; | ||||
| import org.mockito.stubbing.Answer; | ||||
| import org.springframework.extensions.webscripts.GUID; | ||||
|  | ||||
| /** | ||||
|  * Hold bulk service integration test. | ||||
|  */ | ||||
| @SuppressWarnings({ "PMD.TestClassWithoutTestCases", "PMD.JUnit4TestShouldUseTestAnnotation" }) | ||||
| public class HoldBulkServiceTest extends BaseRMTestCase | ||||
| { | ||||
|     private static final int RECORD_COUNT = 10; | ||||
|     private final SearchService searchServiceMock = mock(SearchService.class); | ||||
|     private final ResultSet resultSet = mock(ResultSet.class); | ||||
|     private HoldBulkServiceImpl holdBulkService; | ||||
|     private HoldBulkMonitor holdBulkMonitor; | ||||
|  | ||||
|     @Override | ||||
|     protected void initServices() | ||||
|     { | ||||
|         super.initServices(); | ||||
|         holdBulkMonitor = (HoldBulkMonitor) applicationContext.getBean("holdBulkMonitor"); | ||||
|         holdBulkService = (HoldBulkServiceImpl) applicationContext.getBean("holdBulkService"); | ||||
|         holdBulkService.setSearchService(searchServiceMock); | ||||
|         Mockito.when(searchServiceMock.query(any(SearchParameters.class))).thenReturn(resultSet); | ||||
|     } | ||||
|  | ||||
|     public void testCancelBulkOperation() | ||||
|     { | ||||
|         doBehaviourDrivenTest(new BehaviourDrivenTest() | ||||
|         { | ||||
|             private NodeRef hold; | ||||
|             private HoldBulkStatus holdBulkStatus; | ||||
|             private final ResultSet resultSet = mock(ResultSet.class); | ||||
|  | ||||
|             public void given() | ||||
|             { | ||||
|                 Mockito.when(resultSet.getNumberFound()).thenReturn(4L); | ||||
|                 Mockito.when(resultSet.hasMore()).thenReturn(false).thenReturn(true).thenReturn(false); | ||||
|                 Mockito.when(resultSet.getNodeRefs()) | ||||
|                     .thenAnswer((Answer<List<NodeRef>>) invocationOnMock -> { | ||||
|                         await().pollDelay(1, SECONDS).until(() -> true); | ||||
|                         return List.of(new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, GUID.generate()), | ||||
|                             new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, GUID.generate())); | ||||
|                     }); | ||||
|                 // create a hold | ||||
|                 hold = holdService.createHold(filePlan, GUID.generate(), GUID.generate(), GUID.generate()); | ||||
|             } | ||||
|  | ||||
|             public void when() | ||||
|             { | ||||
|                 BulkOperation bulkOperation = new BulkOperation(new Query("afts", "*", ""), "ADD"); | ||||
|                 // execute the bulk operation | ||||
|                 holdBulkStatus = holdBulkService.execute(hold, bulkOperation); | ||||
|                 // cancel the bulk operation | ||||
|                 holdBulkMonitor.cancelBulkOperation(holdBulkStatus.bulkStatusId(), | ||||
|                     new BulkCancellationRequest("No reason")); | ||||
|                 await().atMost(10, SECONDS) | ||||
|                     .until(() -> Objects.equals( | ||||
|                         holdBulkMonitor.getBulkStatus(holdBulkStatus.bulkStatusId()).getStatus(), | ||||
|                         Status.CANCELLED.getValue())); | ||||
|             } | ||||
|  | ||||
|             public void then() | ||||
|             { | ||||
|                 holdBulkStatus = holdBulkMonitor.getBulkStatus(holdBulkStatus.bulkStatusId()); | ||||
|                 assertNotNull(holdBulkStatus.startTime()); | ||||
|                 assertNotNull(holdBulkStatus.endTime()); | ||||
|                 assertEquals(holdBulkMonitor.getBulkStatus(holdBulkStatus.bulkStatusId()).getStatus(), | ||||
|                     HoldBulkStatus.Status.CANCELLED.getValue()); | ||||
|                 assertEquals(holdBulkMonitor.getBulkStatus(holdBulkStatus.bulkStatusId()).cancellationReason(), | ||||
|                     "No reason"); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     public void testAddRecordsToHoldViaBulk() | ||||
|     { | ||||
|         doBehaviourDrivenTest(new BehaviourDrivenTest() | ||||
|         { | ||||
|             private NodeRef hold; | ||||
|             private NodeRef recordFolder; | ||||
|             private HoldBulkStatus holdBulkStatus; | ||||
|             private final List<NodeRef> records = new ArrayList<>(RECORD_COUNT); | ||||
|  | ||||
|             public void given() | ||||
|             { | ||||
|                 Mockito.when(resultSet.getNumberFound()).thenReturn(Long.valueOf(RECORD_COUNT)); | ||||
|                 Mockito.when(resultSet.hasMore()).thenReturn(false).thenReturn(false); | ||||
|                 // create a hold | ||||
|                 hold = holdService.createHold(filePlan, GUID.generate(), GUID.generate(), GUID.generate()); | ||||
|  | ||||
|                 // create a record folder that contains records | ||||
|                 NodeRef recordCategory = filePlanService.createRecordCategory(filePlan, GUID.generate()); | ||||
|                 recordFolder = recordFolderService.createRecordFolder(recordCategory, GUID.generate()); | ||||
|                 for (int i = 0; i < RECORD_COUNT; i++) | ||||
|                 { | ||||
|                     records.add( | ||||
|                         recordService.createRecordFromContent(recordFolder, GUID.generate(), ContentModel.TYPE_CONTENT, | ||||
|                             null, null)); | ||||
|                 } | ||||
|                 Mockito.when(resultSet.getNodeRefs()).thenReturn(records).thenReturn(records) | ||||
|                     .thenReturn(Collections.emptyList()); | ||||
|  | ||||
|                 // assert current states | ||||
|                 assertFalse(freezeService.isFrozen(recordFolder)); | ||||
|                 assertFalse(freezeService.hasFrozenChildren(recordFolder)); | ||||
|                 for (NodeRef record : records) | ||||
|                 { | ||||
|                     assertFalse(freezeService.isFrozen(record)); | ||||
|                 } | ||||
|  | ||||
|                 // additional check for child held caching | ||||
|                 assertTrue(nodeService.hasAspect(recordFolder, ASPECT_HELD_CHILDREN)); | ||||
|                 assertEquals(0, nodeService.getProperty(recordFolder, PROP_HELD_CHILDREN_COUNT)); | ||||
|             } | ||||
|  | ||||
|             public void when() | ||||
|             { | ||||
|                 BulkOperation bulkOperation = new BulkOperation(new Query("afts", "*", ""), "ADD"); | ||||
|                 // execute the bulk operation | ||||
|                 holdBulkStatus = holdBulkService.execute(hold, bulkOperation); | ||||
|                 await().atMost(10, SECONDS) | ||||
|                     .until(() -> Objects.equals( | ||||
|                         holdBulkMonitor.getBulkStatus(holdBulkStatus.bulkStatusId()).getStatus(), | ||||
|                         Status.DONE.getValue())); | ||||
|             } | ||||
|  | ||||
|             public void then() | ||||
|             { | ||||
|                 holdBulkStatus = holdBulkMonitor.getBulkStatus(holdBulkStatus.bulkStatusId()); | ||||
|                 assertNotNull(holdBulkStatus.startTime()); | ||||
|                 assertNotNull(holdBulkStatus.endTime()); | ||||
|                 assertEquals(RECORD_COUNT, holdBulkStatus.totalItems()); | ||||
|                 assertEquals(RECORD_COUNT, holdBulkStatus.processedItems()); | ||||
|                 assertEquals(0, holdBulkStatus.errorsCount()); | ||||
|                 assertEquals(holdBulkMonitor.getBulkStatus(holdBulkStatus.bulkStatusId()).getStatus(), | ||||
|                     HoldBulkStatus.Status.DONE.getValue()); | ||||
|  | ||||
|                 // record is held | ||||
|                 for (NodeRef record : records) | ||||
|                 { | ||||
|                     assertTrue(freezeService.isFrozen(record)); | ||||
|                 } | ||||
|  | ||||
|                 // record folder has frozen children | ||||
|                 assertFalse(freezeService.isFrozen(recordFolder)); | ||||
|                 assertTrue(freezeService.hasFrozenChildren(recordFolder)); | ||||
|  | ||||
|                 // record folder is not held | ||||
|                 assertFalse(holdService.getHeld(hold).contains(recordFolder)); | ||||
|                 assertFalse(holdService.heldBy(recordFolder, true).contains(hold)); | ||||
|  | ||||
|                 for (NodeRef record : records) | ||||
|                 { | ||||
|                     // hold contains record | ||||
|                     assertTrue(holdService.getHeld(hold).contains(record)); | ||||
|                     assertTrue(holdService.heldBy(record, true).contains(hold)); | ||||
|                 } | ||||
|  | ||||
|                 // additional check for child held caching | ||||
|                 assertTrue(nodeService.hasAspect(recordFolder, ASPECT_HELD_CHILDREN)); | ||||
|                 assertEquals(RECORD_COUNT, nodeService.getProperty(recordFolder, PROP_HELD_CHILDREN_COUNT)); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     public void testAddRecordFolderToHoldViaBulk() | ||||
|     { | ||||
|         doBehaviourDrivenTest(new BehaviourDrivenTest() | ||||
|         { | ||||
|             private NodeRef hold; | ||||
|             private NodeRef recordFolder; | ||||
|             private final List<NodeRef> records = new ArrayList<>(RECORD_COUNT); | ||||
|             private HoldBulkStatus holdBulkStatus; | ||||
|  | ||||
|             public void given() | ||||
|             { | ||||
|                 Mockito.when(resultSet.getNumberFound()).thenReturn(1L); | ||||
|                 Mockito.when(resultSet.hasMore()).thenReturn(false).thenReturn(false); | ||||
|                 // create a hold | ||||
|                 hold = holdService.createHold(filePlan, GUID.generate(), GUID.generate(), GUID.generate()); | ||||
|  | ||||
|                 // create a record folder that contains records | ||||
|                 NodeRef recordCategory = filePlanService.createRecordCategory(filePlan, GUID.generate()); | ||||
|                 recordFolder = recordFolderService.createRecordFolder(recordCategory, GUID.generate()); | ||||
|                 for (int i = 0; i < RECORD_COUNT; i++) | ||||
|                 { | ||||
|                     records.add( | ||||
|                         recordService.createRecordFromContent(recordFolder, GUID.generate(), ContentModel.TYPE_CONTENT, | ||||
|                             null, null)); | ||||
|                 } | ||||
|                 Mockito.when(resultSet.getNodeRefs()).thenReturn(Collections.singletonList(recordFolder)) | ||||
|                     .thenReturn(Collections.singletonList(recordFolder)).thenReturn(Collections.emptyList()); | ||||
|  | ||||
|                 // assert current states | ||||
|                 assertFalse(freezeService.isFrozen(recordFolder)); | ||||
|                 assertFalse(freezeService.hasFrozenChildren(recordFolder)); | ||||
|                 for (NodeRef record : records) | ||||
|                 { | ||||
|                     assertFalse(freezeService.isFrozen(record)); | ||||
|                 } | ||||
|  | ||||
|                 // additional check for child held caching | ||||
|                 assertTrue(nodeService.hasAspect(recordFolder, ASPECT_HELD_CHILDREN)); | ||||
|                 assertEquals(0, nodeService.getProperty(recordFolder, PROP_HELD_CHILDREN_COUNT)); | ||||
|             } | ||||
|  | ||||
|             public void when() | ||||
|             { | ||||
|                 BulkOperation bulkOperation = new BulkOperation(new Query("afts", "*", ""), "ADD"); | ||||
|                 // execute the bulk operation | ||||
|                 holdBulkStatus = holdBulkService.execute(hold, bulkOperation); | ||||
|                 await().atMost(10, SECONDS) | ||||
|                     .until(() -> Objects.equals( | ||||
|                         holdBulkMonitor.getBulkStatus(holdBulkStatus.bulkStatusId()).getStatus(), | ||||
|                         Status.DONE.getValue())); | ||||
|             } | ||||
|  | ||||
|             public void then() | ||||
|             { | ||||
|                 holdBulkStatus = holdBulkMonitor.getBulkStatus(holdBulkStatus.bulkStatusId()); | ||||
|                 assertNotNull(holdBulkStatus.startTime()); | ||||
|                 assertNotNull(holdBulkStatus.endTime()); | ||||
|                 assertEquals(1, holdBulkStatus.totalItems()); | ||||
|                 assertEquals(1, holdBulkStatus.processedItems()); | ||||
|                 assertEquals(0, holdBulkStatus.errorsCount()); | ||||
|                 assertEquals(holdBulkMonitor.getBulkStatus(holdBulkStatus.bulkStatusId()).getStatus(), | ||||
|                     HoldBulkStatus.Status.DONE.getValue()); | ||||
|  | ||||
|                 for (NodeRef record : records) | ||||
|                 { | ||||
|                     // record is held | ||||
|                     assertTrue(freezeService.isFrozen(record)); | ||||
|                     assertFalse(holdService.getHeld(hold).contains(record)); | ||||
|                     assertTrue(holdService.heldBy(record, true).contains(hold)); | ||||
|                 } | ||||
|  | ||||
|                 // record folder has frozen children | ||||
|                 assertTrue(freezeService.isFrozen(recordFolder)); | ||||
|                 assertTrue(freezeService.hasFrozenChildren(recordFolder)); | ||||
|  | ||||
|                 // hold contains record folder | ||||
|                 assertTrue(holdService.getHeld(hold).contains(recordFolder)); | ||||
|                 assertTrue(holdService.heldBy(recordFolder, true).contains(hold)); | ||||
|  | ||||
|                 // additional check for child held caching | ||||
|                 assertTrue(nodeService.hasAspect(recordFolder, ASPECT_HELD_CHILDREN)); | ||||
|                 assertEquals(RECORD_COUNT, nodeService.getProperty(recordFolder, PROP_HELD_CHILDREN_COUNT)); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|     } | ||||
| } | ||||
| @@ -180,6 +180,7 @@ public class CreateRecordTest extends BaseRMTestCase | ||||
|                 Set<Capability> capabilities = new HashSet<>(2); | ||||
|                 capabilities.add(capabilityService.getCapability("ViewRecords")); | ||||
|                 capabilities.add(capabilityService.getCapability("CreateRecords")); | ||||
|                 capabilities.add(capabilityService.getCapability("CreateModifyDestroyFileplanMetadata")); | ||||
|                 filePlanRoleService.createRole(filePlan, roleName, roleName, capabilities); | ||||
|  | ||||
|  | ||||
| @@ -189,6 +190,7 @@ public class CreateRecordTest extends BaseRMTestCase | ||||
|  | ||||
|                 //give read and file permission to user on unfiled records container | ||||
|                 filePlanPermissionService.setPermission(unfiledContainer , user, RMPermissionModel.FILING); | ||||
|                 filePlanPermissionService.setPermission(unfiledContainer , user, RMPermissionModel.CREATE_MODIFY_DESTROY_FILEPLAN_METADATA); | ||||
|             } | ||||
|  | ||||
|             public void when() | ||||
|   | ||||
| @@ -51,6 +51,8 @@ import org.alfresco.module.org_alfresco_module_rm.job.publish.PublishExecutorReg | ||||
| import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; | ||||
| import org.alfresco.module.org_alfresco_module_rm.test.util.BaseRMTestCase; | ||||
| import org.alfresco.module.org_alfresco_module_rm.test.util.CommonRMTestUtils; | ||||
| import org.alfresco.rest.framework.core.exceptions.ConstraintViolatedException; | ||||
| import org.junit.Assert; | ||||
| import org.alfresco.service.cmr.repository.NodeRef; | ||||
| import org.alfresco.service.namespace.QName; | ||||
|  | ||||
| @@ -437,19 +439,12 @@ public class DispositionServiceImplTest extends BaseRMTestCase | ||||
|  | ||||
|             	// Check the disposition schedule | ||||
|             	checkDispositionSchedule(ds, "testCreateDispositionSchedule", "testCreateDispositionSchedule", false); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         // Failure: create disposition schedule on container with existing disposition schedule | ||||
|         doTestInTransaction(new FailureTest | ||||
|         ( | ||||
|         	"Can not create a disposition schedule on a container with an existing disposition schedule" | ||||
|         ) | ||||
|         { | ||||
|             @Override | ||||
|             public void run() | ||||
|             { | ||||
|             	utils.createBasicDispositionSchedule(rmContainer); | ||||
|                 // Failure: create disposition schedule on container with existing disposition schedule | ||||
|                 Assert.assertThrows(ConstraintViolatedException.class, | ||||
|                         () -> { | ||||
|                             utils.createBasicDispositionSchedule(rmContainer); | ||||
|                         }); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| @@ -492,19 +487,12 @@ public class DispositionServiceImplTest extends BaseRMTestCase | ||||
|             	// Check the disposition schedule | ||||
|             	checkDispositionSchedule(testA, "testA", "testA", false); | ||||
|             	checkDispositionSchedule(testB, "testB", "testB", false); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         // Failure: create disposition schedule on container with existing disposition schedule | ||||
|         doTestInTransaction(new FailureTest | ||||
|         ( | ||||
|         	"Can not create a disposition schedule on container with an existing disposition schedule" | ||||
|         ) | ||||
|         { | ||||
|             @Override | ||||
|             public void run() | ||||
|             { | ||||
|             	utils.createBasicDispositionSchedule(mhContainer11); | ||||
|                 // Failure: create disposition schedule on container with existing disposition schedule | ||||
|                 Assert.assertThrows(ConstraintViolatedException.class, | ||||
|                         () -> { | ||||
|                             utils.createBasicDispositionSchedule(rmContainer); | ||||
|                         }); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
|  | ||||
| # Version label | ||||
| version.major=23 | ||||
| version.minor=3 | ||||
| version.minor=4 | ||||
| version.revision=0 | ||||
| version.label= | ||||
|  | ||||
|   | ||||
| @@ -0,0 +1,164 @@ | ||||
| /* | ||||
|  * #%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 org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertNull; | ||||
| import static org.mockito.Mockito.mock; | ||||
| import static org.mockito.Mockito.when; | ||||
|  | ||||
| import java.util.Arrays; | ||||
| import java.util.Collections; | ||||
| import java.util.Date; | ||||
|  | ||||
| import org.alfresco.module.org_alfresco_module_rm.bulk.hold.DefaultHoldBulkMonitor; | ||||
| import org.alfresco.module.org_alfresco_module_rm.bulk.hold.HoldBulkProcessDetails; | ||||
| import org.alfresco.module.org_alfresco_module_rm.bulk.hold.HoldBulkStatus; | ||||
| import org.alfresco.module.org_alfresco_module_rm.bulk.hold.HoldBulkStatusAndProcessDetails; | ||||
| import org.alfresco.repo.cache.SimpleCache; | ||||
| import org.alfresco.service.cmr.repository.NodeRef; | ||||
| import org.alfresco.util.Pair; | ||||
| import org.junit.Before; | ||||
| import org.junit.Test; | ||||
| import org.mockito.Mock; | ||||
| import org.mockito.Mockito; | ||||
| import org.mockito.MockitoAnnotations; | ||||
|  | ||||
| public class DefaultHoldBulkMonitorUnitTest | ||||
| { | ||||
|  | ||||
|     @Mock | ||||
|     private SimpleCache<String, HoldBulkStatus> holdProgressCache; | ||||
|  | ||||
|     @Mock | ||||
|     private SimpleCache<Pair<String, String>, HoldBulkProcessDetails> holdProcessRegistry; | ||||
|  | ||||
|     private DefaultHoldBulkMonitor holdBulkMonitor; | ||||
|  | ||||
|     @Before | ||||
|     public void setUp() | ||||
|     { | ||||
|         MockitoAnnotations.openMocks(this); | ||||
|         holdBulkMonitor = new DefaultHoldBulkMonitor(); | ||||
|         holdBulkMonitor.setHoldProgressCache(holdProgressCache); | ||||
|         holdBulkMonitor.setHoldProcessRegistry(holdProcessRegistry); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     public void testUpdateBulkStatus() | ||||
|     { | ||||
|         HoldBulkStatus status = new HoldBulkStatus("bulkStatusId", null, null, 0L, 0L, 0L, null, false, null); | ||||
|  | ||||
|         holdBulkMonitor.updateBulkStatus(status); | ||||
|  | ||||
|         Mockito.verify(holdProgressCache).put("bulkStatusId", status); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     public void testRegisterProcess() | ||||
|     { | ||||
|         NodeRef holdRef = new NodeRef("workspace://SpacesStore/holdId"); | ||||
|         String processId = "processId"; | ||||
|         when(holdProcessRegistry.get(new Pair<>(holdRef.getId(), processId))).thenReturn(null); | ||||
|  | ||||
|         holdBulkMonitor.registerProcess(holdRef, processId, null); | ||||
|  | ||||
|         Mockito.verify(holdProcessRegistry) | ||||
|             .put(new Pair<>(holdRef.getId(), processId), new HoldBulkProcessDetails(processId, null, null)); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     public void testGetBulkStatusesWithProcessDetailsReturnsEmptyListWhenNoProcessesWithProcessDetails() | ||||
|     { | ||||
|         when(holdProcessRegistry.getKeys()).thenReturn(Collections.emptyList()); | ||||
|         assertEquals(Collections.emptyList(), holdBulkMonitor.getBulkStatusesWithProcessDetails("holdId")); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     public void testGetBulkStatus() | ||||
|     { | ||||
|         BulkOperation bulkOperation = mock(BulkOperation.class); | ||||
|         HoldBulkStatus status1 = new HoldBulkStatus("process1", new Date(1000), new Date(2000), 0L, 0L, 0L, null, false, | ||||
|             null); | ||||
|         when(holdProcessRegistry.get(new Pair<>("holdId", "process1"))).thenReturn( | ||||
|             new HoldBulkProcessDetails("process1", null, bulkOperation)); | ||||
|         when(holdProgressCache.get("process1")).thenReturn(status1); | ||||
|  | ||||
|         assertEquals(new HoldBulkStatusAndProcessDetails(status1, | ||||
|                 new HoldBulkProcessDetails(status1.bulkStatusId(), null, bulkOperation)), | ||||
|             holdBulkMonitor.getBulkStatusWithProcessDetails("holdId", "process1")); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     public void testGetNonExistingBulkStatus() | ||||
|     { | ||||
|         BulkOperation bulkOperation = mock(BulkOperation.class); | ||||
|         when(holdProcessRegistry.get(new Pair<>("holdId", "process1"))).thenReturn( | ||||
|             new HoldBulkProcessDetails("process1", null, bulkOperation)); | ||||
|         when(holdProgressCache.get("process1")).thenReturn(null); | ||||
|  | ||||
|         assertNull(holdBulkMonitor.getBulkStatusWithProcessDetails("holdId", "process1")); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     public void testGetBulkStatusesForHoldReturnsSortedStatusesWithProcessDetails() | ||||
|     { | ||||
|         BulkOperation bulkOperation = mock(BulkOperation.class); | ||||
|         HoldBulkStatus status1 = new HoldBulkStatus("process1", new Date(1000), new Date(2000), 0L, 0L, 0L, null, false, | ||||
|             null); | ||||
|         HoldBulkStatus status2 = new HoldBulkStatus("process2", new Date(3000), null, 0L, 0L, 0L, null, false, null); | ||||
|         HoldBulkStatus status3 = new HoldBulkStatus("process3", new Date(4000), null, 0L, 0L, 0L, null, false, null); | ||||
|         HoldBulkStatus status4 = new HoldBulkStatus("process4", new Date(500), new Date(800), 0L, 0L, 0L, null, false, | ||||
|             null); | ||||
|         HoldBulkStatus status5 = new HoldBulkStatus("process5", null, null, 0L, 0L, 0L, null, false, null); | ||||
|  | ||||
|         when(holdProcessRegistry.getKeys()).thenReturn( | ||||
|             Arrays.asList(new Pair<>("holdId", "process1"), new Pair<>("holdId", "process2"), | ||||
|                 new Pair<>("holdId", "process3"), new Pair<>("holdId", "process4"), new Pair<>("holdId", "process5")) | ||||
|                                                       ); | ||||
|         when(holdProcessRegistry.get(new Pair<>("holdId", "process1"))).thenReturn( | ||||
|             new HoldBulkProcessDetails("process1", null, bulkOperation)); | ||||
|         when(holdProcessRegistry.get(new Pair<>("holdId", "process2"))).thenReturn( | ||||
|             new HoldBulkProcessDetails("process2", null, bulkOperation)); | ||||
|         when(holdProcessRegistry.get(new Pair<>("holdId", "process3"))).thenReturn( | ||||
|             new HoldBulkProcessDetails("process3", null, bulkOperation)); | ||||
|         when(holdProcessRegistry.get(new Pair<>("holdId", "process4"))).thenReturn( | ||||
|             new HoldBulkProcessDetails("process4", null, bulkOperation)); | ||||
|         when(holdProcessRegistry.get(new Pair<>("holdId", "process5"))).thenReturn( | ||||
|             new HoldBulkProcessDetails("process5", null, bulkOperation)); | ||||
|         when(holdProgressCache.get("process1")).thenReturn(status1); | ||||
|         when(holdProgressCache.get("process2")).thenReturn(status2); | ||||
|         when(holdProgressCache.get("process3")).thenReturn(status3); | ||||
|         when(holdProgressCache.get("process4")).thenReturn(status4); | ||||
|         when(holdProgressCache.get("process5")).thenReturn(status5); | ||||
|  | ||||
|         assertEquals(Arrays.asList(status5, status3, status2, status1, status4).stream().map( | ||||
|                 status -> new HoldBulkStatusAndProcessDetails(status, | ||||
|                     new HoldBulkProcessDetails(status.bulkStatusId(), null, bulkOperation))).toList(), | ||||
|             holdBulkMonitor.getBulkStatusesWithProcessDetails("holdId")); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,126 @@ | ||||
| /* | ||||
|  * #%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.disposition; | ||||
|  | ||||
| import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; | ||||
| import org.alfresco.module.org_alfresco_module_rm.test.util.BaseUnitTest; | ||||
| import org.alfresco.rm.rest.api.impl.ApiNodesModelFactory; | ||||
| import org.alfresco.rm.rest.api.model.RetentionSchedule; | ||||
| import org.alfresco.rm.rest.api.model.RetentionScheduleActionDefinition; | ||||
| import org.alfresco.service.cmr.repository.ChildAssociationRef; | ||||
| import org.alfresco.service.cmr.repository.NodeRef; | ||||
| import org.alfresco.service.cmr.repository.Period; | ||||
| import org.junit.Test; | ||||
| import org.mockito.InjectMocks; | ||||
| import org.mockito.Mock; | ||||
|  | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.mockito.Mockito.when; | ||||
|  | ||||
| /** | ||||
|  * Retention schedule model unit test | ||||
|  */ | ||||
| public class RetentionScheduleModelUnitTest extends BaseUnitTest | ||||
| { | ||||
|     private static final String AUTHORITY = "authority"; | ||||
|     private static final String INSTRUCTIONS = "instructions"; | ||||
|     private static final String RETAIN_STEP = "retain"; | ||||
|  | ||||
|     @InjectMocks | ||||
|     private ApiNodesModelFactory apiNodesModelFactory; | ||||
|  | ||||
|     @Mock | ||||
|     DispositionSchedule dispositionSchedule; | ||||
|  | ||||
|     @Mock | ||||
|     DispositionActionDefinition dispositionActionDefinition; | ||||
|  | ||||
|     @Test | ||||
|     public void mapRetentionScheduleDataTest() | ||||
|     { | ||||
|         // Mock data | ||||
|         NodeRef nodeRef = generateNodeRef(RecordsManagementModel.TYPE_DISPOSITION_SCHEDULE, true); | ||||
|         ChildAssociationRef childAssociationRef = generateChildAssociationRef(filePlan, record); | ||||
|         when(dispositionSchedule.getDispositionAuthority()).thenReturn(AUTHORITY); | ||||
|         when(dispositionSchedule.getDispositionInstructions()).thenReturn(INSTRUCTIONS); | ||||
|         when(dispositionSchedule.getNodeRef()).thenReturn(nodeRef); | ||||
|         when(dispositionSchedule.isRecordLevelDisposition()).thenReturn(false); | ||||
|         when(mockedNodeService.getPrimaryParent(nodeRef)).thenReturn(childAssociationRef); | ||||
|         // Call the method | ||||
|         RetentionSchedule actualResult = apiNodesModelFactory.mapRetentionScheduleData(dispositionSchedule); | ||||
|  | ||||
|         //Expected Result | ||||
|         RetentionSchedule expectedResult = new RetentionSchedule(); | ||||
|         expectedResult.setId(nodeRef.getId()); | ||||
|         expectedResult.setParentId(filePlan.getId()); | ||||
|         expectedResult.setAuthority(AUTHORITY); | ||||
|         expectedResult.setInstructions(INSTRUCTIONS); | ||||
|  | ||||
|         // Assertions | ||||
|         assertEquals(expectedResult, actualResult); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     public void mapRetentionScheduleActionDefDataTest() | ||||
|     { | ||||
|         // Mock data | ||||
|         NodeRef nodeRef = generateNodeRef(RecordsManagementModel.TYPE_DISPOSITION_SCHEDULE, true); | ||||
|         String period = "month|10"; | ||||
|         ChildAssociationRef childAssociationRef = generateChildAssociationRef(filePlan, record); | ||||
|         when(dispositionActionDefinition.getNodeRef()).thenReturn(nodeRef); | ||||
|         when(dispositionActionDefinition.getName()).thenReturn(RETAIN_STEP); | ||||
|         when(dispositionActionDefinition.getDescription()).thenReturn("Description"); | ||||
|         when(dispositionActionDefinition.getIndex()).thenReturn(1); | ||||
|         when(dispositionActionDefinition.getGhostOnDestroy()).thenReturn("ghost"); | ||||
|         when(dispositionActionDefinition.getPeriod()).thenReturn(new Period(period)); | ||||
|         when(dispositionActionDefinition.getLocation()).thenReturn("location"); | ||||
|         when(dispositionActionDefinition.getId()).thenReturn(nodeRef.getId()); | ||||
|         when(mockedNodeService.getPrimaryParent(nodeRef)).thenReturn(childAssociationRef); | ||||
|         // Call the method | ||||
|         RetentionScheduleActionDefinition actualResult = apiNodesModelFactory.mapRetentionScheduleActionDefData(dispositionActionDefinition); | ||||
|  | ||||
|         //Expected Result | ||||
|         RetentionScheduleActionDefinition expectedResult = getRetentionScheduleActionDefinition(nodeRef); | ||||
|  | ||||
|         // Assertion | ||||
|         assertEquals(expectedResult, actualResult); | ||||
|     } | ||||
|  | ||||
|     private static RetentionScheduleActionDefinition getRetentionScheduleActionDefinition(NodeRef nodeRef) | ||||
|     { | ||||
|         RetentionScheduleActionDefinition expectedResult = new RetentionScheduleActionDefinition(); | ||||
|         expectedResult.setId(nodeRef.getId()); | ||||
|         expectedResult.setName(RETAIN_STEP); | ||||
|         expectedResult.setDescription("Description"); | ||||
|         expectedResult.setIndex(1); | ||||
|         expectedResult.setLocation("location"); | ||||
|         expectedResult.setPeriod("month"); | ||||
|         expectedResult.setPeriodAmount(10); | ||||
|         expectedResult.setRetainRecordMetadataAfterDestruction(true); | ||||
|         return expectedResult; | ||||
|     } | ||||
| } | ||||
| @@ -93,15 +93,15 @@ public class RMv33HoldAuditEntryValuesPatchUnitTest | ||||
|         verify(mockedRecordsManagementQueryDAO, times(1)).updatePropertyStringValueEntity(deleteHoldPropertyStringValueEntity); | ||||
|  | ||||
|         assertEquals("Add To Hold", addToHoldPropertyStringValueEntity.getStringValue()); | ||||
|         assertEquals("add to hold", addToHoldPropertyStringValueEntity.getStringEndLower()); | ||||
|         assertEquals("add to hold", addToHoldPropertyStringValueEntity.getStringLower()); | ||||
|         assertEquals(Long.valueOf(770_786_109L), addToHoldPropertyStringValueEntity.getStringCrc()); | ||||
|  | ||||
|         assertEquals("Remove From Hold", removeFromHoldPropertyStringValueEntity.getStringValue()); | ||||
|         assertEquals("remove from hold", removeFromHoldPropertyStringValueEntity.getStringEndLower()); | ||||
|         assertEquals("remove from hold", removeFromHoldPropertyStringValueEntity.getStringLower()); | ||||
|         assertEquals(Long.valueOf(2_967_613_012L), removeFromHoldPropertyStringValueEntity.getStringCrc()); | ||||
|  | ||||
|         assertEquals("Delete Hold", deleteHoldPropertyStringValueEntity.getStringValue()); | ||||
|         assertEquals("delete hold", deleteHoldPropertyStringValueEntity.getStringEndLower()); | ||||
|         assertEquals("delete hold", deleteHoldPropertyStringValueEntity.getStringLower()); | ||||
|         assertEquals(Long.valueOf(132_640_810L), deleteHoldPropertyStringValueEntity.getStringCrc()); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-governance-services-community-repo-parent</artifactId> | ||||
|         <version>23.3.0.20</version> | ||||
|         <version>23.4.0.16-SNAPSHOT</version> | ||||
|     </parent> | ||||
|  | ||||
|     <build> | ||||
|   | ||||
| @@ -40,6 +40,8 @@ tags: | ||||
|     description: Retrieve and manage unfiled record folders | ||||
|   - name: holds | ||||
|     description: Retrieve and manage holds | ||||
|   - name: retention-schedules | ||||
|     description: Perform retention schedule specific operations | ||||
|  | ||||
| paths: | ||||
|   ## GS sites | ||||
| @@ -2314,6 +2316,145 @@ paths: | ||||
|           description: Unexpected error | ||||
|           schema: | ||||
|             $ref: '#/definitions/Error' | ||||
|   '/holds/{holdId}/bulk-statuses': | ||||
|     get: | ||||
|       tags: | ||||
|         - holds | ||||
|       operationId: listBulkStatuses | ||||
|       summary: Get bulk statuses | ||||
|       description: | | ||||
|         Gets bulk statuses for hold with id **holdId**. | ||||
|       parameters: | ||||
|         - $ref: '#/parameters/holdIdParam' | ||||
|         - $ref: '#/parameters/skipCountParam' | ||||
|         - $ref: '#/parameters/maxItemsParam' | ||||
|       responses: | ||||
|         '200': | ||||
|           description: Successful response | ||||
|           schema: | ||||
|             $ref: '#/definitions/HoldBulkStatusPaging' | ||||
|         '400': | ||||
|           description: | | ||||
|             Invalid parameter: **holdId** is not a valid format | ||||
|         '401': | ||||
|           description: Authentication failed | ||||
|         '403': | ||||
|           description: Current user does not have permission to read **holdId** | ||||
|         '404': | ||||
|           description: "**holdId** does not exist" | ||||
|         default: | ||||
|           description: Unexpected error | ||||
|           schema: | ||||
|             $ref: '#/definitions/Error' | ||||
|   '/holds/{holdId}/bulk-statuses/{bulkStatusId}': | ||||
|     get: | ||||
|       tags: | ||||
|         - holds | ||||
|       operationId: getBulkStatus | ||||
|       summary: Get a bulk status | ||||
|       description: | | ||||
|         Gets a bulk status specified by **bulkStatusId** for **holdId**. | ||||
|       parameters: | ||||
|         - $ref: '#/parameters/holdIdParam' | ||||
|         - $ref: '#/parameters/bulkStatusId' | ||||
|       responses: | ||||
|         '200': | ||||
|           description: Successful response | ||||
|           schema: | ||||
|             $ref: '#/definitions/HoldBulkStatus' | ||||
|         '400': | ||||
|           description: | | ||||
|             Invalid parameter: **holdId** or **bulkStatusId** is not a valid format | ||||
|         '401': | ||||
|           description: Authentication failed | ||||
|         '403': | ||||
|           description: Current user does not have permission to read **holdId** | ||||
|         '404': | ||||
|           description: "**holdId** or **bulkStatusId** does not exist" | ||||
|         default: | ||||
|           description: Unexpected error | ||||
|           schema: | ||||
|             $ref: '#/definitions/Error' | ||||
|   '/holds/{holdId}/bulk-statuses/{bulkStatusId}/cancel': | ||||
|     post: | ||||
|       tags: | ||||
|         - holds | ||||
|       operationId: cancelBulkStatus | ||||
|       summary: Cancel the bulk operation | ||||
|       description: | | ||||
|         Cancels the bulk operation specified by **bulkStatusId** for **holdId**. | ||||
|       parameters: | ||||
|         - $ref: '#/parameters/holdIdParam' | ||||
|         - $ref: '#/parameters/bulkStatusId' | ||||
|         - in: body | ||||
|           name: cancelReason | ||||
|           description: Cancel reason. | ||||
|           required: false | ||||
|           schema: | ||||
|             $ref: '#/definitions/BulkBodyCancel' | ||||
|       responses: | ||||
|         '200': | ||||
|           description: Successful response | ||||
|         '400': | ||||
|           description: | | ||||
|             Invalid parameter: **holdId** or **bulkStatusId** is not a valid format | ||||
|         '401': | ||||
|           description: Authentication failed | ||||
|         '403': | ||||
|           description: Current user does not have permission to cancel the bulk process for **holdId** and **bulkStatusId** | ||||
|         '404': | ||||
|           description: "**holdId** or **bulkStatusId** does not exist" | ||||
|         default: | ||||
|           description: Unexpected error | ||||
|           schema: | ||||
|             $ref: '#/definitions/Error' | ||||
|   '/holds/{holdId}/bulk': | ||||
|     post: | ||||
|       tags: | ||||
|         - holds | ||||
|       operationId: startHoldBulkProcess | ||||
|       summary: Start the hold bulk process | ||||
|       description: | | ||||
|         Start the asynchronous bulk process for a hold with id **holdId** based on search query results. | ||||
|          | ||||
|         ```JSON | ||||
|         For example, the following JSON body starts the bulk process to add search query results | ||||
|         as children of a hold. | ||||
|          | ||||
|         { | ||||
|           "query": { | ||||
|             "query": "SITE:swsdp and TYPE:content", | ||||
|             "language": "afts" | ||||
|           }, | ||||
|           "op": "ADD" | ||||
|         } | ||||
|         ``` | ||||
|       parameters: | ||||
|         - $ref: '#/parameters/holdIdParam' | ||||
|         - in: body | ||||
|           name: holdBulkOperation | ||||
|           description: Bulk operation. | ||||
|           required: true | ||||
|           schema: | ||||
|             $ref: '#/definitions/HoldBulkOperation' | ||||
|       responses: | ||||
|         '202': | ||||
|           description: Successful response | ||||
|           schema: | ||||
|             $ref: '#/definitions/HoldBulkOperationEntry' | ||||
|         '400': | ||||
|           description: | | ||||
|             Invalid parameter: **holdId** is not a valid format or **HoldBulkOperation** is not valid | ||||
|         '401': | ||||
|           description: Authentication failed | ||||
|         '403': | ||||
|           description: Current user does not have permission to start the bulk process for **holdId** | ||||
|         '404': | ||||
|           description: "**holdId** does not exist" | ||||
|         default: | ||||
|           description: Unexpected error | ||||
|           schema: | ||||
|             $ref: '#/definitions/Error' | ||||
|   '/holds/{holdId}/delete': | ||||
|     post: | ||||
|       tags: | ||||
| @@ -2495,7 +2636,196 @@ paths: | ||||
|           description: Unexpected error | ||||
|           schema: | ||||
|             $ref: '#/definitions/Error' | ||||
|   ##retention-schedule | ||||
|   '/record-categories/{recordCategoryId}/retention-schedules': | ||||
|     post: | ||||
|       tags: | ||||
|         - retention-schedules | ||||
|       summary: Create a retention schedule | ||||
|       description: | | ||||
|         Create a retention schedule. | ||||
|          | ||||
|         For example, using the following JSON body will create a retention schedule: | ||||
|         ```JSON | ||||
|           { | ||||
|               "authority": "Retention Authority", | ||||
|               "instructions": "Retention Instructions", | ||||
|               "isRecordLevel": false | ||||
|           } | ||||
|         ``` | ||||
|  | ||||
|       operationId: createRetentionSchedule | ||||
|       parameters: | ||||
|         - $ref: '#/parameters/recordCategoryIdParam' | ||||
|         - in: body | ||||
|           name: retentionNodeBodyCreate | ||||
|           description: | | ||||
|             The retention schedule information to create. | ||||
|           schema: | ||||
|             $ref: '#/definitions/RetentionNodeBodyCreate' | ||||
|       consumes: | ||||
|         - application/json | ||||
|       produces: | ||||
|         - application/json | ||||
|       responses: | ||||
|         '201': | ||||
|           description: Successful response | ||||
|           schema: | ||||
|             $ref: '#/definitions/RetentionScheduleResponse' | ||||
|         '400': | ||||
|           description: | | ||||
|             Invalid parameter: value of recordCategoryId is invalid | ||||
|         '401': | ||||
|           description: Authentication failed | ||||
|         '403': | ||||
|           description: Current user does not have permission to create retention schedule | ||||
|         '404': | ||||
|           description: recordCategoryId does not exist | ||||
|         '409': | ||||
|           description: Retention schedule already exist for the given recordCategoryId | ||||
|         '422': | ||||
|           description: Record level retention schedule cannot be created for a record category having folder associated | ||||
|         default: | ||||
|           description: Unexpected error | ||||
|           schema: | ||||
|             $ref: '#/definitions/Error' | ||||
|     get: | ||||
|       tags: | ||||
|         - retention-schedules | ||||
|       summary: Get the retention schedule for a record category | ||||
|       description: | | ||||
|         Get the retention schedule for a record category. | ||||
|          | ||||
|         You can use the **include** parameter (include=actions) to return additional information. | ||||
|  | ||||
|       operationId: getRetentionScheduleList | ||||
|       parameters: | ||||
|         - $ref: '#/parameters/recordCategoryIdParam' | ||||
|         - $ref: '#/parameters/retentionScheduleIncludeParam' | ||||
|         - $ref: '#/parameters/skipCountParam' | ||||
|         - $ref: '#/parameters/maxItemsParam' | ||||
|       consumes: | ||||
|         - application/json | ||||
|       produces: | ||||
|         - application/json | ||||
|       responses: | ||||
|         '200': | ||||
|           description: Successful response | ||||
|           schema: | ||||
|             $ref: '#/definitions/RetentionScheduleResponseList' | ||||
|         '400': | ||||
|           description: | | ||||
|             Invalid parameter: value of recordCategoryId is invalid | ||||
|         '401': | ||||
|           description: Authentication failed | ||||
|         '403': | ||||
|           description: Current user does not have permission to get retention schedule | ||||
|         '404': | ||||
|           description: recordCategoryId does not exist | ||||
|         default: | ||||
|           description: Unexpected error | ||||
|           schema: | ||||
|             $ref: '#/definitions/Error' | ||||
|   '/retention-schedules/{retentionScheduleId}/retention-steps': | ||||
|     post: | ||||
|       tags: | ||||
|         - retention-schedules | ||||
|       summary: Create a step in the retention schedule | ||||
|       description: | | ||||
|         Create a step in the retention schedule. | ||||
|          | ||||
|         Order of steps: | ||||
|           * "**retain**" or "**cutoff**" should be first | ||||
|           * can't use "**cutoff**" after "**transfer**" or "**accession**" | ||||
|           * only the "**transfer**" action is allowed multiple times | ||||
|           * no steps are allowed after "**destroy**"     | ||||
|          | ||||
|         For example, the following JSON body will create a step in the retention schedule: | ||||
|         ```JSON | ||||
|           { | ||||
|               "name":"accession", | ||||
|               "description":"Step Description", | ||||
|               "periodAmount": 2, | ||||
|               "period":"month", | ||||
|               "periodProperty":"cm:created", | ||||
|               "combineRetentionStepConditions": false, | ||||
|               "events":["versioned"], | ||||
|               "eligibleOnFirstCompleteEvent": true               | ||||
|           } | ||||
|         ``` | ||||
|       operationId: createRetentionScheduleAction | ||||
|       parameters: | ||||
|         - $ref: '#/parameters/retentionScheduleIdParam' | ||||
|         - in: body | ||||
|           name: nodeBodyCreate | ||||
|           description: | | ||||
|             The retention schedule steps information to create. | ||||
|           required: true | ||||
|           schema: | ||||
|             $ref: '#/definitions/RetentionStepNodeBodyCreate' | ||||
|       consumes: | ||||
|         - application/json | ||||
|       produces: | ||||
|         - application/json | ||||
|       responses: | ||||
|         '201': | ||||
|           description: Successful response | ||||
|           schema: | ||||
|             $ref: '#/definitions/RetentionStepNodeBodyResponse' | ||||
|         '400': | ||||
|           description: | | ||||
|             Invalid parameter: value of retentionScheduleId is invalid | ||||
|             Invalid parameter (e.g. event, period, periodProperty) | ||||
|         '401': | ||||
|           description: Authentication failed | ||||
|         '403': | ||||
|           description: Current user does not have permission to create retention schedule step | ||||
|         '404': | ||||
|           description: retentionScheduleId does not exist | ||||
|         '409': | ||||
|           description: | | ||||
|             * Invalid Step - Can't use Cut Off after Transfer or Accession | ||||
|             * Invalid Step - Destroy action already completed. Can't do any other Action | ||||
|             * Invalid Step - This step already exists. You can’t create this step [Transfer action is allowed many times] | ||||
|         '422': | ||||
|           description: Cut Off or Retain should be the first step | ||||
|         default: | ||||
|           description: Unexpected error | ||||
|           schema: | ||||
|             $ref: '#/definitions/Error' | ||||
|     get: | ||||
|       tags: | ||||
|         - retention-schedules | ||||
|       summary: Get the list of steps in the retention schedule | ||||
|       description: | | ||||
|         Get the list of steps in the retention schedule. | ||||
|       operationId: getRetentionScheduleActionList | ||||
|       parameters: | ||||
|         - $ref: '#/parameters/retentionScheduleIdParam' | ||||
|         - $ref: '#/parameters/skipCountParam' | ||||
|         - $ref: '#/parameters/maxItemsParam' | ||||
|       consumes: | ||||
|         - application/json | ||||
|       produces: | ||||
|         - application/json | ||||
|       responses: | ||||
|         '200': | ||||
|           description: Successful response | ||||
|           schema: | ||||
|             $ref: '#/definitions/RetentionStepsNodeBodyResponse' | ||||
|         '400': | ||||
|           description: | | ||||
|             Invalid parameter: value of retentionScheduleId is invalid | ||||
|         '401': | ||||
|           description: Authentication failed | ||||
|         '403': | ||||
|           description: Current user does not have permission to get retention schedule steps | ||||
|         '404': | ||||
|           description: retentionScheduleId does not exist | ||||
|         default: | ||||
|           description: Unexpected error | ||||
|           schema: | ||||
|             $ref: '#/definitions/Error' | ||||
| parameters: | ||||
|   ## File plans | ||||
|   filePlanEntryIncludeParam: | ||||
| @@ -2862,6 +3192,12 @@ parameters: | ||||
|     description: The identifier of a child of a hold. | ||||
|     required: true | ||||
|     type: string | ||||
|   bulkStatusId: | ||||
|     name: bulkStatusId | ||||
|     in: path | ||||
|     description: The identifier of a bulk process. | ||||
|     required: true | ||||
|     type: string | ||||
|   ## Record | ||||
|   recordIdParam: | ||||
|     name: recordId | ||||
| @@ -2955,6 +3291,22 @@ parameters: | ||||
|       If true, then  a name clash will cause an attempt to auto rename by finding a unique name using an integer suffix. | ||||
|     required: false | ||||
|     type: boolean | ||||
|   ## RetentionSchedule | ||||
|   retentionScheduleIdParam: | ||||
|     name: retentionScheduleId | ||||
|     in: path | ||||
|     description: | ||||
|       The identifier of a retention schedule. | ||||
|     required: true | ||||
|     type: string | ||||
|   retentionScheduleIncludeParam: | ||||
|     name: include | ||||
|     in: query | ||||
|     description: | | ||||
|       Returns additional information about the retention schedule actions. Any optional field from the response model can be requested. For example: | ||||
|       * actions | ||||
|     required: false | ||||
|     type: string | ||||
| definitions: | ||||
|   FilePlanComponentBodyUpdate: | ||||
|     type: object | ||||
| @@ -4018,6 +4370,323 @@ definitions: | ||||
|     properties: | ||||
|       reason: | ||||
|         type: string | ||||
|   SearchRequestQuery: | ||||
|     type: object | ||||
|     required: | ||||
|       - query | ||||
|     properties: | ||||
|       language: | ||||
|         description: The query language in which the query is written. | ||||
|         type: string | ||||
|         default: afts | ||||
|         enum: | ||||
|           - afts | ||||
|           - lucene | ||||
|           - cmis | ||||
|       userQuery: | ||||
|         description: The search request typed in by the user | ||||
|         type: string | ||||
|       query: | ||||
|         description: The query which may have been generated in some way from the userQuery | ||||
|         type: string | ||||
|   HoldBulkOperation: | ||||
|     type: object | ||||
|     properties: | ||||
|       query: | ||||
|         $ref: '#/definitions/SearchRequestQuery' | ||||
|       op: | ||||
|         description: The operation type. | ||||
|         type: string | ||||
|         default: ADD | ||||
|         enum: | ||||
|           - ADD | ||||
|   HoldBulkOperationEntry: | ||||
|     type: object | ||||
|     properties: | ||||
|       bulkStatusId: | ||||
|         type: string | ||||
|       totalItems: | ||||
|         type: integer | ||||
|         format: int64 | ||||
|   BulkBodyCancel: | ||||
|     type: object | ||||
|     properties: | ||||
|       reason: | ||||
|         type: string | ||||
|   HoldBulkStatus: | ||||
|     type: object | ||||
|     properties: | ||||
|       bulkStatusId: | ||||
|         type: string | ||||
|       startTime: | ||||
|         type: string | ||||
|         format: date-time | ||||
|       endTime: | ||||
|         type: string | ||||
|         format: date-time | ||||
|       processedItems: | ||||
|         type: integer | ||||
|         format: int64 | ||||
|       errorsCount: | ||||
|         type: integer | ||||
|         format: int64 | ||||
|       totalItems: | ||||
|         type: integer | ||||
|         format: int64 | ||||
|       lastError: | ||||
|         type: string | ||||
|       status: | ||||
|         type: string | ||||
|         enum: | ||||
|           - PENDING | ||||
|           - IN PROGRESS | ||||
|           - DONE | ||||
|           - CANCELLED | ||||
|       cancellationReason: | ||||
|         type: string | ||||
|       holdBulkOperation: | ||||
|         $ref: '#/definitions/HoldBulkOperation' | ||||
|   HoldBulkStatusEntry: | ||||
|     type: object | ||||
|     required: | ||||
|       - entry | ||||
|     properties: | ||||
|       entry: | ||||
|         $ref: '#/definitions/HoldBulkStatus' | ||||
|   HoldBulkStatusPaging: | ||||
|     type: object | ||||
|     properties: | ||||
|       list: | ||||
|         type: object | ||||
|         properties: | ||||
|           pagination: | ||||
|             $ref: '#/definitions/Pagination' | ||||
|           entries: | ||||
|             type: array | ||||
|             items: | ||||
|               $ref: '#/definitions/HoldBulkStatusEntry' | ||||
|   RetentionNodeBodyCreate: | ||||
|     type: object | ||||
|     properties: | ||||
|       authority: | ||||
|         type: string | ||||
|         description: | | ||||
|           Authority name for the retention schedule. | ||||
|       instructions: | ||||
|         type: string | ||||
|         description: | | ||||
|           Required instructions for the retention schedule. | ||||
|       isRecordLevel: | ||||
|         type: boolean | ||||
|         default: false | ||||
|         description: | | ||||
|           This field is used to specify whether the retention schedule needs to be applied in the folder level or record level. | ||||
|           True will cause the the retention schedule to apply to records and false will cause the retention schedule to apply to record folders. | ||||
|           This cannot be changed once items start being managed by the retention schedule. | ||||
|   RetentionScheduleResponse: | ||||
|     type: object | ||||
|     properties: | ||||
|       id: | ||||
|         type: string | ||||
|       parentId: | ||||
|         type: string | ||||
|       authority: | ||||
|         type: string | ||||
|       instructions: | ||||
|         type: string | ||||
|       isRecordLevel: | ||||
|         type: boolean | ||||
|       unpublishedUpdates: | ||||
|         type: boolean | ||||
|   RetentionScheduleResponseList: | ||||
|     type: object | ||||
|     properties: | ||||
|       list: | ||||
|         type: object | ||||
|         properties: | ||||
|           pagination: | ||||
|             $ref: '#/definitions/Pagination' | ||||
|           entries: | ||||
|             type: array | ||||
|             items: | ||||
|               $ref: '#/definitions/FullRetentionScheduleResponse' | ||||
|   FullRetentionScheduleResponse: | ||||
|     type: object | ||||
|     properties: | ||||
|       id: | ||||
|         type: string | ||||
|       parentId: | ||||
|         type: string | ||||
|       authority: | ||||
|         type: string | ||||
|       instructions: | ||||
|         type: string | ||||
|       isRecordLevel: | ||||
|         type: boolean | ||||
|       unpublishedUpdates: | ||||
|         type: boolean | ||||
|       actions: | ||||
|         type: array | ||||
|         items: | ||||
|           $ref: '#/definitions/Actions' | ||||
|   Actions: | ||||
|     type: object | ||||
|     properties: | ||||
|       id: | ||||
|         type: string | ||||
|       name: | ||||
|         type: string | ||||
|       description: | ||||
|         type: string | ||||
|       periodAmount: | ||||
|         type: integer | ||||
|       period: | ||||
|         type: string | ||||
|       periodProperty: | ||||
|         type: string | ||||
|       combineRetentionStepConditions: | ||||
|         type: boolean | ||||
|         default: false | ||||
|       eligibleOnFirstCompleteEvent: | ||||
|         type: boolean | ||||
|         default: true | ||||
|       retainRecordMetadataAfterDestruction: | ||||
|         type: boolean | ||||
|       location: | ||||
|         type: string | ||||
|       events: | ||||
|         type: array | ||||
|         items: | ||||
|           type: string | ||||
|       index: | ||||
|         type: integer | ||||
|   RetentionStepNodeBodyCreate: | ||||
|     type: object | ||||
|     required: | ||||
|       - name | ||||
|       - description | ||||
|     properties: | ||||
|       name: | ||||
|         type: string | ||||
|         description: | | ||||
|           The valid names are: | ||||
|           * retain | ||||
|           * cutoff | ||||
|           * accession | ||||
|           * transfer | ||||
|           * destroyContent | ||||
|           * destroyNode | ||||
|            | ||||
|           destroyNode step can be used to destroy content along with record metadata.  | ||||
|           In case, record metadata needs to be retained, then destroyContent step should be used. | ||||
|       description: | ||||
|         type: string | ||||
|         description: | | ||||
|           This property is used to provide the step description. | ||||
|       periodAmount: | ||||
|         type: integer | ||||
|         description: | | ||||
|           This property is only applicable for the following period values. | ||||
|           * day | ||||
|           * month | ||||
|           * quarter | ||||
|           * week | ||||
|           * duration | ||||
|           * year | ||||
|       period: | ||||
|         type: string | ||||
|         description: | | ||||
|           Valid values for the period. | ||||
|           * day = Day | ||||
|           * fmend = End Of Financial Month | ||||
|           * fqend = End Of Financial Quarter | ||||
|           * fyend = End Of Financial Year | ||||
|           * immediately = Immediately | ||||
|           * monthend = End Of Month | ||||
|           * quarterend = End Of Quarter | ||||
|           * yearend = End Of Year | ||||
|           * month = Month | ||||
|           * none = None | ||||
|           * quarter = Quarter | ||||
|           * week = Week | ||||
|           * duration = XML Duration | ||||
|           * year = Year | ||||
|            | ||||
|           If you provide XML Duration for the period value, you need to specify a time interval using XML syntax. | ||||
|           The syntax should take the form of: | ||||
|           P = Period (required) | ||||
|           nY = Number of years | ||||
|           nM = Number of months | ||||
|           nD = Number of days | ||||
|           T = Start time of a time section (required if specifying hours, minutes, or seconds) | ||||
|           nH = Number of hours | ||||
|           nM = Number of minutes | ||||
|           nS = Number of seconds | ||||
|           For example, ‘P2M10D’ represents two months and ten days. | ||||
|       periodProperty: | ||||
|         type: string | ||||
|         default: cm:created | ||||
|         description: | | ||||
|           Valid values for the periodProperty property | ||||
|           * cm:created = Created Date (defult value)  | ||||
|           * rma:cutOffDate = Cut Off Date | ||||
|           * rma:dispositionAsOf = Retention Action | ||||
|       combineRetentionStepConditions: | ||||
|         type: boolean | ||||
|         description: | | ||||
|           This property is only valid for **accession** step. | ||||
|           This is used to specify whether to combine the period condition and events for the step execution or only consider one of them. | ||||
|           For example: | ||||
|           **periodCondition**: After a period of 2 months | ||||
|           **eventsCondition**: Case Closed event | ||||
|           This flag can be used to consider only (**periodCondition** or **eventsCondition**) or both of them at once. | ||||
|       events: | ||||
|         type: array | ||||
|         items: | ||||
|           type: string | ||||
|         description: | | ||||
|           Valid values for the events property | ||||
|           * case_closed = Case Closed | ||||
|           * abolished = Abolished | ||||
|           * re_designated = Redesignated | ||||
|           * no_longer_needed = No Longer Needed | ||||
|           * superseded = Superseded | ||||
|           * versioned = Versioned | ||||
|           * study_complete = Study Complete | ||||
|           * training_complete = Training Complete | ||||
|           * related_record_trasfered_inactive_storage = Related Record Transferred to Inactive Storage | ||||
|           * obsolete = Obsolete | ||||
|           * all_allowances_granted_are_terminated = All Allowances Granted are Terminated | ||||
|           * WGI_action_complete = WGI Action Complete | ||||
|           * separation = Separation | ||||
|           * case_complete = Case Complete | ||||
|           * declassification_review = Declassification Review | ||||
|       eligibleOnFirstCompleteEvent: | ||||
|         type: boolean | ||||
|         description: | | ||||
|           * false = When all events have happened | ||||
|           * true = Whichever event is earlier | ||||
|       location: | ||||
|         type: string | ||||
|         description: | | ||||
|           This property is only valid for transfer step | ||||
|   RetentionStepNodeBodyResponse: | ||||
|     type: object | ||||
|     properties: | ||||
|       actions: | ||||
|         $ref: '#/definitions/Actions' | ||||
|   RetentionStepsNodeBodyResponse: | ||||
|     type: object | ||||
|     properties: | ||||
|       list: | ||||
|         type: object | ||||
|         properties: | ||||
|           pagination: | ||||
|             $ref: '#/definitions/Pagination' | ||||
|           entries: | ||||
|             type: array | ||||
|             items: | ||||
|               $ref: '#/definitions/RetentionStepNodeBodyResponse' | ||||
|   ## | ||||
|   RequestBodyFile: | ||||
|     type: object | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo</artifactId> | ||||
|         <version>23.3.0.20</version> | ||||
|         <version>23.4.0.16-SNAPSHOT</version> | ||||
|     </parent> | ||||
|  | ||||
|     <modules> | ||||
|   | ||||
| @@ -8,7 +8,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-amps</artifactId> | ||||
|         <version>23.3.0.20</version> | ||||
|         <version>23.4.0.16-SNAPSHOT</version> | ||||
|     </parent> | ||||
|  | ||||
|     <properties> | ||||
|   | ||||
							
								
								
									
										10
									
								
								core/pom.xml
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								core/pom.xml
									
									
									
									
									
								
							| @@ -7,7 +7,7 @@ | ||||
|    <parent> | ||||
|       <groupId>org.alfresco</groupId> | ||||
|       <artifactId>alfresco-community-repo</artifactId> | ||||
|       <version>23.3.0.20</version> | ||||
|       <version>23.4.0.16-SNAPSHOT</version> | ||||
|    </parent> | ||||
|  | ||||
|    <dependencies> | ||||
| @@ -47,11 +47,11 @@ | ||||
|          <artifactId>commons-math3</artifactId> | ||||
|          <version>3.6.1</version> | ||||
|       </dependency> | ||||
|       <!-- https://mvnrepository.com/artifact/com.fasterxml.uuid/java-uuid-generator --> | ||||
|       <dependency> | ||||
|          <groupId>org.safehaus.jug</groupId> | ||||
|          <artifactId>jug</artifactId> | ||||
|          <version>2.0.0</version> | ||||
|          <classifier>asl</classifier> | ||||
|          <groupId>com.fasterxml.uuid</groupId> | ||||
|          <artifactId>java-uuid-generator</artifactId> | ||||
|          <version>5.1.0</version> | ||||
|       </dependency> | ||||
|       <dependency> | ||||
|          <groupId>org.apache.logging.log4j</groupId> | ||||
|   | ||||
| @@ -21,7 +21,7 @@ package org.alfresco.util; | ||||
| import java.security.SecureRandom; | ||||
| import java.util.Random; | ||||
|  | ||||
| import org.safehaus.uuid.UUIDGenerator; | ||||
| import com.fasterxml.uuid.Generators; | ||||
| import org.alfresco.api.AlfrescoPublicApi; | ||||
|  | ||||
| /** | ||||
| @@ -69,7 +69,7 @@ public final class GUID | ||||
|     public static String generate() | ||||
|     { | ||||
|         int randomInt = RANDOM.nextInt(SECURE_RANDOM_POOL_MAX_ITEMS); | ||||
|         return UUIDGenerator.getInstance().generateRandomBasedUUID(SECURE_RANDOM_POOL[randomInt]).toString(); | ||||
|         return Generators.randomBasedGenerator(SECURE_RANDOM_POOL[randomInt]).generate().toString(); | ||||
|     } | ||||
|  | ||||
| // == Not sure if we need this functionality again (derekh) == | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
|  * Copyright (C) 2005-2023 Alfresco Software Limited. | ||||
|  * Copyright (C) 2005-2024 Alfresco Software Limited. | ||||
|  * | ||||
|  * This file is part of Alfresco | ||||
|  * | ||||
| @@ -67,4 +67,14 @@ public interface TransactionListener | ||||
|      * be used only for cleaning up resources after a rollback has occurred. | ||||
|      */ | ||||
|     void afterRollback(); | ||||
|  | ||||
|     /** | ||||
|      * Allows to provide a custom listener's order. | ||||
|      * See {@link org.alfresco.repo.transaction.AlfrescoTransactionSupport#COMMIT_ORDER_NORMAL} | ||||
|      * @return custom order or null for the default one | ||||
|      */ | ||||
|     default Integer getCustomOrder() | ||||
|     { | ||||
|         return null; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo</artifactId> | ||||
|         <version>23.3.0.20</version> | ||||
|         <version>23.4.0.16-SNAPSHOT</version> | ||||
|     </parent> | ||||
|  | ||||
|     <properties> | ||||
| @@ -132,7 +132,7 @@ | ||||
|         <dependency> | ||||
|             <groupId>com.fasterxml.woodstox</groupId> | ||||
|             <artifactId>woodstox-core</artifactId> | ||||
|             <version>6.5.1</version> | ||||
|             <version>7.0.0</version> | ||||
|         </dependency> | ||||
|  | ||||
|         <!-- the cxf libs were updated, see dependencyManagement section --> | ||||
|   | ||||
| @@ -33,13 +33,13 @@ import java.util.HashSet; | ||||
| import java.util.Set; | ||||
| import java.util.zip.CRC32; | ||||
|  | ||||
| import com.fasterxml.uuid.Generators; | ||||
| import org.alfresco.repo.cache.SimpleCache; | ||||
| import org.alfresco.service.cmr.repository.datatype.Duration; | ||||
| import org.alfresco.util.GUID; | ||||
| import org.apache.commons.codec.binary.Hex; | ||||
| import org.apache.commons.logging.Log; | ||||
| import org.apache.commons.logging.LogFactory; | ||||
| import org.safehaus.uuid.UUIDGenerator; | ||||
| import org.alfresco.util.ParameterCheck; | ||||
|  | ||||
| /** | ||||
| @@ -497,7 +497,7 @@ public class InMemoryTicketComponentImpl implements TicketComponent | ||||
|             this.userName = userName; | ||||
|             this.validDuration = validDuration; | ||||
|             this.testDuration = validDuration.divide(2); | ||||
|             final String guid = UUIDGenerator.getInstance().generateRandomBasedUUID().toString(); | ||||
|             final String guid = Generators.randomBasedGenerator().generate().toString(); | ||||
|  | ||||
|             this.ticketId = computeTicketId(expires, expiryDate, userName, guid); | ||||
|  | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
|  * #%L | ||||
|  * Alfresco Data model classes | ||||
|  * %% | ||||
|  * Copyright (C) 2005 - 2016 Alfresco Software Limited | ||||
|  * 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  | ||||
| @@ -78,6 +78,28 @@ public interface PermissionService | ||||
|      */ | ||||
|     public static final String GUEST_AUTHORITY = "ROLE_GUEST"; | ||||
|  | ||||
|     /** | ||||
|      * The dynamic authority for the Admin service account. | ||||
|      */ | ||||
|     String ADMIN_SVC_AUTHORITY = "ROLE_ADMIN_SERVICE_ACCOUNT"; | ||||
|  | ||||
|     /** | ||||
|      * The dynamic authority for the Collaborator service account. | ||||
|      */ | ||||
|     String COLLABORATOR_SVC_AUTHORITY = "ROLE_COLLABORATOR_SERVICE_ACCOUNT"; | ||||
|  | ||||
|     /** | ||||
|      * The dynamic authority for the Editor service account. | ||||
|      */ | ||||
|     String EDITOR_SVC_AUTHORITY = "ROLE_EDITOR_SERVICE_ACCOUNT"; | ||||
|  | ||||
|     /** | ||||
|      * A convenient set of service account authorities to simplify checks | ||||
|      * for whether a given authority is a service account authority or not. | ||||
|      */ | ||||
|     Set<String> SVC_AUTHORITIES_SET = Set.of(ADMIN_SVC_AUTHORITY, COLLABORATOR_SVC_AUTHORITY, | ||||
|                                              EDITOR_SVC_AUTHORITY); | ||||
|  | ||||
|     /** | ||||
|      * The permission for all - not defined in the model. Repsected in the code. | ||||
|      */ | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo</artifactId> | ||||
|         <version>23.3.0.20</version> | ||||
|         <version>23.4.0.16-SNAPSHOT</version> | ||||
|     </parent> | ||||
|  | ||||
|     <dependencies> | ||||
| @@ -84,7 +84,7 @@ | ||||
|                             <include>org.alfresco:alfresco-core</include> | ||||
|                             <include>org.alfresco:alfresco-repository</include> | ||||
|                             <include>org.apache.commons:commons-compress</include> | ||||
|                             <include>org.safehaus.jug:jug</include> | ||||
|                             <include>com.fasterxml.uuid:java-uuid-generator</include> | ||||
|                             <include>org.alfresco.surf:spring-surf-core</include> | ||||
|                             <include>org.tukaani:xz</include> | ||||
|                             <include>org.apache.maven:maven-artifact</include> | ||||
|   | ||||
| @@ -25,6 +25,7 @@ | ||||
|  */ | ||||
| package org.alfresco.repo.module.tool; | ||||
|  | ||||
| import com.fasterxml.uuid.Generators; | ||||
| import de.schlichtherle.truezip.file.*; | ||||
| import de.schlichtherle.truezip.fs.FsSyncException; | ||||
| import de.schlichtherle.truezip.fs.archive.zip.JarDriver; | ||||
| @@ -34,7 +35,6 @@ import org.alfresco.error.AlfrescoRuntimeException; | ||||
| import org.alfresco.repo.module.ModuleVersionNumber; | ||||
| import org.alfresco.service.cmr.module.ModuleDetails; | ||||
| import org.alfresco.service.cmr.module.ModuleInstallState; | ||||
| import org.safehaus.uuid.UUIDGenerator; | ||||
|  | ||||
| import java.io.BufferedInputStream; | ||||
| import java.io.IOException; | ||||
| @@ -916,7 +916,7 @@ public class ModuleManagementTool implements LogOutput | ||||
|      */ | ||||
|     private static String generateGuid() | ||||
|     { | ||||
|         return UUIDGenerator.getInstance().generateTimeBasedUUID().toString(); | ||||
|         return Generators.timeBasedGenerator().generate().toString(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -9,6 +9,6 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-packaging</artifactId> | ||||
|         <version>23.3.0.20</version> | ||||
|         <version>23.4.0.16-SNAPSHOT</version> | ||||
|     </parent> | ||||
| </project> | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| # Fetch image based on Tomcat 9.0, Java 17 and Rocky Linux 8 | ||||
| # More infos about this image: https://github.com/Alfresco/alfresco-docker-base-tomcat | ||||
| FROM alfresco/alfresco-base-tomcat:tomcat10-jre17-rockylinux8-202306291245 | ||||
| FROM alfresco/alfresco-base-tomcat:tomcat10-jre17-rockylinux9@sha256:395664f9d9be0c9f73d3b722a58fd559ee7231609b263dfe19502617652740e3 | ||||
|  | ||||
| # Set default docker_context. | ||||
| ARG resource_path=target | ||||
| @@ -14,6 +13,9 @@ ARG USERID=33000 | ||||
| # Set default environment args | ||||
| ARG TOMCAT_DIR=/usr/local/tomcat | ||||
|  | ||||
| # Needed for installation but make sure another USER directive is added after | ||||
| # this with a non-root user | ||||
| USER root | ||||
|  | ||||
| # Create prerequisite to store tools and properties | ||||
| RUN mkdir -p ${TOMCAT_DIR}/shared/classes/alfresco/extension/mimetypes && \ | ||||
| @@ -61,13 +63,7 @@ RUN sed -i -e "s_appender.rolling.fileName\=alfresco.log_appender.rolling.fileNa | ||||
|     sed -i -e "\$a\grant\ codeBase\ \"file:\$\{catalina.base\}\/webapps\/alfresco\/-\" \{\n\    permission\ java.security.AllPermission\;\n\};\ngrant\ codeBase\ \"file:\$\{catalina.base\}\/webapps\/_vti_bin\/-\" \{\n\    permission\ java.security.AllPermission\;\n\};\ngrant\ codeBase\ \"file:\$\{catalina.base\}\/webapps\/ROOT\/-\" \{\n\    permission org.apache.catalina.security.DeployXmlPermission \"ROOT\";\n\};" ${TOMCAT_DIR}/conf/catalina.policy | ||||
|  | ||||
| # fontconfig is required by Activiti worflow diagram generator | ||||
| # installing pinned dependencies as well | ||||
| RUN yum install -y fontconfig-2.13.1-4.el8 \ | ||||
|                    dejavu-fonts-common-2.35-7.el8 \ | ||||
|                    fontpackages-filesystem-1.44-22.el8 \ | ||||
|                    freetype-2.9.1-9.el8 \ | ||||
|                    libpng-1.6.34-5.el8 \ | ||||
|                    dejavu-sans-fonts-2.35-7.el8 && \ | ||||
| RUN yum install -y fontconfig-2.14.0-2.el9_1 && \ | ||||
|     yum clean all | ||||
|  | ||||
| # The standard configuration is to have all Tomcat files owned by root with group GROUPNAME and whilst owner has read/write privileges,  | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-packaging</artifactId> | ||||
|         <version>23.3.0.20</version> | ||||
|         <version>23.4.0.16-SNAPSHOT</version> | ||||
|     </parent> | ||||
|  | ||||
|     <properties> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo</artifactId> | ||||
|         <version>23.3.0.20</version> | ||||
|         <version>23.4.0.16-SNAPSHOT</version> | ||||
|     </parent> | ||||
|  | ||||
|     <modules> | ||||
|   | ||||
| @@ -1,3 +1,3 @@ | ||||
| SOLR6_TAG=2.0.10-A1 | ||||
| SOLR6_TAG=2.0.11 | ||||
| POSTGRES_TAG=15.4 | ||||
| ACTIVEMQ_TAG=5.18.3-jre17-rockylinux8 | ||||
|   | ||||
| @@ -6,7 +6,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-packaging</artifactId> | ||||
|         <version>23.3.0.20</version> | ||||
|         <version>23.4.0.16-SNAPSHOT</version> | ||||
|     </parent> | ||||
|  | ||||
|     <modules> | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user