Compare commits

...

254 Commits

Author SHA1 Message Date
Travis CI User
89522ba09d [maven-release-plugin][skip ci] prepare release 17.155 2022-10-10 09:13:32 +00:00
Tom Page
86761bd408 Merge pull request #1478 from Alfresco/feature/ACS-3615_AllowNullParams
ACS-3615 Allow actions in rules without any params.
2022-10-10 09:29:29 +01:00
Maciej Pichura
4d6b4c2ecf ACS-3572 V1 API for action constraints (#1469)
* ACS-3572: Action constraints endpoint + logic + unit tests.

* ACS-3572: Action constraints endpoint + logic + unit tests.

* ACS-3572: Action constraints endpoint fixes.

* ACS-3572: Cleanup after removal of GET all Action constraints.

* ACS-3572: Fixing formatting in tests.

* ACS-3572: Loosening V1 constraints restrictions for aspects, types and properties.

* ACS-3572: Removing unnecessary code.

* ACS-3572: Removing unnecessary field.

* ACS-3572: Removing unnecessary field - fixing the tests.

* ACS-3572: Renaming the endpoint and fixing license headers.

* ACS-3572: Fixing license headers.

* ACS-3572: Fixing tas-restapi dependency version.

* ACS-3649: Bumping tes-restapi version.

* ACS-3649: Bumping tes-restapi version.
2022-10-10 10:14:47 +02:00
Travis CI User
98cb7762c4 [maven-release-plugin][skip ci] prepare for next development iteration 2022-10-09 09:44:47 +00:00
Travis CI User
ee01595f31 [maven-release-plugin][skip ci] prepare release 17.154 2022-10-09 09:44:44 +00:00
Elia Porciani
75acf5110e Fix/acs 2699 mandatory modifier (#1472)
[ACS-2699] managed + and - modifiers in fts query parser
2022-10-09 10:47:18 +02:00
Travis CI User
3a5971e267 [maven-release-plugin][skip ci] prepare for next development iteration 2022-10-09 00:11:41 +00:00
Travis CI User
99ef570054 [maven-release-plugin][skip ci] prepare release 17.153 2022-10-09 00:11:39 +00:00
Alfresco CI User
9649324d33 [force] Force release for 2022-10-09. 2022-10-09 00:04:36 +00:00
Tom Page
ad96f85251 ACS-3615 Allow actions in rules without any params. 2022-10-07 16:37:08 +01:00
krdabrowski
7ac48f8a99 ACS-3687: Execute rules API: Remove field "isEachInheritedRuleExecuted" (#1476)
* ACS-3620: E2Es - API for manual triggering rules on a folder
2022-10-07 17:01:08 +02:00
Travis CI User
aa38bcf9df [maven-release-plugin][skip ci] prepare for next development iteration 2022-10-07 14:27:33 +00:00
Travis CI User
86d1d36ffe [maven-release-plugin][skip ci] prepare release 17.152 2022-10-07 14:27:31 +00:00
krdabrowski
b557a0d0f1 ACS-3620: E2Es - API for manual triggering rules on a folder (#1471) 2022-10-07 15:51:48 +02:00
Tom Page
1f81a50677 Merge pull request #1474 from Alfresco/feature/ACS-3657_PartialListOfRuleSets
ACS-3657 Allow returning partial list of rule sets.
2022-10-07 14:38:39 +01:00
Tom Page
855e2522f2 ACS-3657 Allow returning partial list of rule sets.
If a user does not have access to a rule set applied to a node then it will be
excluded from the results, but the user will be able to see the list of other
rule sets.

Also add E2E tests for permissions when viewing rule sets.
2022-10-07 13:03:37 +01:00
Travis CI User
f342b27c74 [maven-release-plugin][skip ci] prepare for next development iteration 2022-10-07 11:29:09 +00:00
Travis CI User
b00f01d385 [maven-release-plugin][skip ci] prepare release 17.151 2022-10-07 11:29:06 +00:00
rrajoria
ce68c4bf8b Reverting Change 2022-10-07 15:58:52 +05:30
Travis CI User
2544885f5e [maven-release-plugin][skip ci] prepare for next development iteration 2022-10-05 17:02:40 +00:00
Travis CI User
1f7b272e57 [maven-release-plugin][skip ci] prepare release 17.150 2022-10-05 17:02:37 +00:00
Tom Page
df92ff9328 Merge pull request #1466 from Alfresco/feature/ACS-3630_PrivateActionTests
ACS-3630 E2E tests for creating and updating rules with private actions.
2022-10-05 16:24:47 +01:00
Travis CI User
b262c8fb92 [maven-release-plugin][skip ci] prepare for next development iteration 2022-10-05 15:19:28 +00:00
Travis CI User
0254323de5 [maven-release-plugin][skip ci] prepare release 17.149 2022-10-05 15:19:26 +00:00
Tom Page
89e8f7ff66 Merge master into ACS-3630_PrivateActionTests. 2022-10-05 16:09:29 +01:00
Tom Page
5735a41a40 Merge pull request #1468 from Alfresco/feature/ACS-3291_LinkUnlinkPermissionChecks
ACS-3291 Permission checks when linking and unlinking to rule sets.
2022-10-05 15:37:25 +01:00
Piotr Żurek
68b9a0e8a6 ACS-3643 Add meaningful message in the Workflow Console when deployment fails (#1467) 2022-10-05 15:53:42 +02:00
Tom Page
39f4a2179e ACS-3630 Use existing features in AccessRestrictionUtil. 2022-10-05 14:33:18 +01:00
Tom Page
440f0568e3 ACS-3291 Permission checks when linking and unlinking to rule sets. 2022-10-05 14:22:37 +01:00
krdabrowski
67f7fff0b7 ACS-3525: API for manual triggering rules on a folder (#1458)
* ACS-3525: API for manual triggering rules on a folder
2022-10-05 12:19:49 +02:00
Tom Page
1a08480f22 ACS-3630 E2E tests for creating and updating rules with private actions.
Ensure that 403 Forbidden is returned rather than 500 Internal Server Error.
2022-10-05 09:13:01 +01:00
Tom Page
8afd06a57f Merge pull request #1465 from Alfresco/feature/ACS-3605_LimitLinkedToBy
ACS-3605 Add limit to maximum size of optional linkedToBy field.
2022-10-05 09:05:21 +01:00
Tom Page
5e82555269 ACS-3605 Fix reference in unit test. 2022-10-04 16:21:23 +01:00
Tom Page
3682ddf652 ACS-3605 Add limit to maximum size of optional linkedToBy field. 2022-10-04 15:54:31 +01:00
Travis CI User
6747e300af [maven-release-plugin][skip ci] prepare for next development iteration 2022-10-04 08:53:26 +00:00
Travis CI User
4bb2f67a48 [maven-release-plugin][skip ci] prepare release 17.148 2022-10-04 08:53:23 +00:00
Tom Page
c5c6b45f17 Merge pull request #1462 from Alfresco/feature/ACS-3377_UpdateRuleOrder
ACS-3377 Update rule order.
2022-10-04 09:16:30 +01:00
Travis CI User
1d9823d2e9 [maven-release-plugin][skip ci] prepare for next development iteration 2022-10-03 22:54:33 +00:00
Travis CI User
bfae9dd78d [maven-release-plugin][skip ci] prepare release 17.147 2022-10-03 22:54:30 +00:00
Kristian Dimitrov
86af67a435 ACS-3601: Add unlink e2es (#1461)
* ACS-3601: Add unlink e2es
2022-10-03 23:16:59 +01:00
Travis CI User
7ce6c121ef [maven-release-plugin][skip ci] prepare for next development iteration 2022-10-03 16:00:31 +00:00
Travis CI User
d0061fb530 [maven-release-plugin][skip ci] prepare release 17.146 2022-10-03 16:00:28 +00:00
Tom Page
1c91452ee9 Merge pull request #1456 from Alfresco/feature/ACS-3376_GETRuleIds
ACS-3376 GET rule order within rule set.
2022-10-03 16:24:39 +01:00
Tom Page
f707906943 ACS-3377 Fix review comments. 2022-10-03 15:40:52 +01:00
Tom Page
af7e9f97aa ACS-3376 Simplify method of obtaining rule ids. 2022-10-03 15:28:00 +01:00
Tom Page
e9105f0f0c ACS-3377 Update rule order. 2022-10-03 10:04:07 +01:00
Travis CI User
282186b877 [maven-release-plugin][skip ci] prepare for next development iteration 2022-10-03 09:02:38 +00:00
Travis CI User
5ab44f4f35 [maven-release-plugin][skip ci] prepare release 17.145 2022-10-03 09:02:36 +00:00
kavitshah-gl
bf855c5965 Feature/acs-3579 (#1443)
* Fixed DeleteRecordTests-destroyOfRecord

* [ags]

* [ags]

* [ags]

* [ags]

* [ags]

* [ags]

* [ags]

* [ags]

* [ags]

* [ags]

* [ags]

* [ags]

* [ags]

* [ags]

* [ags]
2022-10-03 13:47:59 +05:30
Travis CI User
d02f88eed4 [maven-release-plugin][skip ci] prepare for next development iteration 2022-10-02 00:13:36 +00:00
Travis CI User
e487061e96 [maven-release-plugin][skip ci] prepare release 17.144 2022-10-02 00:13:33 +00:00
Alfresco CI User
615406d5a1 [force] Force release for 2022-10-02. 2022-10-02 00:06:17 +00:00
Travis CI User
548fc9e64a [maven-release-plugin][skip ci] prepare for next development iteration 2022-09-30 12:40:26 +00:00
Travis CI User
e7cc9ba008 [maven-release-plugin][skip ci] prepare release 17.143 2022-09-30 12:40:23 +00:00
kavitshah-gl
e10e9fe1c5 Update pom.xml
Updated Google Drive Version.
2022-09-30 16:42:10 +05:30
Travis CI User
d5a33e3f03 [maven-release-plugin][skip ci] prepare for next development iteration 2022-09-30 09:54:34 +00:00
Travis CI User
b62dfc1d76 [maven-release-plugin][skip ci] prepare release 17.142 2022-09-30 09:54:32 +00:00
Sara
3711be4e80 Feature/acs 3623 descriptor startup log (#1460)
* Pick up api-explorer 7.3.0-A1

* remove unused import

* ACS-3623 Add customembeddedworkflow to descriptor startup log
2022-09-30 09:33:36 +01:00
Travis CI User
33297757bf [maven-release-plugin][skip ci] prepare for next development iteration 2022-09-29 15:52:20 +00:00
Travis CI User
5f9c8fc499 [maven-release-plugin][skip ci] prepare release 17.141 2022-09-29 15:52:16 +00:00
tiagosalvado10
119ff309ac [MNT-23241] Prevent duplicated default headers if key/value pair is already in the request, otherwise, header is added (#1454)
* [MNT-23241] Prevent duplicated default headers if key/value pair is already in the request, otherwise, header is added
2022-09-29 16:17:23 +01:00
Tom Page
a93dd27674 ACS-3376 GET rule order within rule set. 2022-09-29 15:21:35 +01:00
Travis CI User
609ffdcbf0 [maven-release-plugin][skip ci] prepare for next development iteration 2022-09-28 16:29:18 +00:00
Travis CI User
85baa164db [maven-release-plugin][skip ci] prepare release 17.140 2022-09-28 16:29:15 +00:00
Kristian Dimitrov
8776109582 ACS-3427: Add GET e2es for actions and conditions (#1451)
* ACS-3427: Add GET e2es for actions and conditions

* ACS-3427: Remove unnecessary cast
2022-09-28 16:25:47 +01:00
Travis CI User
c8cf52baef [maven-release-plugin][skip ci] prepare for next development iteration 2022-09-28 10:04:35 +00:00
Travis CI User
9d82828959 [maven-release-plugin][skip ci] prepare release 17.139 2022-09-28 10:04:32 +00:00
Piotr Żurek
d475c74707 ACS-3563 Workflow Licence Check (#1422) 2022-09-28 11:31:32 +02:00
Travis CI User
cc3ea3167b [maven-release-plugin][skip ci] prepare for next development iteration 2022-09-28 08:21:39 +00:00
Travis CI User
36f1c7083c [maven-release-plugin][skip ci] prepare release 17.138 2022-09-28 08:21:36 +00:00
Tom Page
22a158a1ee Merge pull request #1446 from Alfresco/feature/ACS-3488_RemoveXMLDataProvider
ACS-3488 Remove xml data provider.
2022-09-28 08:45:54 +01:00
Sara
2a75a304a9 ACS-3560 Bump tas-restapi to 1.124 (#1449) 2022-09-28 08:37:49 +01:00
Domenico Sibilio
472b3d044f ACS-3594 Add mechanism to deprecate modules within the Repo (#1445) 2022-09-28 09:25:57 +02:00
dependabot[bot]
e2db0aab11 Bump alfresco/alfresco-base-tomcat in /packaging/docker-alfresco (#1439) 2022-09-28 07:18:32 +00:00
Travis CI User
031efe67d7 [maven-release-plugin][skip ci] prepare for next development iteration 2022-09-27 16:23:09 +00:00
Travis CI User
e22b5fff86 [maven-release-plugin][skip ci] prepare release 17.137 2022-09-27 16:23:06 +00:00
George Evangelopoulos
91e2421d7c ACS-3291: Support for unlinking rule sets (#1421)
* ACS-3291: Support for unlinking rule sets

* ACS-3291: add unit tests and edge case exceptions

* ACS-3291: add support for -default- ruleSetId

Co-authored-by: Tom Page <tpage-alfresco@users.noreply.github.com>
2022-09-27 18:45:55 +03:00
Tom Page
529f6b31e8 Update SearchInFolderTests.java 2022-09-27 16:22:09 +01:00
Maciej Pichura
4bc36ae18d ACS-3510 Rule mappers refactor pt3 (#1438)
* ACS-3510: Rule mappers refactor pt3 (presumably final)

* ACS-3510: Rule mappers refactor pt3 - fixes after master merge.

* ACS-3510: Fixing error script conversion, adding some logging.
2022-09-27 15:24:39 +02:00
Tom Page
47187ee12e ACS-3488 Refactor in-folder tests. 2022-09-27 14:05:49 +01:00
Tom Page
520b9e7fcb ACS-3488 Remove usage of XML data provider.
This broke when we updated TestNG.
2022-09-27 10:38:25 +01:00
Travis CI User
73518a0342 [maven-release-plugin][skip ci] prepare for next development iteration 2022-09-27 09:09:54 +00:00
Travis CI User
cc1682c209 [maven-release-plugin][skip ci] prepare release 17.136 2022-09-27 09:09:51 +00:00
Tom Page
c9d7d0993e ACS-3589 Ensure tas-cmis is built before we try to use it in tas-integration. 2022-09-27 09:31:26 +01:00
Tom Page
ec13ac08c4 ACS-3589 Use project version when importing tas-cmis. 2022-09-27 09:03:07 +01:00
Tom Page
e0df4e8831 ACS-3589 Remove legacy reference to tas-cmis. 2022-09-27 08:58:41 +01:00
Tom Page
dffdb59b05 Merge pull request #1435 from Alfresco/feature/ACS-3589_TASCMISMerge
ACS-3589 Merge TAS CMIS into community repo.
2022-09-27 08:17:55 +01:00
Travis CI User
1d03f3aaa6 [maven-release-plugin][skip ci] prepare for next development iteration 2022-09-26 18:42:29 +00:00
Travis CI User
21e444da9f [maven-release-plugin][skip ci] prepare release 17.135 2022-09-26 18:42:26 +00:00
evasques
a512c3443c MNT-23174 - RM upgrade from 3.4.1.1 to 11.153 fails (#1434)
* MNT-23174 - RM upgrade from 3.4.1.1 to 11.153 fails
* Added batching capability to process records in hold
* Changed the retrieval of child assocs witout preload to not fill up the caches when we have very big holds
* Added property rm.patch.v35.holdNewChildAssocPatch.batchSize to be able to configure the batchSize
* Adapt mocks on unit test to the new calls
2022-09-26 19:05:03 +01:00
Travis CI User
e4f9c5539b [maven-release-plugin][skip ci] prepare for next development iteration 2022-09-26 15:51:59 +00:00
Travis CI User
2a5df0dd7c [maven-release-plugin][skip ci] prepare release 17.134 2022-09-26 15:51:56 +00:00
tiagosalvado10
be7d4be636 [MNT-23118] Allow import with long paths (#1426)
* [MNT-23118] Allow nodes with long paths to be created during ACP import
2022-09-26 16:16:35 +01:00
Marcin Strankowski
d40ed346a7 Update transform-core to 3.0.0-A3. Update transform-servce to 2.0.0-A3 (#1437) 2022-09-26 16:45:04 +02:00
Tom Page
8c556eeaca ACS-3359 Rename boolean rule fields. (#1436)
* ACS-3359 Rename boolean rule fields.

* ACS-3359 Re-add unused import.

This has started to be used on master now.

* ACS-3359 Fix E2E test.

The isShared field is requested and comes back correctly as false.
2022-09-26 15:43:13 +01:00
Travis CI User
d0eea6835e [maven-release-plugin][skip ci] prepare for next development iteration 2022-09-26 13:00:15 +00:00
Travis CI User
031dde6293 [maven-release-plugin][skip ci] prepare release 17.133 2022-09-26 13:00:12 +00:00
Maciej Pichura
819f0c9921 ACS-3510: Rule Action updates E2E tests. (#1431)
* ACS-3510: Rule Action updates E2E tests.

* ACS-3510: Rule Action updates E2E tests - fixes and enhancements.
2022-09-26 14:26:59 +02:00
Travis CI User
0b2e9e0a97 [maven-release-plugin][skip ci] prepare for next development iteration 2022-09-26 11:19:03 +00:00
Travis CI User
09ce322111 [maven-release-plugin][skip ci] prepare release 17.132 2022-09-26 11:19:00 +00:00
evasques
49cedfc40c MNT-23043 - Property Tables Cleaner Job V3 (#1330)
* Implementation of V3

* Correct commit placement. If done while cursors are still open can result lost cursors - see MNT-23127

* Correct formatting

* Unit Test

* Add test to test suite

* Corrections on the unit test

* Correct typo and add documentation

* Fix typos

* Set the default values as constants

* remove initialization of rowsProcessed

* Improve comments

* Optimizations regarding retrieving min and max values

* Fix PostgreSQL sql script for v3
2022-09-26 11:44:01 +01:00
Tom Page
dcc739db76 ACS-3589 Merge alfresco-tas-cmis into master.
Remove files that are not needed in new structure.
2022-09-26 11:26:22 +01:00
Sara
0aa4b5de9c Feature/acs 3555 add custom embedded workflow license code (#1432)
* ACS-3555 Add license code for custom embedded workflow

* ACS-3555 remove unused import

* ACS-3560 Discovery API for custom embedded workflow license

* ACS-3560 fix compilation error

* use default method

* restore discovery api
2022-09-26 11:04:26 +01:00
Travis CI User
f196de23b0 [maven-release-plugin][skip ci] prepare for next development iteration 2022-09-25 00:10:59 +00:00
Travis CI User
59901e44fe [maven-release-plugin][skip ci] prepare release 17.131 2022-09-25 00:10:57 +00:00
Alfresco CI User
964cd86e00 [force] Force release for 2022-09-25. 2022-09-25 00:04:06 +00:00
Travis CI User
636ef33cc0 [maven-release-plugin][skip ci] prepare for next development iteration 2022-09-23 15:47:19 +00:00
Travis CI User
0d3ab4d921 [maven-release-plugin][skip ci] prepare release 17.130 2022-09-23 15:47:16 +00:00
Sara
02a0454055 Revert "Feature/acs 3555 add custom embedded workflow license code (#1420)" (#1430)
This reverts commit 19a061dc04.
2022-09-23 15:13:33 +01:00
Travis CI User
653f685dc9 [maven-release-plugin][skip ci] prepare for next development iteration 2022-09-23 13:01:58 +00:00
Travis CI User
2362ef986b [maven-release-plugin][skip ci] prepare release 17.129 2022-09-23 13:01:55 +00:00
Sara
dc5404b8c2 Revert "Feature/acs 3560 discovery api for custom embedded workflow license (#1423)" (#1429)
This reverts commit 541bf63d80.
2022-09-23 13:27:56 +01:00
tiagosalvado10
125b35e11c [MNT-21638] Script task execution (#1210)
* [MNT-21638] Script task execution based on workflow deloyment category. Javadoc. Unit tests.
2022-09-23 13:24:49 +01:00
brijmohan1
a77584a398 MNT-22233 resolved error while loading subcategory with cutoff. (#1424)
* MNT-22233 resolved error while loading subcategory with cutoff.

* MNT-22233 added log message when action definition is null.

* MNT-22233 updated log message when action definition is null.
2022-09-23 11:49:01 +05:30
Sara
541bf63d80 Feature/acs 3560 discovery api for custom embedded workflow license (#1423)
* ACS-3555 Add license code for custom embedded workflow

* ACS-3555 remove unused import

* ACS-3560 Discovery API for custom embedded workflow license

* ACS-3560 fix compilation error
2022-09-22 11:19:18 +01:00
Sara
19a061dc04 Feature/acs 3555 add custom embedded workflow license code (#1420)
* ACS-3555 Add license code for custom embedded workflow

* ACS-3555 remove unused import
2022-09-22 11:14:46 +01:00
Travis CI User
b1282cba89 [maven-release-plugin][skip ci] prepare for next development iteration 2022-09-22 06:49:24 +00:00
Travis CI User
d137dd89ec [maven-release-plugin][skip ci] prepare release 17.128 2022-09-22 06:49:22 +00:00
Maciej Pichura
9c8f98c12f ACS-3509 Rule mappers refactor pt2 (#1428)
* ACS-3509: Fixes and refactors for rule mappers pt 2.

* ACS-3509: Rule Condition mappings refactor + tests.
2022-09-22 08:10:59 +02:00
Tom Page
e0d52f98ae ACS-3366 Add support for isLinkedTo to GET rule sets. (#1427)
* ACS-3364 Add permission handling to linkedToBy.

* ACS-3366 Add support for isLinkedTo to GET rule sets.
2022-09-21 15:18:41 +01:00
Tom Page
c4d432b136 ACS-3364 Add permission handling to linkedToBy. (#1425) 2022-09-21 15:18:30 +01:00
Travis CI User
e0844d72e1 [maven-release-plugin][skip ci] prepare for next development iteration 2022-09-21 10:32:38 +00:00
Travis CI User
857771593b [maven-release-plugin][skip ci] prepare release 17.127 2022-09-21 10:32:35 +00:00
Maciej Pichura
a80ac51b5d ACS-3509: Adding E2E TAS REST tests for update rule conditions. (#1411)
* ACS-3509: Adding E2E TAS REST tests for update rule conditions.

* ACS-3509: Clean up after review.

* ACS-3509: Fixes after review, adding some more E2E tests.
2022-09-21 11:56:10 +02:00
Travis CI User
d7d50d0a67 [maven-release-plugin][skip ci] prepare for next development iteration 2022-09-21 09:32:31 +00:00
Travis CI User
11dd5307a7 [maven-release-plugin][skip ci] prepare release 17.126 2022-09-21 09:32:28 +00:00
rrajoria
a038f068ab Encoding to handle XSS (#1409) 2022-09-21 14:26:12 +05:30
Travis CI User
cc1c539a4c [maven-release-plugin][skip ci] prepare for next development iteration 2022-09-21 08:45:24 +00:00
Travis CI User
20ec3351b3 [maven-release-plugin][skip ci] prepare for next development iteration 2022-09-16 13:34:48 +00:00
Travis CI User
e79b434c54 [maven-release-plugin][skip ci] prepare release v1.34 2022-09-16 13:34:47 +00:00
dependabot-preview[bot]
163add3bfd Bump resteasy-jackson2-provider from 3.6.3.Final to 4.7.1.Final (#60)
Bumps resteasy-jackson2-provider from 3.6.3.Final to 4.7.1.Final.

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2022-09-16 14:30:54 +01:00
dependabot-preview[bot]
ba2367bf92 Bump alfresco-super-pom from 10 to 12 (#19)
Bumps [alfresco-super-pom](https://github.com/Alfresco/alfresco-super-pom) from 10 to 12.
- [Release notes](https://github.com/Alfresco/alfresco-super-pom/releases)
- [Commits](https://github.com/Alfresco/alfresco-super-pom/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2022-09-16 14:30:39 +01:00
dependabot-preview[bot]
84857e1a71 Upgrade to GitHub-native Dependabot (#54)
Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2022-09-16 14:30:29 +01:00
dependabot-preview[bot]
cda1c682ae Bump maven-project-info-reports-plugin from 2.9 to 3.1.2 (#55)
Bumps [maven-project-info-reports-plugin](https://github.com/apache/maven-project-info-reports-plugin) from 2.9 to 3.1.2.
- [Release notes](https://github.com/apache/maven-project-info-reports-plugin/releases)
- [Commits](https://github.com/apache/maven-project-info-reports-plugin/compare/maven-project-info-reports-plugin-2.9...maven-project-info-reports-plugin-3.1.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2022-09-16 14:30:18 +01:00
Travis CI User
9c989c424f [maven-release-plugin][skip ci] prepare for next development iteration 2022-09-01 14:10:33 +00:00
Travis CI User
c338001de2 [maven-release-plugin][skip ci] prepare release v1.33 2022-09-01 14:10:30 +00:00
Tom Page
c6519c2a3f Upgrade tas-utility version to use latest TestNG. 2022-09-01 15:05:38 +01:00
Travis CI User
c7559ba8b1 [maven-release-plugin][skip ci] prepare for next development iteration 2022-08-10 08:56:35 +00:00
Travis CI User
c5fcce20f1 [maven-release-plugin][skip ci] prepare release v1.32 2022-08-10 08:56:34 +00:00
Damian Ujma
0a97d98271 ACS-3351 Upgrade to Java 17 (#62) 2022-08-10 10:38:48 +02:00
Travis CI User
83cd90c85e [maven-release-plugin][skip ci] prepare for next development iteration 2021-08-02 22:33:17 +00:00
Travis CI User
6186757322 [maven-release-plugin][skip ci] prepare release v1.31 2021-08-02 22:33:11 +00:00
dependabot-preview[bot]
27344421e0 Bump utility from 3.0.44 to 3.0.45 (#61) 2021-08-02 22:25:05 +00:00
Travis CI User
a8f9d4df81 [maven-release-plugin][skip ci] prepare for next development iteration 2021-05-13 11:12:31 +00:00
Travis CI User
366cea6e33 [maven-release-plugin][skip ci] prepare release v1.30 2021-05-13 11:12:27 +00:00
Denis Ungureanu
eb9792339f REPO-5391 : [cmis webservices] isPrivateWorkingCopy throws NullPointerException (#56)
- Alfresco supports BindingType.WEBSERVICES for CMIS 1.0 and "cmis:isPrivateWorkingCopy" was introduced with CMIS 1.1
 - thus checking if the document is a pwc (private working copy) through CMIS 1.0 method https://chemistry.apache.org/java/javadoc/org/apache/chemistry/opencmis/client/api/Document.html#isVersionSeriesPrivateWorkingCopy--
2021-05-13 14:03:48 +03:00
Travis CI User
fcb7ec339f [maven-release-plugin][skip ci] prepare for next development iteration 2021-03-26 22:21:06 +00:00
Travis CI User
110a21cf27 [maven-release-plugin][skip ci] prepare release v1.29 2021-03-26 22:21:02 +00:00
dependabot-preview[bot]
bfc9fdc802 Bump utility from 3.0.43 to 3.0.44 (#53) 2021-03-26 22:17:50 +00:00
Travis CI User
ff8e95e1cf [maven-release-plugin][skip ci] prepare for next development iteration 2021-03-02 22:20:09 +00:00
Travis CI User
8e5a259204 [maven-release-plugin][skip ci] prepare release v1.28 2021-03-02 22:20:02 +00:00
dependabot-preview[bot]
f993a9c904 Bump utility from 3.0.42 to 3.0.43 (#52) 2021-03-02 22:16:40 +00:00
Travis CI User
4cd63227ae [maven-release-plugin][skip ci] prepare for next development iteration 2021-02-04 22:28:37 +00:00
Travis CI User
f42e8b2e82 [maven-release-plugin][skip ci] prepare release v1.27 2021-02-04 22:28:29 +00:00
dependabot-preview[bot]
fff66fea81 Bump utility from 3.0.41 to 3.0.42 (#51) 2021-02-04 22:20:13 +00:00
Travis CI User
451736a9b4 [maven-release-plugin][skip ci] prepare for next development iteration 2020-12-17 22:21:59 +00:00
Travis CI User
7c2ae622ad [maven-release-plugin][skip ci] prepare release v1.26 2020-12-17 22:21:53 +00:00
dependabot-preview[bot]
2d34b3579b Bump utility from 3.0.40 to 3.0.41 (#49) 2020-12-17 22:18:17 +00:00
Travis CI User
78d98bba49 [maven-release-plugin][skip ci] prepare for next development iteration 2020-11-19 22:20:01 +00:00
Travis CI User
d8085b463f [maven-release-plugin][skip ci] prepare release v1.25 2020-11-19 22:19:55 +00:00
dependabot-preview[bot]
1ff9c13985 Bump utility from 3.0.39 to 3.0.40 (#48) 2020-11-19 22:16:08 +00:00
Travis CI User
f23c013950 [maven-release-plugin][skip ci] prepare for next development iteration 2020-11-18 22:19:50 +00:00
Travis CI User
e7f6d7aa15 [maven-release-plugin][skip ci] prepare release v1.24 2020-11-18 22:19:43 +00:00
dependabot-preview[bot]
7d00b5a965 Bump utility from 3.0.38 to 3.0.39 (#47) 2020-11-18 22:15:34 +00:00
Travis CI User
78df8e8f18 [maven-release-plugin][skip ci] prepare for next development iteration 2020-11-12 22:20:38 +00:00
Travis CI User
56fca4f248 [maven-release-plugin][skip ci] prepare release v1.23 2020-11-12 22:20:31 +00:00
dependabot-preview[bot]
7646714e3a Bump utility from 3.0.37 to 3.0.38 (#46) 2020-11-12 22:16:53 +00:00
Travis CI User
68948a568a [maven-release-plugin][skip ci] prepare for next development iteration 2020-11-11 22:23:36 +00:00
Travis CI User
c10c522a00 [maven-release-plugin][skip ci] prepare release v1.22 2020-11-11 22:23:30 +00:00
dependabot-preview[bot]
a6fb00721c Bump utility from 3.0.36 to 3.0.37 (#45) 2020-11-11 22:16:48 +00:00
Travis CI User
b07a96a289 [maven-release-plugin][skip ci] prepare for next development iteration 2020-11-04 22:19:54 +00:00
Travis CI User
fb58ef512e [maven-release-plugin][skip ci] prepare release v1.21 2020-11-04 22:19:48 +00:00
dependabot-preview[bot]
99d4b52ccb Bump utility from 3.0.35 to 3.0.36 (#44) 2020-11-04 22:15:37 +00:00
Travis CI User
6946969ab9 [maven-release-plugin][skip ci] prepare for next development iteration 2020-11-04 11:07:37 +00:00
Travis CI User
e1dbd1d4fe [maven-release-plugin][skip ci] prepare release v1.20 2020-11-04 11:07:31 +00:00
Adina Parpalita
0723852ac3 correctly throw assert error (#43) 2020-11-04 13:04:36 +02:00
Travis CI User
3dbcbfa535 [maven-release-plugin][skip ci] prepare for next development iteration 2020-10-30 14:24:15 +00:00
Travis CI User
163c938848 [maven-release-plugin][skip ci] prepare release v1.19 2020-10-30 14:24:09 +00:00
Adina Parpalita
e334c60d7a remove RuntimeException from catch (#42) 2020-10-30 16:20:03 +02:00
Travis CI User
f159e66c1b [maven-release-plugin][skip ci] prepare for next development iteration 2020-10-28 22:22:18 +00:00
Travis CI User
83e4ace76f [maven-release-plugin][skip ci] prepare release v1.18 2020-10-28 22:22:11 +00:00
dependabot-preview[bot]
c60f940cc1 Bump utility from 3.0.34 to 3.0.35 (#41) 2020-10-28 22:18:42 +00:00
Travis CI User
81abbf55b4 [maven-release-plugin][skip ci] prepare for next development iteration 2020-10-27 22:21:47 +00:00
Travis CI User
f97a9605f2 [maven-release-plugin][skip ci] prepare release v1.17 2020-10-27 22:21:39 +00:00
dependabot-preview[bot]
0c81885ea0 Bump utility from 3.0.33 to 3.0.34 (#40) 2020-10-27 22:16:32 +00:00
Travis CI User
248830e77f [maven-release-plugin][skip ci] prepare for next development iteration 2020-10-13 21:22:03 +00:00
Travis CI User
9c950f767f [maven-release-plugin][skip ci] prepare release v1.16 2020-10-13 21:21:57 +00:00
dependabot-preview[bot]
13b23ad55f Bump utility from 3.0.32 to 3.0.33 (#39) 2020-10-13 21:18:35 +00:00
Travis CI User
01f19d717b [maven-release-plugin][skip ci] prepare for next development iteration 2020-10-08 21:24:15 +00:00
Travis CI User
4aeffd2647 [maven-release-plugin][skip ci] prepare release v1.15 2020-10-08 21:24:09 +00:00
dependabot-preview[bot]
03f02a7687 Bump utility from 3.0.30 to 3.0.32 (#38) 2020-10-08 21:20:35 +00:00
Travis CI User
d70555e60a [maven-release-plugin][skip ci] prepare for next development iteration 2020-09-30 13:20:42 +00:00
Travis CI User
255c6c8e8a [maven-release-plugin][skip ci] prepare release v1.14 2020-09-30 13:20:36 +00:00
Alexandru-Eusebiu Epure
961b336c11 Add WS support - alfa version. (#37)
* Add support for WS Binding - alfa stage.
2020-09-30 15:57:54 +03:00
dependabot-preview[bot]
ec79172479 Bump utility from 3.0.29 to 3.0.30 (#36) 2020-09-25 21:18:48 +00:00
dependabot-preview[bot]
47de5d9e0a Bump utility from 3.0.28 to 3.0.29 (#32) 2020-08-31 21:20:26 +00:00
dependabot-preview[bot]
4cb843024c Bump utility from 3.0.27 to 3.0.28 (#31) 2020-08-28 21:19:22 +00:00
dependabot-preview[bot]
b8cb5d89a6 Bump utility from 3.0.26 to 3.0.27 (#29) 2020-07-21 21:45:50 +00:00
dependabot-preview[bot]
d61e5826f7 Bump utility from 3.0.25 to 3.0.26 (#28) 2020-07-14 21:31:16 +00:00
dependabot-preview[bot]
8238947ee0 Bump utility from 3.0.24 to 3.0.25 (#27) 2020-07-10 21:33:37 +00:00
dependabot-preview[bot]
129dac1916 Bump utility from 3.0.23 to 3.0.24 (#26) 2020-07-07 21:23:52 +00:00
dependabot-preview[bot]
cb5548914a Bump utility from 3.0.22 to 3.0.23 (#25) 2020-07-02 21:20:47 +00:00
dependabot-preview[bot]
32b66fad2b Bump utility from 3.0.21 to 3.0.22 (#24) 2020-07-01 21:23:03 +00:00
dependabot-preview[bot]
475dd43a32 Bump utility from 3.0.20 to 3.0.21 (#22) 2020-06-15 21:23:58 +00:00
dependabot-preview[bot]
178ae7375a Bump utility from 3.0.19 to 3.0.20 (#16) 2020-02-26 22:18:06 +00:00
dependabot-preview[bot]
d935b7fc2e Bump utility from 3.0.18 to 3.0.19 (#13) 2020-01-23 22:16:28 +00:00
Travis CI User
5bb26afe91 [maven-release-plugin][skip ci] prepare for next development iteration 2020-01-15 22:25:55 +00:00
Travis CI User
8c121889ae [maven-release-plugin][skip ci] prepare release v1.13 2020-01-15 22:25:49 +00:00
dependabot-preview[bot]
e241c2a4ec Bump utility from 3.0.17 to 3.0.18 (#12) 2020-01-15 22:17:05 +00:00
Travis CI User
da1fd787e7 [maven-release-plugin][skip ci] prepare for next development iteration 2019-12-17 23:29:59 +00:00
Travis CI User
0cf76c6d3f [maven-release-plugin][skip ci] prepare release v1.12 2019-12-17 23:29:53 +00:00
dependabot-preview[bot]
347c05c855 Bump utility from 3.0.16 to 3.0.17 (#9) 2019-12-17 22:56:44 +00:00
Travis CI User
440a3640a9 [maven-release-plugin][skip ci] prepare for next development iteration 2019-12-04 23:07:44 +00:00
Travis CI User
9d4c92bedf [maven-release-plugin][skip ci] prepare release v1.11 2019-12-04 23:07:39 +00:00
dependabot-preview[bot]
e5655effee Bump utility from 3.0.15 to 3.0.16 (#8) 2019-12-04 22:29:55 +00:00
Travis CI User
cf6495a22c [maven-release-plugin][skip ci] prepare for next development iteration 2019-11-30 00:00:11 +00:00
Travis CI User
f917f67a00 [maven-release-plugin][skip ci] prepare release v1.10 2019-11-30 00:00:06 +00:00
dependabot-preview[bot]
f33e7b9555 Bump utility from 3.0.14 to 3.0.15 (#7) 2019-11-29 22:34:36 +00:00
Travis CI User
cfec11a246 [maven-release-plugin][skip ci] prepare for next development iteration 2019-11-29 16:34:51 +00:00
Travis CI User
c066e42152 [maven-release-plugin][skip ci] prepare release v1.9 2019-11-29 16:34:45 +00:00
Tom Page
d2ab9a7998 SEARCH-1989 Allow checking ordered values returned in a column. (#6) 2019-11-29 16:27:05 +00:00
Travis CI User
15dea80d0a [maven-release-plugin][skip ci] prepare for next development iteration 2019-11-29 14:53:58 +00:00
Travis CI User
28ef5fa32c [maven-release-plugin][skip ci] prepare release v1.8 2019-11-29 14:53:52 +00:00
Tom Page
7da7544c37 SEARCH-1989 Allow checking values returned in a column. (#1) 2019-11-29 14:50:05 +00:00
Travis CI User
98bc091adc [maven-release-plugin][skip ci] prepare for next development iteration 2019-11-29 14:44:01 +00:00
Travis CI User
8839ed7027 [maven-release-plugin][skip ci] prepare release v1.7 2019-11-29 14:43:55 +00:00
dependabot-preview[bot]
3ee1db50f8 Bump utility from 3.0.8 to 3.0.14 (#3) 2019-11-29 14:35:21 +00:00
dependabot-preview[bot]
3fbbf5a891 Bump chemistry-opencmis-commons-api from 1.0.0 to 1.1.0 (#2) 2019-11-29 14:32:03 +00:00
Travis CI User
fb7e03b02f [maven-release-plugin][skip ci] prepare for next development iteration 2019-08-02 15:58:13 +00:00
Travis CI User
e6e1245a7a [maven-release-plugin][skip ci] prepare release v1.6 2019-08-02 15:58:07 +00:00
Alex Mukha
2e499fb377 Remove source jar config 2019-08-02 16:53:26 +01:00
Alex Mukha
b25dbeb608 Remove test jar 2019-08-02 16:52:58 +01:00
Alex Mukha
8fba004f83 Remove unused properties from pom 2019-08-02 16:49:34 +01:00
Alex Mukha
47b391315b Fix pom formatting 2019-08-02 16:48:22 +01:00
Travis CI User
deade9c64c [maven-release-plugin][skip ci] prepare for next development iteration 2019-08-02 15:13:30 +00:00
Travis CI User
ffe1d0a158 [maven-release-plugin][skip ci] prepare release v1.5 2019-08-02 15:13:24 +00:00
Alex Mukha
1cd2352dc9 Cleanup of resources 2019-08-02 16:09:11 +01:00
Travis CI User
a27871e7a7 [maven-release-plugin][skip ci] prepare for next development iteration 2019-08-02 14:59:03 +00:00
Travis CI User
030363d7c0 [maven-release-plugin][skip ci] prepare release v1.4 2019-08-02 14:58:57 +00:00
Alex Mukha
0af021b469 Release 1.4 2019-08-02 15:53:53 +01:00
Travis CI User
0dc4f3fbf7 [maven-release-plugin][skip ci] prepare for next development iteration 2019-08-02 14:33:56 +00:00
Travis CI User
988846e1df [maven-release-plugin][skip ci] prepare release v1.3 2019-08-02 14:33:50 +00:00
Alex Mukha
67e366def2 Release 1.3 2019-08-02 15:29:05 +01:00
Travis CI User
a673102baf [maven-release-plugin][skip ci] prepare for next development iteration 2019-08-02 14:24:20 +00:00
Travis CI User
652c0784d5 [maven-release-plugin][skip ci] prepare release v1.2 2019-08-02 14:24:14 +00:00
Alex Mukha
5ca5e96b7e Fix javadoc on JDK11 2019-08-02 15:19:08 +01:00
Travis CI User
ed39b9a114 [maven-release-plugin][skip ci] prepare for next development iteration 2019-08-02 14:15:22 +00:00
Travis CI User
35e543f37d [maven-release-plugin][skip ci] prepare release v1.1 2019-08-02 14:15:17 +00:00
Alex Mukha
a8585b55cb Add compiler plugin config to run on JDK11 2019-08-02 15:09:19 +01:00
Travis CI User
3ffb350567 [maven-release-plugin][skip ci] prepare for next development iteration 2019-08-02 14:03:49 +00:00
Travis CI User
4d2073e4c5 [maven-release-plugin][skip ci] prepare release v1.0 2019-08-02 14:03:43 +00:00
Alex Mukha
19ebec0320 Update build agent to xenial 2019-08-01 22:41:13 +01:00
Alex Mukha
0c23a3fa4b Initial version after move 2019-08-01 22:34:53 +01:00
Alex Mukha
3c2269f51c Initial commit 2019-08-01 22:30:29 +01:00
160 changed files with 11053 additions and 1618 deletions

View File

@@ -335,6 +335,7 @@ jobs:
before_script:
- ${TAS_SCRIPTS}/start-compose.sh ${TAS_ENVIRONMENT}/docker-compose-minimal.yml
- ${TAS_SCRIPTS}/wait-for-alfresco-start.sh "http://localhost:8082/alfresco"
- travis_retry travis_wait 40 mvn install -pl :alfresco-community-repo-integration-test -am -DskipTests -Pall-tas-tests
script: travis_wait 30 mvn -B verify -f packaging/tests/tas-integration/pom.xml -Pall-tas-tests -Denvironment=default -DrunBugs=false
after_failure: ${TAS_SCRIPTS}/output_logs_for_failures.sh "packaging/tests/tas-integration"

View File

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

View File

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

View File

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

View File

@@ -292,7 +292,6 @@ public class DeleteRecordTests extends BaseRMRestTest
* </pre>
*/
@Test (description = "Destroying record doesn't delete the content for the associated copy")
@Ignore
@AlfrescoTest (jira = "MNT-20145")
public void destroyOfRecord()
{
@@ -320,8 +319,8 @@ public class DeleteRecordTests extends BaseRMRestTest
RecordCategoryChild recFolder = createFolder(recordCategory.getId(), getRandomName("recFolder"));
RecordBodyFile recordBodyFile = RecordBodyFile.builder().targetParentId(recFolder.getId()).build();
Record recordFiled = getRestAPIFactory().getRecordsAPI().fileRecord(recordBodyFile, testFile.getNodeRefWithoutVersion());
getRestAPIFactory().getRecordsAPI().completeRecord(recordFiled.getId());
assertStatusCode(CREATED);
completeRecord(recordFiled.getId());
assertStatusCode(OK);
STEP("Execute the disposition schedule steps.");
rmRolesAndActionsAPI.executeAction(getAdminUser().getUsername(), getAdminUser().getUsername(), recordFiled.getName(),

View File

@@ -53,7 +53,6 @@ import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.testng.AssertJUnit;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Ignore;
import org.testng.annotations.Test;
import java.io.IOException;
@@ -135,7 +134,6 @@ public class DispositionScheduleLinkedRecordsTest extends BaseRMRestTest {
* <p/> TestRail Test C775<p/>
**/
@Test
@Ignore
@AlfrescoTest(jira = "RM-1622")
public void dispositionScheduleLinkedRecords() throws UnsupportedEncodingException {
STEP("Create record category");
@@ -202,7 +200,6 @@ public class DispositionScheduleLinkedRecordsTest extends BaseRMRestTest {
* When the record is linked to a folder with the same disposition schedule
* */
@Test
@Ignore
@AlfrescoTest (jira = "RM-3060")
public void sameDispositionScheduleLinkedRecords() throws UnsupportedEncodingException {
@@ -366,7 +363,6 @@ public class DispositionScheduleLinkedRecordsTest extends BaseRMRestTest {
}
@Test
@Ignore
@AlfrescoTest(jira = "RM-1622")
public void sameLevelDispositionScheduleStepsPeriodsCalculation() throws Exception {
@@ -419,7 +415,6 @@ public class DispositionScheduleLinkedRecordsTest extends BaseRMRestTest {
}
@Test (dependsOnMethods = {"sameLevelDispositionScheduleStepsPeriodsCalculation" })
@Ignore
public void deleteLongestPeriodTestPrecondition() {
// Delete the RM site
getRestAPIFactory().getRMSiteAPI().deleteRMSite();

View File

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

View File

@@ -109,6 +109,10 @@ rm.completerecord.mandatorypropertiescheck.enabled=true
#
rm.patch.v22.convertToStandardFilePlan=false
#
# Max Batch size for adding the associations between the frozen nodes and the hold
rm.patch.v35.holdNewChildAssocPatch.batchSize=1000
# Permission mapping
# these take a comma separated string of permissions from org.alfresco.service.cmr.security.PermissionService
# read maps to ReadRecords and write to FileRecords

View File

@@ -17,5 +17,6 @@
<property name="filePlanService" ref="filePlanService" />
<property name="holdService" ref="holdService" />
<property name="nodeService" ref="nodeService" />
<property name="batchSize" value="${rm.patch.v35.holdNewChildAssocPatch.batchSize}" />
</bean>
</beans>

View File

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

View File

@@ -38,9 +38,12 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import org.alfresco.model.ContentModel;
import org.alfresco.module.org_alfresco_module_rm.capability.CapabilityService;
import org.alfresco.module.org_alfresco_module_rm.capability.impl.ViewRecordsCapability;
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionAction;
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionActionDefinition;
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService;
import org.alfresco.module.org_alfresco_module_rm.event.EventCompletionDetails;
import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanComponentKind;
@@ -76,6 +79,7 @@ import org.json.simple.JSONObject;
*
* @author Roy Wetherall
*/
@Slf4j
public class JSONConversionComponent extends org.alfresco.repo.jscript.app.JSONConversionComponent
implements NodeServicePolicies.OnDeleteNodePolicy,
NodeServicePolicies.OnCreateNodePolicy
@@ -515,17 +519,25 @@ public class JSONConversionComponent extends org.alfresco.repo.jscript.app.JS
AuthenticationUtil.runAsSystem((RunAsWork<Void>) () -> {
//Add details of the next incomplete event in the disposition schedule
if (dispositionService.getNextDispositionAction(nodeRef) != null)
DispositionAction nextDispositionAction = dispositionService.getNextDispositionAction(nodeRef);
if (nextDispositionAction != null)
{
for (EventCompletionDetails details : dispositionService.getNextDispositionAction(nodeRef).getEventCompletionDetails())
for (EventCompletionDetails details : nextDispositionAction.getEventCompletionDetails())
{
if (!details.isEventComplete())
{
DispositionActionDefinition dispositionActionDefinition = nextDispositionAction.getDispositionActionDefinition();
HashMap properties = (HashMap) rmNodeValues.get("properties");
properties.put("combineDispositionStepConditions", nodeService.getProperty(dispositionService.getNextDispositionAction(nodeRef).getDispositionActionDefinition().getNodeRef(), PROP_COMBINE_DISPOSITION_STEP_CONDITIONS));
properties.put("incompleteDispositionEvent", details.getEventName());
properties.put("dispositionEventCombination", nodeService.getProperty(dispositionService.getNextDispositionAction(nodeRef).getDispositionActionDefinition().getNodeRef(), PROP_DISPOSITION_EVENT_COMBINATION));
if(dispositionActionDefinition == null)
{
log.debug("Disposition action definition for disposition action "+ nextDispositionAction.getName() +" has been removed or never exist");
}
else
{
properties.put("combineDispositionStepConditions", nodeService.getProperty(dispositionActionDefinition.getNodeRef(), PROP_COMBINE_DISPOSITION_STEP_CONDITIONS));
properties.put("dispositionEventCombination", nodeService.getProperty(dispositionActionDefinition.getNodeRef(), PROP_DISPOSITION_EVENT_COMBINATION));
}
break;
}
}

View File

@@ -30,6 +30,9 @@ import static org.alfresco.model.ContentModel.ASSOC_CONTAINS;
import static org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementCustomModel.RM_CUSTOM_URI;
import static org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel.ASSOC_FROZEN_CONTENT;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.alfresco.model.ContentModel;
@@ -37,11 +40,14 @@ import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanService;
import org.alfresco.module.org_alfresco_module_rm.hold.HoldService;
import org.alfresco.module.org_alfresco_module_rm.patch.AbstractModulePatch;
import org.alfresco.repo.policy.BehaviourFilter;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
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.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Patch to create new hold child association to link the record to the hold
@@ -52,8 +58,15 @@ import org.alfresco.service.namespace.RegexQNamePattern;
*/
public class RMv35HoldNewChildAssocPatch extends AbstractModulePatch
{
/** logger */
protected static final Logger LOGGER = LoggerFactory.getLogger(RMv35HoldNewChildAssocPatch.class);
/** A name for the associations created by this patch. */
protected static final QName PATCH_ASSOC_NAME = QName.createQName(RM_CUSTOM_URI, RMv35HoldNewChildAssocPatch.class.getSimpleName());
protected static final QName PATCH_ASSOC_NAME = QName.createQName(RM_CUSTOM_URI,
RMv35HoldNewChildAssocPatch.class.getSimpleName());
/** The batch size for processing frozen nodes. */
private int batchSize = 1000;
/**
* File plan service interface
@@ -75,7 +88,8 @@ public class RMv35HoldNewChildAssocPatch extends AbstractModulePatch
/**
* Setter for fileplanservice
*
* @param filePlanService File plan service interface
* @param filePlanService
* File plan service interface
*/
public void setFilePlanService(FilePlanService filePlanService)
{
@@ -85,7 +99,8 @@ public class RMv35HoldNewChildAssocPatch extends AbstractModulePatch
/**
* Setter for hold service
*
* @param holdService Hold service interface.
* @param holdService
* Hold service interface.
*/
public void setHoldService(HoldService holdService)
{
@@ -95,7 +110,8 @@ public class RMv35HoldNewChildAssocPatch extends AbstractModulePatch
/**
* Setter for node service
*
* @param nodeService Interface for public and internal node and store operations.
* @param nodeService
* Interface for public and internal node and store operations.
*/
public void setNodeService(NodeService nodeService)
{
@@ -112,33 +128,49 @@ public class RMv35HoldNewChildAssocPatch extends AbstractModulePatch
this.behaviourFilter = behaviourFilter;
}
/**
* Setter for maximum batch size
*
* @param maxBatchSize
* The max amount of associations to be created between the frozen nodes and the hold in a transaction
*/
public void setBatchSize(int batchSize)
{
this.batchSize = batchSize;
}
@Override
public void applyInternal()
{
behaviourFilter.disableBehaviour(ContentModel.ASPECT_AUDITABLE);
behaviourFilter.disableBehaviour(ContentModel.ASPECT_VERSIONABLE);
try
{
int patchedNodesCounter = 0;
for (NodeRef filePlan : filePlanService.getFilePlans())
{
for (NodeRef hold : holdService.getHolds(filePlan))
{
List<ChildAssociationRef> frozenAssoc = nodeService.getChildAssocs(hold, ASSOC_FROZEN_CONTENT, RegexQNamePattern.MATCH_ALL);
for (ChildAssociationRef ref : frozenAssoc)
LOGGER.debug("Analyzing hold {}", hold.getId());
BatchWorker batchWorker = new BatchWorker(hold);
LOGGER.debug("Hold has {} items to be analyzed", batchWorker.getWorkSize());
while (batchWorker.hasMoreResults())
{
NodeRef childNodeRef = ref.getChildRef();
// In testing we found that this was returning more than just "contains" associations.
// Possibly this is due to the code in Node2ServiceImpl.getParentAssocs not using the second parameter.
List<ChildAssociationRef> parentAssocs = nodeService.getParentAssocs(childNodeRef, ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL);
boolean childContainedByHold =
parentAssocs.stream().anyMatch(entry -> entry.getParentRef().equals(hold) && entry.getTypeQName().equals(ASSOC_CONTAINS));
if (!childContainedByHold)
{
nodeService.addChild(hold, childNodeRef, ASSOC_CONTAINS, PATCH_ASSOC_NAME);
}
processBatch(hold, batchWorker);
}
LOGGER.debug("Patched {} items in hold", batchWorker.getTotalPatchedNodes());
patchedNodesCounter += batchWorker.getTotalPatchedNodes();
}
}
LOGGER.debug("Patch applied to {} children across all holds", patchedNodesCounter);
}
finally
{
@@ -146,4 +178,92 @@ public class RMv35HoldNewChildAssocPatch extends AbstractModulePatch
behaviourFilter.enableBehaviour(ContentModel.ASPECT_VERSIONABLE);
}
}
private void processBatch(NodeRef hold, BatchWorker batch)
{
transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
Collection<ChildAssociationRef> childRefs = batch.getNextWork();
LOGGER.debug("Processing batch of {} children in hold", childRefs.size());
for (ChildAssociationRef child : childRefs)
{
NodeRef childNodeRef = child.getChildRef();
if (!isChildContainedByHold(hold, childNodeRef))
{
nodeService.addChild(hold, childNodeRef, ASSOC_CONTAINS, PATCH_ASSOC_NAME);
batch.countPatchedNode();
}
}
return null;
}, false, true);
}
private boolean isChildContainedByHold(NodeRef hold, NodeRef child)
{
// In testing we found that this was returning more than just "contains" associations.
// Possibly this is due to the code in Node2ServiceImpl.getParentAssocs not using the second
// parameter.
List<ChildAssociationRef> parentAssocs = nodeService.getParentAssocs(child, ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL);
return parentAssocs.stream()
.anyMatch(entry -> entry.getParentRef().equals(hold) && entry.getTypeQName().equals(ASSOC_CONTAINS));
}
private class BatchWorker
{
NodeRef hold;
int totalPatchedNodes = 0;
int workSize;
Iterator<ChildAssociationRef> iterator;
public BatchWorker(NodeRef hold)
{
this.hold = hold;
setupHold();
}
public boolean hasMoreResults()
{
return iterator == null ? true : iterator.hasNext();
}
public void countPatchedNode()
{
this.totalPatchedNodes += 1;
}
public int getTotalPatchedNodes()
{
return totalPatchedNodes;
}
public int getWorkSize()
{
return workSize;
}
public void setupHold()
{
// Get child assocs without preloading
List<ChildAssociationRef> holdChildren = nodeService.getChildAssocs(hold, ASSOC_FROZEN_CONTENT,
RegexQNamePattern.MATCH_ALL, Integer.MAX_VALUE, false);
this.iterator = holdChildren.listIterator();
this.workSize = holdChildren.size();
}
public Collection<ChildAssociationRef> getNextWork()
{
List<ChildAssociationRef> frozenNodes = new ArrayList<ChildAssociationRef>(batchSize);
while (iterator.hasNext() && frozenNodes.size() < batchSize)
{
frozenNodes.add(iterator.next());
}
return frozenNodes;
}
}
}

View File

@@ -34,7 +34,9 @@ import static org.alfresco.model.ContentModel.ASSOC_CONTAINS;
import static org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel.ASSOC_FROZEN_CONTENT;
import static org.alfresco.module.org_alfresco_module_rm.patch.v35.RMv35HoldNewChildAssocPatch.PATCH_ASSOC_NAME;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -51,16 +53,21 @@ import java.util.Set;
import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanService;
import org.alfresco.module.org_alfresco_module_rm.hold.HoldService;
import org.alfresco.repo.policy.BehaviourFilter;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
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.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.alfresco.service.transaction.TransactionService;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
/**
* RM V3.5 Create new hold child association to link the record to the hold
@@ -81,6 +88,12 @@ public class RMv35HoldNewChildAssocPatchUnitTest
@Mock
private BehaviourFilter mockBehaviourFilter;
@Mock
private TransactionService mockTransactionService;
@Mock
private RetryingTransactionHelper mockRetryingTransactionHelper;
@InjectMocks
private RMv35HoldNewChildAssocPatch patch;
@@ -112,25 +125,63 @@ public class RMv35HoldNewChildAssocPatchUnitTest
/**
* Test secondary associations are created for held items so that they are "contained" in the hold.
*/
@SuppressWarnings("unchecked")
@Test
public void testAddChildDuringUpgrade()
{
when(mockFilePlanService.getFilePlans()).thenReturn(fileplans);
when(mockHoldService.getHolds(filePlanRef)).thenReturn(holds);
when(mockNodeService.getChildAssocs(holdRef, ASSOC_FROZEN_CONTENT, RegexQNamePattern.MATCH_ALL)).thenReturn(childAssocs);
when(mockNodeService.getChildAssocs(holdRef, ASSOC_FROZEN_CONTENT, RegexQNamePattern.MATCH_ALL, Integer.MAX_VALUE, false))
.thenReturn(childAssocs);
when(childAssociationRef.getChildRef()).thenReturn(heldItemRef);
// setup retrying transaction helper
Answer<Object> doInTransactionAnswer = new Answer<Object>()
{
@SuppressWarnings("rawtypes")
@Override
public Object answer(InvocationOnMock invocation) throws Throwable
{
RetryingTransactionCallback callback = (RetryingTransactionCallback) invocation.getArguments()[0];
// when(childAssociationRef.getChildRef()).thenReturn(heldItemRef);
return callback.execute();
}
};
doAnswer(doInTransactionAnswer).when(mockRetryingTransactionHelper)
.<Object> doInTransaction(any(RetryingTransactionCallback.class), anyBoolean(), anyBoolean());
when(mockTransactionService.getRetryingTransactionHelper()).thenReturn(mockRetryingTransactionHelper);
patch.applyInternal();
verify(mockNodeService, times(1)).addChild(holdRef, heldItemRef, ASSOC_CONTAINS, PATCH_ASSOC_NAME);
}
@SuppressWarnings("unchecked")
@Test
public void patchRunWithSuccessWhenNoHeldChildren()
{
when(mockFilePlanService.getFilePlans()).thenReturn(fileplans);
when(mockHoldService.getHolds(filePlanRef)).thenReturn(holds);
when(mockNodeService.getChildAssocs(holdRef, ASSOC_FROZEN_CONTENT, RegexQNamePattern.MATCH_ALL)).thenReturn(emptyList());
when(mockNodeService.getChildAssocs(holdRef, ASSOC_FROZEN_CONTENT, RegexQNamePattern.MATCH_ALL, Integer.MAX_VALUE, false))
.thenReturn(emptyList());
// setup retrying transaction helper
Answer<Object> doInTransactionAnswer = new Answer<Object>()
{
@SuppressWarnings("rawtypes")
@Override
public Object answer(InvocationOnMock invocation) throws Throwable
{
RetryingTransactionCallback callback = (RetryingTransactionCallback) invocation.getArguments()[0];
when(childAssociationRef.getChildRef()).thenReturn(heldItemRef);
return callback.execute();
}
};
doAnswer(doInTransactionAnswer).when(mockRetryingTransactionHelper)
.<Object> doInTransaction(any(RetryingTransactionCallback.class), anyBoolean(), anyBoolean());
when(mockTransactionService.getRetryingTransactionHelper()).thenReturn(mockRetryingTransactionHelper);
patch.applyInternal();

View File

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

View File

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

View File

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

View File

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

View File

@@ -21,6 +21,7 @@ package org.alfresco.httpclient;
import java.io.IOException;
import java.util.Map;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
@@ -57,7 +58,12 @@ public class RequestHeadersHttpClient extends HttpClient
if (defaultHeaders != null)
{
defaultHeaders.forEach((k,v) -> {
method.addRequestHeader(k, v);
Header h = method.getRequestHeader(k);
boolean add = h == null || h.getValue() == null || !h.getValue().equals(v);
if (add)
{
method.addRequestHeader(k, v);
}
});
}
}

View File

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

View File

@@ -346,7 +346,7 @@ public class FTSQueryParser
}
constraints.add(constraint);
}
if (constraints.size() == 1 && Occur.EXCLUDE != constraints.get(0).getOccur())
if (constraints.size() == 1)
{
return constraints.get(0);
}

View File

@@ -69,21 +69,20 @@ public class LuceneDisjunction<Q, S, E extends Throwable> extends BaseDisjunctio
@SuppressWarnings("unchecked")
LuceneQueryBuilderComponent<Q, S, E> luceneQueryBuilderComponent = (LuceneQueryBuilderComponent<Q, S, E>) constraint;
Q constraintQuery = luceneQueryBuilderComponent.addComponent(selectors, functionArgs, luceneContext, functionContext);
queriestoDisjoin.add(new Pair<Constraint, Q>(constraint, constraintQuery));
queriestoDisjoin.add(new Pair<>(constraint, constraintQuery));
if (constraintQuery != null)
{
switch (constraint.getOccur())
{
case DEFAULT:
case MANDATORY:
case OPTIONAL:
expressionBuilder.addOptional(constraintQuery, constraint.getBoost());
break;
case MANDATORY:
expressionBuilder.addRequired(constraintQuery, constraint.getBoost());
break;
case EXCLUDE:
QueryParserExpressionAdaptor<Q, E> subExpressionBuilder = luceneContext.getLuceneQueryParserAdaptor().getExpressionAdaptor();
subExpressionBuilder.addRequired(luceneContext.getLuceneQueryParserAdaptor().getMatchAllNodesQuery());
subExpressionBuilder.addExcluded(constraintQuery);
expressionBuilder.addOptional(subExpressionBuilder.getQuery(), constraint.getBoost());
expressionBuilder.addExcluded(constraintQuery, constraint.getBoost());
break;
}
}

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
# 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:tomcat9-jre17-rockylinux8-202209131110
FROM alfresco/alfresco-base-tomcat:tomcat9-jre17-rockylinux8-202209261711
# Set default docker_context.
ARG resource_path=target

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,495 @@
![in progress](https://img.shields.io/badge/Document_Level-In_Progress-yellow.svg?style=flat-square)
:paw_prints: Back to [TAS Master Documentation](https://gitlab.alfresco.com/tas/documentation/wikis/home)
---
## Table of Contents
* [Synopsis](#synopsis)
* [Prerequisite](#prerequisite)
* [Installation](#installation-if-you-want-to-contribute)
* [Package Presentation](#package-presentation)
* [Sample Usage](#sample-usage)
* [How to write a test](#how-to-write-a-test)
* [How to run tests?](#how-to-run-tests)
* [from IDE](#from-ide)
* [from command line](#from-command-line)
* [Perform CMIS Queries](#perform-cmis-queries)
* [Listeners](#listeners)
* [Test Results](#test-results)
* [Test Rail Integration](#test-rail-integration)
* [Configuration](#configuration)
* [How to enable Test Rail Integration?](#how-to-enable-test-rail-integration)
* [Change Log](docs/CHANGELOG.md) :glowing_star:
* [Reference](#reference)
* [Releasing](#releasing)
* [Contributors](#contributors)
* [License](#license)
## Synopsis
**TAS**( **T**est **A**utomation **S**ystem)- **CMIS** is the project that handles the automated tests related only to CMIS API integrated with Alfresco One [Alfresco CMIS API](http://docs.alfresco.com/5.1/pra/1/topics/cmis-welcome.html).
It is based on Apache Maven, compatible with major IDEs and is using also Spring capabilities for dependency injection.
As a high level overview, this project makes use of the following functionality useful in automation testing as:
* reading/defining test environment settings (e.g. alfresco server details, authentication, etc.)
* managing resource (i.e. creating files and folders)
* test data generators (for site, users, content, etc)
* helpers (i.e. randomizers, test environment information)
* test logging generated on runtime and test reporting capabilities
* test management tool integration (at this point we support integration with [Test Rail](https://alfresco.testrail.net) (v5.2.1)
* health checks (verify if server is reachable, if server is online)
* generic Internal-DSL (Domain Specific Language)
Using Nexus -Release Repository, everyone will be able to use individual interfaces in their projects by extending the automation core functionalities.
**[Back to Top ^](#table-of-contents)**
## Prerequisite
(tested on unix/non-unix distribution)
* [Java SE 1.8](http://www.oracle.com/technetwork/java/javase/downloads/index.html).
* [Maven 3.3](https://maven.apache.org/download.cgi) installed and configure according to [Windows OS](https://maven.apache.org/guides/getting-started/windows-prerequisites.html) or [Mac OS](https://maven.apache.org/install.html).
* Configure Maven to use Alfresco alfresco-internal repository following this [Guide](https://ts.alfresco.com/share/page/site/eng/wiki-page?title=Maven_Setup).
* Your favorite IDE as [Eclipse](https://eclipse.org/downloads/) or [IntelliJ](https://www.jetbrains.com/idea).
* Access to [Nexus](https://nexus.alfresco.com/nexus/) repository.
* Access to GitLab [TAS](https://gitlab.alfresco.com/tas/) repository.
* GitLab client for your operating system. (we recommend [SourceTree](https://www.sourcetreeapp.com) - use your google account for initial setup).
* Getting familiar with [Basic Git Commands](http://docs.gitlab.com/ee/gitlab-basics/basic-git-commands.html).
* Getting familiar with [Maven](https://maven.apache.org/guides/getting-started/maven-in-five-minutes.html).
* Getting familiar with [Spring](http://docs.spring.io).
* Getting familiar with [TestNG](http://testng.org/doc/index.html)
**[Back to Top ^](#table-of-contents)**
## Installation (if you want to contribute)
* Open your GitLab client and clone the repository of this project.
* You can do this also from command line (or in your terminal) adding:
```bash
$ git clone https://gitlab.alfresco.com/tas/alfresco-tas-cmis-test.git
# this clone will have the latest changes from repository. If you want to checkout a specific version released, take a look at the [Change Log](docs/CHANGELOG.md) page
$ cd alfresco-tas-cmis-test
# this command will checkout the remove v1.0.0 tagged repository and create locally a new branch v1.0.0
$ git checkout tags/v1.0.0 -b v1.0.0
```
* Install and check if all dependencies are downloaded
```bash
$ mvn clean install -DskipTests
# you should see one [INFO] BUILD SUCCESS message displayed
```
**[Back to Top ^](#table-of-contents)**
## Package Presentation
The project uses a maven layout [archetype](https://maven.apache.org/plugins-archives/maven-archetype-plugin-1.0-alpha-7/examples/simple.html):
```ruby
├── pom.xml
├── src
   ├── main
      └── java
      └── org
      └── alfresco
      └── cmis
      ├── (...)
      ├── CmisProperties.java #handles all properties from default.properties
      ├── CmisWrapper.java #wrapper around CMIS API
      └── exception
      └── (...)
   ├── test
      ├── java
         └── org
         └── alfresco
         └── cmis
         ├── CmisDemoTests.java #demo example
         └── CmisTest.java #abstract base class that should be inherited by all tests
      └── resources
      ├── alfresco-cmis-context.xml #spring configuration
      ├── default.properties #all settings related to environment, protocol
      ├── log4j.properties
      └── sanity-cmis.xml # default suite of tests
```
**[Back to Top ^](#table-of-contents)**
## Sample Usage
Following the standard layout for Maven projects, the application sources locate in src/main/java and test sources locate in src/test/java.
Application sources consist in defining the CMIS object that simulates the API calls.
The tests are based on an abstract object: CmisTest.java that handles the common behavior: checking the health status of the test server, configuration settings, getting the general properties, etc.
Please take a look at [CmisDemoTests.java](src/test/java/org/alfresco/cmis/CmisDemoTests.java) class for an example.
Common configuration settings required for this project are stored in properties file, see [default.properties](src/test/resources/default.properties).
Please analyze and update it accordingly with Alfresco test server IP, port, credentials, etc.
Example:
```java
# Alfresco HTTP Server Settings
alfresco.scheme=http
alfresco.server=<add-here-the-ip-of-your-test-server>
alfresco.port=<default-port-for-alfresco-not-share>
```
* optional update the logging level in [log4j](src/test/resources/log4j.properties) file (you can increase/decrease the deails of the [logging file](https://logging.apache.org/log4j/1.2/manual.html), setting the ```log4j.rootLogger=DEBUG``` if you want.)
* go to [running](#how-to-run-tests) section for more information on how to run this tests.
**[Back to Top ^](#table-of-contents)**
### How to write a test
* Tests are organized in java classes and located on src/test/java as per maven layout.
* One test class should contain the tests that cover one functionality as we want to have a clear separation of test scope: tests for sanity/core/full, tests that verify manage of folder/files etc.
* These are the conventions that need to follow when you write a test:
* The test class has @Test annotation with the group defined: protocols, cmis. You can add more groups like sanity, regression
```java
@Test(groups={ "sanity"}
```
* The test has @TestRail annotation in order to assure that the details and results will be submitted on TestRail. The fields for TestRail annotation will be explained on next chapter.
```java
@TestRail(section = { "cmis-api" }, executionType=ExecutionType.SANITY,
description = "Verify admin user creates folder in DocumentLibrary with CMIS")
public void adminShouldCreateFolderInSite() throws Exception
{ cmisApi.usingSite(testSite).createFolder(testFolder).assertExistsInRepo(); }
```
* Use Spring capabilities to initialize the objects(Models, Wrappers) with @Autowired
* We followed Builder pattern to develop specific DSL for simple and clear usage of protocol client in test:
```java
cmisApi.usingSite(testSite) .createFolder(testFolder) .assertExistsInRepo();
```
* To view a simple class that is using this utility, just browse on [CmisDemoTests.java](src/test/java/org/alfresco/cmis/CmisDemoTests.java)
Notice the class definition and inheritance value:
```java
public class CmisDemoTests extends CmisTest
```
* as a convention, before running your test, check if the test environment is reachable and your alfresco test server is online.
(this will stop the test if the server defined in your property file is not healthy - method available in parent class)
```java
@BeforeClass(alwaysRun = true)
public void setupCmisTest() throws Exception {
serverHealth.assertServerIsOnline();
}
```
* the test name are self explanatory:
```java
@TestRail(section = { "cmis-api" }, executionType=ExecutionType.SANITY, description = "Verify admin user creates folder in DocumentLibrary with CMIS")
public void adminShouldCreateFolderInSite() throws Exception
{
cmisApi.usingSite(testSite)
.createFolder(testFolder)
.assertExistsInRepo();
}
```
```java
@TestRail(section = { "cmis-api" }, executionType=ExecutionType.SANITY, description = "Verify admin user creates and renames folder in DocumentLibrary with CMIS")
public void adminShouldRenameFolderInSite() throws Exception
{
cmisApi.usingSite(testSite).createFolder(testFolder)
.and().rename("renamed")
.assertExistsInRepo();
}
```
**[Back to Top ^](#table-of-contents)**
### How to run tests
#### from IDE
* The project can be imported into a development environment tool (Eclipse or IntelliJ). You have the possibility to execute tests or suite of tests using [TestNG plugin](http://testng.org/doc/eclipse.html) previously installed in IDE.
From Eclipse, just right click on the testNG class (something similar to [CmisDemoTests.java](src/test/java/org/alfresco/cmis/CmisDemoTests.java)), select Run As - TestNG Test
You should see your test passed.
* In case you are using the default settings that points to localhost (127.0.0.1) and you don't have Alfresco installed on your machine, you will see one exception thrown (as expected):
```java
org.alfresco.utility.exception.ServerUnreachableException: Server {127.0.0.1} is unreachable.
```
#### from command line
* In terminal or CMD, navigate (with CD) to root folder of your project (you can use the sample project):
The tests can be executed on command line/terminal using Maven command
```bash
mvn test
```
This command with trigger the tests specified in the default testNG suite from POM file: <suiteXmlFile>src/main/resources/shared-resources/cmis-suites.xml</suiteXmlFile>
You can use -Dtest parameter to run the test/suites through command line (http://maven.apache.org/surefire/maven-surefire-plugin/examples/single-test.html).
You can also specify a different suiteXMLFile like:
```bash
mvn test -DsuiteXmlFile=src/resources/your-custom-suite.xml
```
Or even a single test:
```bash
mvn test -Dtest=org.alfresco.cmis.CmisDemoTests
```
But pay attention that you will not have enabled all the [listeners](#listeners) in this case (the Reporting listener or TestRail integration one)
### Perform CMIS Queries
(:glowing_star: please notice that at this point we assert only the results count returned by the query: we plan to extend the functionality to assert on QueryResult iterable objects also: simple modification on [QueryExecutor.java](src/main/java/org/alfresco/cmis/dsl/QueryExecutor.java)
There are a couple of ways to test the results count after performing CMIS queries, choose the one that you like the most:
a) direct queries using a simple TestNG test:
(see example [here](src/test/java/org/alfresco/cmis/search/SorlSearchSimpleQueryTests.java))
```java
public class SorlSearchSimpleQueryTests extends CmisTest
{
@Test
public void simpleQueryOnFolderDesc() throws Exception
{
// create here multiple folder as data preparation
cmisApi.authenticateUser(dataUser.getAdminUser())
.withQuery("SELECT * FROM cmis:folder ORDER BY cmis:createdBy DESC").assertResultsCount().isLowerThan(101);
}
}
```
- just extend CmisTest
- authenticate with your UserModel and perform the query. The DSL will allow you to assert the result count if is equal, lower or greater than to a particular value. You can update the methods in [QueryResultAssertion](src/main/java/org/alfresco/cmis/dsl/QueryExecutor.java) class.
b) define one set of test data (folders, files, etc. ) that you will search in all tests then execute all CMIS queris from one common XML file
- see test class [SolrSearchInFolderTests](src/test/java/org/alfresco/cmis/search/SolrSearchInFolderTests.java)
- see [XML test data](src/main/resources/shared-resources/testdata/search-in-folder.xml) used in [SolrSearchInFolderTests](src/test/java/org/alfresco/cmis/search/SolrSearchInFolderTests.java) into one DataProvider. Notice that XML file has two parameter: the query that will be executed and the expected result count returned.
c) define test data (user, sites, folder, files, aspects, comments, custom models, etc) all into one XML file with all cmis queries related.
- see example on [SolrSearchByIdTests](https://gitlab.alfresco.com/tas/alfresco-tas-cmis-test/blob/master/src/test/java/org/alfresco/cmis/search/SolrSearchByIdTests.java)
- notice the 'NODE_REF[x]'; 'NODE_REF[y]' keywords that will dynamically take the test data identified by id: x, y (you will figure it out based on examples).
**Info**: all search test queries are found [org.alfresco.cmis.search](src/test/java/org/alfresco/cmis/search) package.
**[Back to Top ^](#table-of-contents)**
## Listeners
With the help of Listeners we can modify the behaviour of TestNG framework. There are a lot of testNG listener interfaces that we can override in order to provide new functionalities.
The tas framework provides out of the box a couple of listeners that you could use. These could be enabled and added at the class level or suite level.
### a) org.alfresco.utility.report.ReportListenerAdapter
* if added at the class level:
```java
@Listeners(value=ReportListenerAdapter.class)
public class MyTestClass extends CmisTest
{
(...)
}
```
* or suite xml level
```java
<suite name="Your Suite test" parallel="classes">
<listeners>
<listener class-name="org.alfresco.utility.report.ReportListenerAdapter"></listener>
</listeners>
(...)
</suite>
```
It will automatically generate one html named "report.html" in ./target/report folder.
Please also take a look at [Test Results](#test-results) section.
### b) org.alfresco.utility.testrail.TestRailExecutorListener
It will automatically update Test Rail application with the test cases that you've automated.
Please take a look at [Test Rail Integration](#test-rail-integration) section for more details.
### c) org.alfresco.utility.report.log.LogsListener
This is a new listener that will generate further details in one XML format of the automated test steps that you will write.
Example:
```java
public void myDSLMethod1()
{
STEP("Lorem ipsum dolor sit amet");
//code for first step
STEP("consectetur adipiscing elit");
//code for the next description
}
public void myDSLMethod2()
{
STEP("sed do eiusmod tempor incididunt ut labore");
//code for first step
STEP("et dolore magna aliqua");
//code for the next description
}
```
If these methods will be executed insite a test method, all those steps will be automatically logged in the XML report generated.
Example:
```java
@Test
public void adminShouldCreateFileInSite()
{
myDSLMethod1();
myDSLMethod2()
}
```
So if "testingSomething" will be executed this is what you will see on the XML file generated. (please take a look at [Test Results](#test-results) section for defining the defaul location)
Here is one example of XML file generated with these steps:
![](docs/pics/xml-steps-report.JPG)
**[Back to Top ^](#table-of-contents)**
## Test Results
We already executed a couple of tests using command line as indicated above. Sweet! Please take a look at [sanity-cmis.xml](src/test/resources/sanity-cmis.xml) one more time.
You will see there that we have one listener added:
```java
<listener class-name="org.alfresco.utility.report.ReportListenerAdapter"></listener>
```
This will tell our framework, after we run all tests, to generate one HTML report file with graphs and metrics.
Take a look at the target/reports folder (created after running the tests) and open the report.html file.
![](docs/pics/html-report-sample.JPG)
Playing with this report, you will notice that you will be able to:
* search tests cases by name
* filter test cases by errors, labels, groups, test types, date when it was executed, protocol used, etc.
* view overall pass/fail metrics of current test suite, history of tests execution, etc.
The report path can be configured in default.properties):
```
# The location of the reports path
reports.path=your-new-location-of-reports
```
**[Back to Top ^](#table-of-contents)**
## Test Rail Integration
Alfresco is using now https://alfresco.testrail.net (v5.3.0.3601).
We aim to accelerate the delivery of automated test by minimizing the interaction with the test management tool - TestRail. In this scope we developed the following capabilities:
* creating automatically the manual tests in TestRail
* submitting the test results (with stack trace) after each execution into TestRail Test Runs
* adding the test steps for each test.
### Configuration
In order to use Test Rail Integration you will need to add a couple of information in [default.properties](src/test/resources/default.properties) file:
(the document is pretty self explanatory)
```java
# Example of configuration:
# ------------------------------------------------------
# testManagement.endPoint=https://alfresco.testrail.com/
# testManagement.username=<yourusername-that-you-connect-to-testrail>
# testManagement.apiKey=<api-key>
# testManagement.project=<id-of-your-project
# testManagement.testRun=<test-run-name>
```
!This settings are already defined in default.properties for you.
For generating a new API Key take a look at the official documentation, TestRail [APIv2](http://docs.gurock.com/testrail-api2)
* _testManagement.project= **<id-of-your-project**_ this is the ID of the project where you want to store your test cases.
If you want to use [Alfresco ONE](https://alfresco.testrail.net/index.php?/projects/overview/1) project in TestRail, open that project and notice the URL, after "/overview/**1**" link you will see the ID of the project (1 in this case)
If you want to use [TAS Project](https://alfresco.testrail.net/index.php?/projects/overview/7) you will notice the ID 7, so _"testManagement.project=7"_
* "_testManagement.testRun=<test-run-name>_" this represents the name of the Test Run from your project.
* In Test Rail, navigating to Test Runs & Results, create a new Test Run and include all/particular test cases. If this test run name is "Automation", update _testManagement.testRun= **Automation**_.
All test results will be updated only on this test run at runtime as each test is executed by TAS framework.
### How to enable Test Rail Integration?
We wanted to simplify the Test Rail integration, so we used listeners in order to enable/disable the integration of Test Rail.
* first configure your default.properties as indicated above
* now on your TestNG test, add the @TestRail annotation, so let's say you will have this test:
```java
@Test(groups="sample-tests")
public void thisAutomatedTestWillBePublishedInTestRail()
{
}
```
add now @TestRail integration with mandatory field ```section```. This means that this tests annotated, will be uploaded in TestRail:
```java
@Test(groups="sample-tests")
@TestRail(section = { "protocols", "TBD" })
public void thisAutomatedTestWillBePublishedInTestRail()
{
}
```
The section field, represents an array of strings, the hierarchy of sections that SHOULD be found on TestRail under the project you've selected in default.properties. Follow the TestRail [user-guide](http://docs.gurock.com/testrail-userguide/start) for more information regarding sections.
In our example we created in Test Rail one root section "protocols" with a child section: "TBD" (you can go further and add multiple section as you wish)
* now, lets add the listener, the TestRailExecutorListener that will handle this TC Management interaction.
This listener can be added at the class level or suite level (approach that we embrace)
Take a look at [sanity-cmis.xml](src/test/resources/sanity-cmis.xml) for further example.
```xml
<listeners>
<listener class-name="org.alfresco.utility.testrail.TestRailExecutorListener"></listener>
(...)
</listeners>
```
Right click on cmis-suites.xml file and run it, or just "mvn test" from root if this sample project.
After everything passes, go in Test Rail, open your project and navigate to "Test Cases" section. Notice that under protocols/TBD section, you will see your test case published.
If you defined also the "testManagement.testRun" correctly, you will see under Test Runs, the status of this case marked as passed.
The @TestRail annotation offers also other options like:
- "description" this is the description that will be updated in Test Rail for your test case
- "testType", the default value is set to Functional test
- "executionType", default value is set to ExecutionType.REGRESSION, but you can also use ExecutionType.SMOKE, ExecutionType.SANITY, etc
Take a look at the demo scenarios in this project for further examples.
**[Back to Top ^](#table-of-contents)**
## Reference
* For any improvements, bugs, please use Jira - [TAS](https://issues.alfresco.com/jira/browse/TAS) project.
* Setup the environment using [docker](https://gitlab.alfresco.com/tas/alfresco-docker-provisioning/blob/master/Readme.md).
* [Bamboo Test Plan](https://bamboo.alfresco.com/bamboo/browse/TAS-CMIS)
## Contributors
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other... [more](CODE_OF_CONDUCT.md)
## Releasing
Any commit done on this project should be automatically executed by [TAS Build Plan](https://bamboo.alfresco.com/bamboo/browse/TAS-TAS)
If the build passes, then you didn't broke anything.
If you want to perform a release, open [TAS-CMIS](https://bamboo.alfresco.com/bamboo/browse/TAS-CMIS) Bamboo Build.
Run the Default stage and if it passes, then manually perform the Release stage (this will auto-increment the version in pom.xml)
## License
TBD

View File

@@ -0,0 +1,20 @@
:paw_prints: Back to Utility [README](README.md).
---
# Change Log
All notable changes to this project will be documented in this file.
Each tag bellow has a corresponded version released in [Nexus](https://nexus.alfresco.com/nexus/#welcome).
Currently we are testing [CMIS v1.1](http://docs.oasis-open.org/cmis/CMIS/v1.1/CMIS-v1.1.html) with Alfresco One.
(if you need to update/polish tests please branch from the release tags)
## [[v5.2.0-1] - 2016-12-12](/tas/alfresco-tas-cmis-test/commits/v5.2.0-1)
### TBD
## [[v5.2.0-0] - 2016-12-12](/tas/alfresco-tas-cmis-test/commits/v5.2.0-0)
- works with 5.2 alfresco
- 100% Core tests for CMIS
- 100% Sanity test for CMIS
- use released v1.0.7 utility

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@@ -1,27 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.alfresco.tas</groupId>
<artifactId>alfresco-community-repo-cmis-test</artifactId>
<name>cmis test</name>
<packaging>jar</packaging>
<artifactId>cmis</artifactId>
<name>alfresco-tas-cmis</name>
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-tests</artifactId>
<version>17.125</version>
<version>17.155</version>
</parent>
<developers>
<developer>
<name>Paul Brodner</name>
<roles>
<role>Test Automation Architect</role>
</roles>
</developer>
</developers>
<organization>
<name>Alfresco Software</name>
<url>http://www.alfresco.com/</url>
</organization>
<properties>
<maven.build.sourceVersion>11</maven.build.sourceVersion>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<chemistry-opencmis-commons-api>1.1.0</chemistry-opencmis-commons-api>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<maven-release.version>2.5.3</maven-release.version>
<java.version>11</java.version>
<suiteXmlFile>${project.basedir}/src/test/resources/cmis-suite.xml</suiteXmlFile>
<cmis.binding />
<cmis.basePath />
@@ -58,10 +58,29 @@
</profiles>
<dependencies>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jackson2-provider</artifactId>
<version>4.7.1.Final</version>
</dependency>
<!-- alfresco tester settings -->
<dependency>
<groupId>org.alfresco.tas</groupId>
<artifactId>cmis</artifactId>
<scope>test</scope>
<artifactId>utility</artifactId>
<exclusions>
<exclusion>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- open cmis settings -->
<dependency>
<groupId>org.apache.chemistry.opencmis</groupId>
<artifactId>chemistry-opencmis-commons-api</artifactId>
<version>${chemistry-opencmis-commons-api}</version>
</dependency>
</dependencies>

View File

@@ -0,0 +1,130 @@
package org.alfresco.cmis;
import org.alfresco.utility.data.AisToken;
import org.alfresco.utility.data.auth.DataAIS;
import org.alfresco.utility.model.UserModel;
import org.apache.chemistry.opencmis.commons.SessionParameter;
import org.keycloak.authorization.client.util.HttpResponseException;
import org.keycloak.representations.AccessTokenResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import static org.alfresco.utility.report.log.Step.STEP;
@Service
public class AuthParameterProviderFactory
{
public static String STEP_PREFIX = "CMIS AuthParameterProvider:";
@Autowired
private DataAIS dataAIS;
@Autowired
private CmisProperties cmisProperties;
/**
*
* The default provider uses AIS if support for Alfresco Identity Service is enabled.
* Otherwise a provider which uses Basic authentication is returned.
*
* @return Function which takes a {@link UserModel} and returns a map of
* authentication parameters to be used with {@link CmisWrapper#authenticateUser(UserModel, Function)}
*/
public Function<UserModel, Map<String, String>> getDefaultProvider()
{
if (dataAIS.isEnabled())
{
STEP(String.format("%s Retrieved default AIS auth parameter provider.", STEP_PREFIX));
return new AisAuthParameterProvider();
}
else
{
STEP(String.format("%s Retrieved default Basic auth parameter provider.", STEP_PREFIX));
return new BasicAuthParameterProvider();
}
}
public Function<UserModel, Map<String, String>> getAISProvider()
{
return new AisAuthParameterProvider();
}
public Function<UserModel, Map<String, String>> getBasicProvider()
{
return new BasicAuthParameterProvider();
}
private class BasicAuthParameterProvider implements Function<UserModel, Map<String, String>>
{
@Override
public Map<String, String> apply(UserModel userModel)
{
STEP(String.format("%s Using Basic auth parameter provider.", STEP_PREFIX));
Map<String, String> parameters = new HashMap<>();
parameters.put(SessionParameter.USER, userModel.getUsername());
parameters.put(SessionParameter.PASSWORD, userModel.getPassword());
return parameters;
}
}
private class AisAuthParameterProvider implements Function<UserModel, Map<String, String>>
{
@Override
public Map<String, String> apply(UserModel userModel)
{
Map<String, String> parameters = new HashMap<>();
STEP(String.format("%s Using AIS auth parameter provider.", STEP_PREFIX));
AisToken aisToken = getAisAccessToken(userModel);
parameters.put(SessionParameter.AUTHENTICATION_PROVIDER_CLASS, "org.apache.chemistry.opencmis.client.bindings.spi.OAuthAuthenticationProvider");
parameters.put(SessionParameter.OAUTH_ACCESS_TOKEN, aisToken.getToken());
parameters.put(SessionParameter.OAUTH_REFRESH_TOKEN, aisToken.getRefreshToken());
parameters.put(SessionParameter.OAUTH_EXPIRATION_TIMESTAMP, String.valueOf(System.currentTimeMillis()
+ (aisToken.getExpiresIn() * 1000))); // getExpiresIn is in seconds
parameters.put(SessionParameter.OAUTH_TOKEN_ENDPOINT, cmisProperties.aisProperty().getAdapterConfig().getAuthServerUrl()
+ "/realms/alfresco/protocol/openid-connect/token");
parameters.put(SessionParameter.OAUTH_CLIENT_ID, cmisProperties.aisProperty().getAdapterConfig().getResource());
return parameters;
}
/**
* Returns a valid access token for valid user credentials in userModel.
* An invalid access token is returned for invalid user credentials,
* which can be used for tests involving non existing or unauthorized users.
* @param userModel
* @return
*/
private AisToken getAisAccessToken(UserModel userModel)
{
String badToken = "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJUazFPZ2JqVlo1UEw2bmtsNWFvTUlacTZ4cW9PZzc5WGtzdnJTTUcxLUFZIn0.eyJqdGkiOiI3NTVkMGZiOS03NzI5LTQ1NzYtYWM4Ny1hZWZjZWNiZDE0ZGEiLCJleHAiOjE1NTM2MjQ1NDgsIm5iZiI6MCwiaWF0IjoxNTUzNjI0MjQ4LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0L2F1dGgvcmVhbG1zL2FsZnJlc2NvIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6Ijk4NDE0Njg4LTUwMDUtNDVmOS05YTVjLTlkMDRlODMyYTNkMiIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFsZnJlc2NvIiwiYXV0aF90aW1lIjowLCJzZXNzaW9uX3N0YXRlIjoiNjJlN2U5YzktZmFlNS00N2RhLTk5MDItMTZjYTJhZWUwMWMwIiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwOi8vbG9jYWxob3N0KiIsImh0dHBzOi8vbG9jYWxob3N0KiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoidXNlci12eGlrcXd3cG5jYmpzeHgifQ.PeLGCNCzj-P2m0knwUU9Vfx4dzLLQER9IdV7GyLel9LRN-3J9nh7GBDRQsyDJ0pqhObQyMg4V3wSsrsXRQ6gKhmUyDemmD-w1YMC2a2HKX6GlxsTEF_f1K_R15lIQOawNVErlWjZWORJGCvCYZOJ99SOmeOC6PGY79zLL94MMnf6dXcegePPMOKG-59eNjBkOylTipYebvM40nbbKrS5vzNHQlvUh4ALFeBoMSKGnLSjQd06Dj4SWojG0p1BrxurqDjW0zz6pQlEAm4vcWApRZ6qBLZcMH8adYix07zCDb87GOn1pmfEBWpwd3BEgC_LLu06guaCPHC9tpeIaDTHLg";
String badRefreshToken = "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJmM2YyMjhjYS1jMzg5LTQ5MGUtOGU1Zi02YWI1MmJhZDVjZGEifQ.eyJqdGkiOiIyNmExZWNhYy00Zjk0LTQwYzctYjJjNS04NTlhZmQ3NjBiYWMiLCJleHAiOjE1NTM2MjYwNDgsIm5iZiI6MCwiaWF0IjoxNTUzNjI0MjQ4LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0L2F1dGgvcmVhbG1zL2FsZnJlc2NvIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdC9hdXRoL3JlYWxtcy9hbGZyZXNjbyIsInN1YiI6Ijk4NDE0Njg4LTUwMDUtNDVmOS05YTVjLTlkMDRlODMyYTNkMiIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJhbGZyZXNjbyIsImF1dGhfdGltZSI6MCwic2Vzc2lvbl9zdGF0ZSI6IjYyZTdlOWM5LWZhZTUtNDdkYS05OTAyLTE2Y2EyYWVlMDFjMCIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJwcm9maWxlIGVtYWlsIn0.lRBJQc7tj0rk7JBC0zpM0dDdZgDKjm9wcxP8nzLnXe4";
AisToken aisToken;
try
{
// Attempt to get an access token for userModel from AIS
aisToken = dataAIS.perform().getAccessToken(userModel);
}
catch (HttpResponseException e)
{
// Trying to authenticate with invalid user credentials so return an invalid access token
if (e.getStatusCode() == 401)
{
STEP(String.format("%s Invalid user credentials were provided %s:%s. Using invalid token for reqest.",
STEP_PREFIX, userModel.getUsername(), userModel.getPassword()));
aisToken = new AisToken(badToken, badRefreshToken, System.currentTimeMillis(), 300000);
}
else
{
throw e;
}
}
return aisToken;
}
}
}

View File

@@ -0,0 +1,64 @@
package org.alfresco.cmis;
import org.alfresco.utility.TasAisProperties;
import org.alfresco.utility.TasProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
@Configuration
@PropertySource("classpath:default.properties")
@PropertySource(value = "classpath:${environment}.properties", ignoreResourceNotFound = true)
public class CmisProperties
{
@Autowired
private TasProperties properties;
@Autowired
private TasAisProperties aisProperties;
public TasProperties envProperty()
{
return properties;
}
public TasAisProperties aisProperty()
{
return aisProperties;
}
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer()
{
return new PropertySourcesPlaceholderConfigurer();
}
@Value("${cmis.binding}")
private String cmisBinding;
@Value("${cmis.basePath}")
private String basePath;
public String getCmisBinding()
{
return cmisBinding;
}
public String getBasePath()
{
return basePath;
}
public void setBasePath(String basePath)
{
this.basePath = basePath;
}
public void setCmisBinding(String cmisBinding)
{
this.cmisBinding = cmisBinding;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,185 @@
package org.alfresco.cmis.dsl;
import static org.alfresco.utility.report.log.Step.STEP;
import java.util.Iterator;
import java.util.List;
import org.alfresco.cmis.CmisWrapper;
import org.alfresco.utility.LogFactory;
import org.apache.chemistry.opencmis.client.api.ItemIterable;
import org.apache.chemistry.opencmis.client.api.ObjectType;
import org.apache.chemistry.opencmis.client.api.Tree;
import org.apache.chemistry.opencmis.client.runtime.objecttype.ObjectTypeHelper;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.slf4j.Logger;
import org.testng.Assert;
/**
* DSL for preparing calls on getting the type children of a type.
*/
public class BaseObjectType
{
private CmisWrapper cmisAPI;
private String baseTypeID;
private boolean includePropertyDefinition = false;
private Logger LOG = LogFactory.getLogger();
public BaseObjectType(CmisWrapper cmisAPI, String baseTypeID)
{
this.cmisAPI = cmisAPI;
this.baseTypeID = baseTypeID;
}
public BaseObjectType withPropertyDefinitions()
{
this.includePropertyDefinition = true;
return this;
}
public BaseObjectType withoutPropertyDefinitions()
{
this.includePropertyDefinition = false;
return this;
}
/**
* Example of objectTypeID:
* "D:trx:transferReport" - see {@link ObjectTypeHelper} "D:trx:tempTransferStore"
* "D:imap:imapAttach"
*
* @param objectTypeID
*/
public PropertyDefinitionObject hasChildren(String objectTypeID)
{
return checkChildren(objectTypeID, true);
}
/**
* Example of objectTypeID:
* "D:trx:transferReport" - see {@link ObjectTypeHelper} "D:trx:tempTransferStore"
* "D:imap:imapAttach"
*
* @param objectTypeID
*/
public CmisWrapper doesNotHaveChildren(String objectTypeID)
{
checkChildren(objectTypeID, false);
return cmisAPI;
}
/**
* Example of objectTypeID:
* "D:trx:transferReport" - see {@link ObjectTypeHelper} "D:trx:tempTransferStore"
* "D:imap:imapAttach"
*
* @param objectTypeID
*/
private PropertyDefinitionObject checkChildren(String objectTypeID, boolean exist)
{
ItemIterable<ObjectType> values = cmisAPI.withCMISUtil().getTypeChildren(this.baseTypeID, includePropertyDefinition);
boolean foundChild = false;
PropertyDefinitionObject propDefinition = null;
for (Iterator<ObjectType> iterator = values.iterator(); iterator.hasNext();)
{
ObjectType type = (ObjectType) iterator.next();
LOG.info("Found child Object Type: {}", ToStringBuilder.reflectionToString(type, ToStringStyle.MULTI_LINE_STYLE));
if (type.getId().equals(objectTypeID))
{
foundChild = true;
propDefinition = new PropertyDefinitionObject(type);
break;
}
}
Assert.assertEquals(foundChild, exist,
String.format("Object Type with ID[%s] is found as children for Parent Type: [%s]", objectTypeID, this.baseTypeID));
return propDefinition;
}
public class PropertyDefinitionObject
{
ObjectType type;
public PropertyDefinitionObject(ObjectType type)
{
this.type = type;
}
public PropertyDefinitionObject propertyDefinitionIsEmpty()
{
STEP(String.format("%s Verify that property definitions map is empty.", CmisWrapper.STEP_PREFIX));
Assert.assertTrue(type.getPropertyDefinitions().isEmpty(), "Property definitions is empty.");
return this;
}
public PropertyDefinitionObject propertyDefinitionIsNotEmpty()
{
STEP(String.format("%s Verify that property definitions map is not empty.", CmisWrapper.STEP_PREFIX));
Assert.assertFalse(type.getPropertyDefinitions().isEmpty(), "Property definitions is not empty.");
return this;
}
}
private CmisWrapper checkDescendents(int depth, boolean exist, String... objectTypeIDs)
{
List<Tree<ObjectType>> values = cmisAPI.withCMISUtil().getTypeDescendants(this.baseTypeID, depth, includePropertyDefinition);
for (String objectTypeID : objectTypeIDs)
{
boolean foundChild = false;
for (Tree<ObjectType> tree : values)
{
if (tree.getItem().getId().equals(objectTypeID))
{
foundChild = true;
break;
}
}
Assert.assertEquals(foundChild, exist,
String.format("Assert %b: Descendant [%s] is found as descendant for Type: [%s]", exist, objectTypeID, this.baseTypeID));
if (foundChild)
{
STEP(String.format("%s Cmis object '%s' is found as descendant.", CmisWrapper.STEP_PREFIX, objectTypeID));
}
else
{
STEP(String.format("%s Cmis object '%s' is NOT found as descendant.", CmisWrapper.STEP_PREFIX, objectTypeID));
}
}
return cmisAPI;
}
/**
* Assert that specified descendantType is present in the depth of tree
* Depth can be -1 or >= 1
* Example of objectTypeID:
* "D:trx:transferReport" - see {@link ObjectTypeHelper} "D:trx:tempTransferStore"
* "D:imap:imapAttach"
*
* @param objectTypeID
* @param depth
* @return
*/
public CmisWrapper hasDescendantType(int depth, String... objectTypeIDs)
{
return checkDescendents(depth, true, objectTypeIDs);
}
/**
* Assert that specified descendantType is NOT present in the depth of tree
* Depth can be -1 or >= 1
* Example of objectTypeID:
* "D:trx:transferReport" - see {@link ObjectTypeHelper} "D:trx:tempTransferStore"
* "D:imap:imapAttach"
*
* @param objectTypeID
* @param depth
* @return
*/
public CmisWrapper doesNotHaveDescendantType(int depth, String... objectTypeIDs)
{
checkDescendents(depth, false, objectTypeIDs);
return cmisAPI;
}
}

View File

@@ -0,0 +1,78 @@
package org.alfresco.cmis.dsl;
import org.alfresco.cmis.CmisWrapper;
import org.alfresco.utility.Utility;
import org.apache.chemistry.opencmis.client.api.Document;
import org.apache.chemistry.opencmis.commons.data.ContentStream;
import org.apache.chemistry.opencmis.commons.exceptions.CmisStorageException;
import java.util.Map;
/**
* DSL pertaining only to check in a {@link Document}
*/
public class CheckIn
{
private CmisWrapper cmisWrapper;
private boolean version;
private Map<String, ?> properties;
private String content;
private String comment;
public CheckIn(CmisWrapper cmisWrapper)
{
this.cmisWrapper = cmisWrapper;
}
public CheckIn withMajorVersion()
{
this.version = true;
return this;
}
public CheckIn withMinorVersion()
{
this.version = false;
return this;
}
public CheckIn withContent(String content)
{
this.content = content;
return this;
}
public CheckIn withoutComment()
{
this.comment = null;
return this;
}
public CheckIn withComment(String comment)
{
this.comment = comment;
return this;
}
public CmisWrapper checkIn() throws Exception
{
return checkIn(properties);
}
public CmisWrapper checkIn(Map<String, ?> properties) throws Exception
{
ContentStream contentStream = cmisWrapper.withCMISUtil().getContentStream(content);
try
{
Document pwc = cmisWrapper.withCMISUtil().getPWCDocument();
pwc.refresh();
Utility.waitToLoopTime(2);
pwc.checkIn(version, properties, contentStream, comment);
}
catch(CmisStorageException st)
{
cmisWrapper.withCMISUtil().getPWCDocument().checkIn(version, properties, contentStream, comment);
}
return cmisWrapper;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,761 @@
package org.alfresco.cmis.dsl;
import org.alfresco.cmis.CmisWrapper;
import org.alfresco.cmis.exception.InvalidCmisObjectException;
import org.alfresco.utility.LogFactory;
import org.alfresco.utility.Utility;
import org.alfresco.utility.constants.UserRole;
import org.alfresco.utility.exception.IORuntimeException;
import org.alfresco.utility.model.ContentModel;
import org.alfresco.utility.model.FileModel;
import org.alfresco.utility.model.FolderModel;
import org.alfresco.utility.model.GroupModel;
import org.alfresco.utility.model.UserModel;
import org.apache.chemistry.opencmis.client.api.CmisObject;
import org.apache.chemistry.opencmis.client.api.Document;
import org.apache.chemistry.opencmis.client.api.FileableCmisObject;
import org.apache.chemistry.opencmis.client.api.Folder;
import org.apache.chemistry.opencmis.client.api.ItemIterable;
import org.apache.chemistry.opencmis.client.api.ObjectType;
import org.apache.chemistry.opencmis.client.api.OperationContext;
import org.apache.chemistry.opencmis.client.api.Property;
import org.apache.chemistry.opencmis.client.api.QueryResult;
import org.apache.chemistry.opencmis.client.api.Rendition;
import org.apache.chemistry.opencmis.client.api.SecondaryType;
import org.apache.chemistry.opencmis.client.api.Tree;
import org.apache.chemistry.opencmis.commons.PropertyIds;
import org.apache.chemistry.opencmis.commons.data.Ace;
import org.apache.chemistry.opencmis.commons.data.Acl;
import org.apache.chemistry.opencmis.commons.data.AclCapabilities;
import org.apache.chemistry.opencmis.commons.data.ContentStream;
import org.apache.chemistry.opencmis.commons.data.PermissionMapping;
import org.apache.chemistry.opencmis.commons.data.PropertyData;
import org.apache.chemistry.opencmis.commons.data.RepositoryInfo;
import org.apache.chemistry.opencmis.commons.enums.Action;
import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
import org.apache.chemistry.opencmis.commons.enums.BindingType;
import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisVersioningException;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.testng.collections.Lists;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static org.alfresco.utility.report.log.Step.STEP;
/**
* DSL utility for managing CMIS objects
*/
public class CmisUtil
{
private CmisWrapper cmisAPI;
private Logger LOG = LogFactory.getLogger();
public CmisUtil(CmisWrapper cmisAPI)
{
this.cmisAPI = cmisAPI;
}
/**
* Get cmis object by object id
*
* @param objectId cmis object id
* @return CmisObject cmis object
*/
public CmisObject getCmisObjectById(String objectId)
{
LOG.debug("Get CMIS object by ID {}", objectId);
if (cmisAPI.getSession() == null)
{
throw new CmisRuntimeException("Please authenticate user, call: cmisAPI.authenticate(..)!");
}
if (objectId == null)
{
throw new InvalidCmisObjectException("Invalid content id");
}
return cmisAPI.getSession().getObject(objectId);
}
/**
* Get cmis object by object id with OperationContext
*
* @param objectId cmis object id
* @param context OperationContext
* @return CmisObject cmis object
*/
public CmisObject getCmisObjectById(String objectId, OperationContext context)
{
if (cmisAPI.getSession() == null)
{
throw new CmisRuntimeException("Please authenticate user, call: cmisAPI.authenticate(..)!");
}
if (objectId == null)
{
throw new InvalidCmisObjectException("Invalid content id");
}
return cmisAPI.getSession().getObject(objectId, context);
}
/**
* Get cmis object by path
*
* @param pathToItem String path to item
* @return CmisObject cmis object
*/
public CmisObject getCmisObject(String pathToItem)
{
if (cmisAPI.getSession() == null)
{
throw new CmisRuntimeException("Please authenticate user, call: cmisAPI.authenticate(..)!");
}
if (pathToItem == null)
{
throw new InvalidCmisObjectException("Invalid path set for content");
}
CmisObject cmisObject = cmisAPI.getSession().getObjectByPath(Utility.removeLastSlash(pathToItem));
if (cmisObject instanceof Document)
{
if (!((Document) cmisObject).getVersionLabel().contentEquals("pwc"))
{
// get last version of document
cmisObject = ((Document) cmisObject).getObjectOfLatestVersion(false);
}
else
{
// get pwc document
cmisObject = cmisAPI.getSession().getObject(((Document) cmisObject).getObjectOfLatestVersion(false).getVersionSeriesCheckedOutId());
}
}
return cmisObject;
}
/**
* Get cmis object by path with context
*
* @param pathToItem String path to item
* @param context OperationContext
* @return CmisObject cmis object
*/
public CmisObject getCmisObject(String pathToItem, OperationContext context)
{
if (cmisAPI.getSession() == null)
{
throw new CmisRuntimeException("Please authenticate user!");
}
if (pathToItem == null)
{
throw new InvalidCmisObjectException("Invalid path set for content");
}
CmisObject cmisObject = cmisAPI.getSession().getObjectByPath(Utility.removeLastSlash(pathToItem), context);
if (cmisObject instanceof Document)
{
if (!((Document) cmisObject).getVersionLabel().contentEquals("pwc"))
{
// get last version of document
cmisObject = ((Document) cmisObject).getObjectOfLatestVersion(false, context);
}
else
{
// get pwc document
cmisObject = cmisAPI.getSession().getObject(((Document) cmisObject).getObjectOfLatestVersion(false, context).getVersionSeriesCheckedOutId());
}
}
return cmisObject;
}
/**
* Get Document object for a file
*
* @param path String path to document
* @return {@link Document} object
*/
public Document getCmisDocument(final String path)
{
LOG.debug("Get CMIS Document by path {}", path);
Document d = null;
CmisObject docObj = getCmisObject(path);
if (docObj instanceof Document)
{
d = (Document) docObj;
}
else if (docObj instanceof Folder)
{
throw new InvalidCmisObjectException("Content at " + path + " is not a file");
}
return d;
}
/**
* Get Folder object for a folder
*
* @param path String path to folder
* @return {@link Folder} object
*/
public Folder getCmisFolder(final String path)
{
Folder f = null;
CmisObject folderObj = getCmisObject(path);
if (folderObj instanceof Folder)
{
f = (Folder) folderObj;
}
else if (folderObj instanceof Document)
{
throw new InvalidCmisObjectException("Content at " + path + " is not a folder");
}
return f;
}
/**
* Helper method to get the contents of a stream
*
* @param stream
* @return
* @throws IORuntimeException
*/
protected String getContentAsString(ContentStream stream)
{
LOG.debug("Get Content as String {}", stream);
InputStream inputStream = stream.getStream();
String result;
try
{
result = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
}
catch (IOException e)
{
throw new IORuntimeException(e);
}
IOUtils.closeQuietly(inputStream);
return result;
}
/**
* Copy all the children of the source folder to the target folder
*
* @param sourceFolder
* @param targetFolder
*/
protected void copyChildrenFromFolder(Folder sourceFolder, Folder targetFolder)
{
for (Tree<FileableCmisObject> t : sourceFolder.getDescendants(-1))
{
CmisObject obj = t.getItem();
if (obj instanceof Document)
{
Document d = (Document) obj;
d.copy(targetFolder);
}
else if (obj instanceof Folder)
{
copyFolder((Folder) obj, targetFolder);
}
}
}
/**
* Copy folder with all children
*
* @param sourceFolder source folder
* @param targetFolder target folder
* @return CmisObject of new created folder
*/
public CmisObject copyFolder(Folder sourceFolder, Folder targetFolder)
{
Map<String, Object> folderProperties = new HashMap<String, Object>(2);
folderProperties.put(PropertyIds.NAME, sourceFolder.getName());
folderProperties.put(PropertyIds.OBJECT_TYPE_ID, sourceFolder.getBaseTypeId().value());
Folder newFolder = targetFolder.createFolder(folderProperties);
copyChildrenFromFolder(sourceFolder, newFolder);
return newFolder;
}
protected boolean isPrivateWorkingCopy()
{
boolean result;
try
{
result = getPWCDocument().isVersionSeriesPrivateWorkingCopy();
}
catch (CmisVersioningException cmisVersioningException)
{
result = false;
}
return result;
}
/**
* Returns the PWC (private working copy) ID of the document version series
*/
public Document getPWCDocument()
{
Document document = getCmisDocument(cmisAPI.getLastResource());
String pwcId = document.getVersionSeriesCheckedOutId();
if (pwcId != null)
{
return (Document) cmisAPI.getSession().getObject(pwcId);
}
else
{
throw new CmisVersioningException(String.format("Document %s is not checked out", document.getName()));
}
}
public FileModel getPWCFileModel()
{
Document document = getPWCDocument();
String[] pathTokens = cmisAPI.getLastResource().split("/");
String path = "";
for (int i = 0; i < pathTokens.length - 1; i++)
path = Utility.buildPath(path, pathTokens[i]);
path = Utility.buildPath(path, document.getName());
FileModel fileModel = new FileModel();
fileModel.setName(document.getName());
fileModel.setCmisLocation(path);
return fileModel;
}
protected Folder getFolderParent()
{
return getCmisFolder(cmisAPI.getLastResource()).getFolderParent();
}
/**
* @return List<Action> of allowable actions for the current object
*/
protected List<Action> getAllowableActions()
{
return Lists.newArrayList(getCmisObject(cmisAPI.getLastResource()).getAllowableActions().getAllowableActions());
}
/**
* Returns the requested property. If the property is not available, null is returned
*
* @param propertyId
* @return CMIS Property
*/
protected <T> Property<T> getProperty(String propertyId)
{
CmisObject cmisObject = getCmisObject(cmisAPI.getLastResource());
return cmisObject.getProperty(propertyId);
}
protected List<Rendition> getRenditions()
{
OperationContext operationContext = cmisAPI.getSession().createOperationContext();
operationContext.setRenditionFilterString("*");
CmisObject obj = cmisAPI.getSession().getObjectByPath(cmisAPI.getLastResource(), operationContext);
obj.refresh();
List<Rendition> renditions = obj.getRenditions();
int retry = 0;
while ((renditions == null || renditions.isEmpty()) && retry < Utility.retryCountSeconds)
{
Utility.waitToLoopTime(1);
obj.refresh();
renditions = obj.getRenditions();
retry++;
}
return obj.getRenditions();
}
protected List<SecondaryType> getSecondaryTypes()
{
CmisObject obj = getCmisObject(cmisAPI.getLastResource());
obj.refresh();
return obj.getSecondaryTypes();
}
/**
* Get the children from a parent folder
*
* @return Map<ContentModel, ObjectType>
*/
public Map<ContentModel, ObjectType> getChildren()
{
String folderParent = cmisAPI.getLastResource();
ItemIterable<CmisObject> children = cmisAPI.withCMISUtil().getCmisFolder(folderParent).getChildren();
Map<ContentModel, ObjectType> contents = new HashMap<ContentModel, ObjectType>();
for (CmisObject o : children)
{
ContentModel content = new ContentModel(o.getName());
content.setNodeRef(o.getId());
content.setDescription(o.getDescription());
content.setCmisLocation(Utility.buildPath(folderParent, o.getName()));
contents.put(content, o.getType());
}
return contents;
}
/**
* Gets the folder descendants starting with the current folder
*
* @param depth level of the tree that you want to go to
* - currentFolder
* -- file1.txt
* -- file2.txt
* -- folderB
* --- file3.txt
* --- file4.txt
* e.g. A depth of 1 will give you just the current folder descendants (file1.txt, file2.txt, folder1)
* e.g. A depth of -1 will return all the descendants (file1.txt, file2.txt, folder1, file3.txt and file4.txt)
*/
public List<CmisObject> getFolderDescendants(int depth)
{
return getFolderTreeCmisObjects(getCmisFolder(cmisAPI.getLastResource()).getDescendants(depth));
}
/**
* Returns a list of Cmis objects for the provided Content Models
*
* @param contentModels
*/
public List<CmisObject> getCmisObjectsFromContentModels(ContentModel... contentModels)
{
List<CmisObject> cmisObjects = new ArrayList<>();
for (ContentModel contentModel : contentModels)
cmisObjects.add(getCmisObject(contentModel.getCmisLocation()));
return cmisObjects;
}
public ContentStream getContentStream(String content)
{
String fileName = getCmisDocument(cmisAPI.getLastResource()).getName();
return cmisAPI.getDataContentService().getContentStream(fileName, content);
}
public Acl getAcls()
{
OperationContext context = cmisAPI.getSession().createOperationContext();
context.setIncludeAcls(true);
return getCmisObject(cmisAPI.getLastResource(), context).getAcl();
}
/**
* Gets only the folder descendants for the {@link #getLastResource()} folder
*
* @param depth level of the tree that you want to go to
* - currentFolder
* -- folderB
* -- folderC
* --- folderD
* e.g. A depth of 1 will give you just the current folder descendants (folderB, folderC)
* e.g. A depth of -1 will return all the descendants (folderB, folderC, folderD)
*/
public List<CmisObject> getFolderTree(int depth)
{
return getFolderTreeCmisObjects(getCmisFolder(cmisAPI.getLastResource()).getFolderTree(depth));
}
/**
* Helper method for getFolderTree and getFolderDescendants that cycles threw the folder descendants and returns List<CmisObject>
*/
private List<CmisObject> getFolderTreeCmisObjects(List<Tree<FileableCmisObject>> descendants)
{
List<CmisObject> cmisObjectList = new ArrayList<>();
for (Tree<FileableCmisObject> descendant : descendants)
{
cmisObjectList.add(descendant.getItem());
cmisObjectList.addAll(descendant.getChildren().stream().map(Tree::getItem).collect(Collectors.toList()));
}
return cmisObjectList;
}
protected List<Document> getAllDocumentVersions()
{
return getCmisDocument(cmisAPI.getLastResource()).getAllVersions();
}
public List<Document> getAllDocumentVersionsBy(OperationContext context)
{
return getCmisDocument(cmisAPI.getLastResource()).getAllVersions(context);
}
public List<Document> getCheckedOutDocumentsFromSession()
{
return com.google.common.collect.Lists.newArrayList(cmisAPI.getSession().getCheckedOutDocs());
}
public List<Document> getCheckedOutDocumentsFromSession(OperationContext context)
{
return com.google.common.collect.Lists.newArrayList(cmisAPI.getSession().getCheckedOutDocs(context));
}
public List<Document> getCheckedOutDocumentsFromFolder()
{
Folder folder = cmisAPI.withCMISUtil().getCmisFolder(cmisAPI.getLastResource());
return com.google.common.collect.Lists.newArrayList(folder.getCheckedOutDocs());
}
public List<Document> getCheckedOutDocumentsFromFolder(OperationContext context)
{
Folder folder = cmisAPI.withCMISUtil().getCmisFolder(cmisAPI.getLastResource());
return com.google.common.collect.Lists.newArrayList(folder.getCheckedOutDocs(context));
}
protected boolean isCmisObjectContainedInCmisCheckedOutDocumentsList(CmisObject cmisObject, List<Document> cmisCheckedOutDocuments)
{
for (Document cmisCheckedOutDocument : cmisCheckedOutDocuments)
if (cmisObject.getId().split(";")[0].equals(cmisCheckedOutDocument.getId().split(";")[0]))
return true;
return false;
}
public Map<String, Object> getProperties(ContentModel contentModel, String baseTypeId)
{
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(PropertyIds.OBJECT_TYPE_ID, baseTypeId);
properties.put(PropertyIds.NAME, contentModel.getName());
// WebServices binding does not have SecondaryTypes and cannot be added to Object.
// cm:title and cm:description Policies
if (cmisAPI.getSession().getBinding().getBindingType().value().equals(BindingType.WEBSERVICES.value()))
{
return properties;
}
List<Object> aspects = new ArrayList<Object>();
aspects.add("P:cm:titled");
properties.put(PropertyIds.SECONDARY_OBJECT_TYPE_IDS, aspects);
properties.put("cm:title", contentModel.getTitle());
properties.put("cm:description", contentModel.getDescription());
return properties;
}
public OperationContext setIncludeAclContext()
{
OperationContext context = cmisAPI.getSession().createOperationContext();
context.setIncludeAcls(true);
return context;
}
public List<Ace> createAce(UserModel user, UserRole role)
{
List<String> addPermission = new ArrayList<String>();
addPermission.add(role.getRoleId());
Ace addAce = cmisAPI.getSession().getObjectFactory().createAce(user.getUsername(), addPermission);
List<Ace> addAces = new ArrayList<Ace>();
addAces.add(addAce);
return addAces;
}
public List<Ace> createAce(GroupModel group, UserRole role)
{
List<String> addPermission = new ArrayList<String>();
addPermission.add(role.getRoleId());
Ace addAce = cmisAPI.getSession().getObjectFactory().createAce(group.getDisplayName(), addPermission);
List<Ace> addAces = new ArrayList<Ace>();
addAces.add(addAce);
return addAces;
}
public List<Ace> createAce(UserModel user, String... permissions)
{
List<Ace> addAces = new ArrayList<Ace>();
RepositoryInfo repositoryInfo = cmisAPI.getSession().getRepositoryInfo();
AclCapabilities aclCapabilities = repositoryInfo.getAclCapabilities();
Map<String, PermissionMapping> permissionMappings = aclCapabilities.getPermissionMapping();
for (String perm : permissions)
{
STEP(String.format("%s Add permission '%s' for user %s ", CmisWrapper.STEP_PREFIX, perm, user.getUsername()));
PermissionMapping permissionMapping = permissionMappings.get(perm);
List<String> permissionsList = permissionMapping.getPermissions();
Ace addAce = cmisAPI.getSession().getObjectFactory().createAce(user.getUsername(), permissionsList);
addAces.add(addAce);
}
return addAces;
}
public ObjectType getTypeDefinition()
{
CmisObject cmisObject = cmisAPI.withCMISUtil().getCmisObject(cmisAPI.getLastResource());
return cmisAPI.getSession().getTypeDefinition(cmisObject.getBaseTypeId().value());
}
public ItemIterable<ObjectType> getTypeChildren(String baseType, boolean includePropertyDefinitions)
{
STEP(String.format("%s Get type children for '%s' and includePropertyDefinitions set to '%s'", CmisWrapper.STEP_PREFIX, baseType,
includePropertyDefinitions));
return cmisAPI.getSession().getTypeChildren(baseType, includePropertyDefinitions);
}
public List<Tree<ObjectType>> getTypeDescendants(String baseTypeId, int depth, boolean includePropertyDefinitions)
{
STEP(String.format("%s Get type descendants for '%s' with depth set to %d and includePropertyDefinitions set to '%s'", CmisWrapper.STEP_PREFIX,
baseTypeId, depth, includePropertyDefinitions));
return cmisAPI.getSession().getTypeDescendants(baseTypeId, depth, includePropertyDefinitions);
}
public String getObjectId(String pathToObject)
{
return getCmisObject(pathToObject).getId();
}
/**
* Update property for last resource cmis object
*
* @param propertyName String property name (e.g. cmis:name)
* @param propertyValue Object property value
*/
public void updateProperties(String propertyName, Object propertyValue)
{
Map<String, Object> props = new HashMap<String, Object>();
props.put(propertyName, propertyValue);
getCmisObject(cmisAPI.getLastResource()).updateProperties(props);
}
protected boolean isFolderInList(FolderModel folderModel, List<FolderModel> folders)
{
for (FolderModel folder : folders)
{
if (folderModel.getName().equals(folder.getName()))
{
return true;
}
}
return false;
}
protected boolean isFileInList(FileModel fileModel, List<FileModel> files)
{
for (FileModel file : files)
{
if (fileModel.getName().equals(file.getName()))
{
return true;
}
}
return false;
}
protected boolean isContentInList(ContentModel contentModel, List<ContentModel> contents)
{
for (ContentModel content : contents)
{
if (content.getName().equals(content.getName()))
{
return true;
}
}
return false;
}
/**
* Get children folders from a parent folder
*
* @return List<FolderModel>
*/
public List<FolderModel> getFolders()
{
STEP(String.format("%s Get the folder children from '%s'", CmisWrapper.STEP_PREFIX, cmisAPI.getLastResource()));
Map<ContentModel, ObjectType> children = getChildren();
List<FolderModel> folders = new ArrayList<FolderModel>();
for (Map.Entry<ContentModel, ObjectType> entry : children.entrySet())
{
if (entry.getValue().getId().equals(BaseTypeId.CMIS_FOLDER.value()))
{
FolderModel folder = new FolderModel(entry.getKey().getName());
folder.setNodeRef(entry.getKey().getNodeRef());
folder.setDescription(entry.getKey().getDescription());
folder.setCmisLocation(entry.getKey().getCmisLocation());
folder.setProtocolLocation(entry.getKey().getCmisLocation());
folders.add(folder);
}
}
return folders;
}
/**
* Get children documents from a parent folder
*
* @return List<FolderModel>
*/
public List<FileModel> getFiles()
{
STEP(String.format("%s Get the file children from '%s'", CmisWrapper.STEP_PREFIX, cmisAPI.getLastResource()));
Map<ContentModel, ObjectType> children = getChildren();
List<FileModel> files = new ArrayList<FileModel>();
for (Map.Entry<ContentModel, ObjectType> entry : children.entrySet())
{
if (entry.getValue().getId().equals(BaseTypeId.CMIS_DOCUMENT.value()))
{
FileModel file = new FileModel(entry.getKey().getName());
file.setNodeRef(entry.getKey().getNodeRef());
file.setDescription(entry.getKey().getDescription());
file.setCmisLocation(entry.getKey().getCmisLocation());
file.setProtocolLocation(entry.getKey().getCmisLocation());
files.add(file);
}
}
return files;
}
/*
* Get document(set as last resource) content
*/
public String getDocumentContent()
{
Utility.waitToLoopTime(2);
Document lastVersion = getCmisDocument(cmisAPI.getLastResource());
lastVersion.refresh();
LOG.info(String.format("Get the content from %s - node: %s", lastVersion.getName(), lastVersion.getId()));
ContentStream contentStream = lastVersion.getContentStream();
String actualContent = "";
if (contentStream != null)
{
actualContent = getContentAsString(contentStream);
}
else
{
lastVersion = getCmisDocument(cmisAPI.getLastResource());
lastVersion.refresh();
LOG.info(String.format("Retry get content stream for %s node: %s", lastVersion.getName(), lastVersion.getId()));
contentStream = lastVersion.getContentStream();
if (contentStream != null)
{
actualContent = getContentAsString(contentStream);
}
}
if(actualContent.isEmpty())
{
Utility.waitToLoopTime(2);
Document retryDoc = getCmisDocument(cmisAPI.getLastResource());
retryDoc.refresh();
LOG.info(String.format("Retry get content stream for %s node: %s", retryDoc.getName(), retryDoc.getId()));
contentStream = retryDoc.getContentStream();
if (contentStream != null)
{
actualContent = getContentAsString(contentStream);
}
}
return actualContent;
}
/**
* Get user noderef
*
* @param user {@link UserModel}
*/
public String getUserNodeRef(UserModel user)
{
String objectId = "";
ItemIterable<QueryResult> results = cmisAPI.getSession().query("select cmis:objectId from cm:person where cm:userName = '" + user.getUsername() + "'",
false);
for (QueryResult qResult : results)
{
PropertyData<?> propData = qResult.getPropertyById("cmis:objectId");
objectId = (String) propData.getFirstValue();
}
return objectId;
}
}

View File

@@ -0,0 +1,137 @@
package org.alfresco.cmis.dsl;
import static org.alfresco.utility.report.log.Step.STEP;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.alfresco.cmis.CmisWrapper;
import org.apache.chemistry.opencmis.client.api.CmisObject;
import org.apache.chemistry.opencmis.client.api.Document;
import org.apache.chemistry.opencmis.client.api.OperationContext;
import org.testng.Assert;
/**
* DSL utility for verifying a document version {@link Document}
*/
public class DocumentVersioning
{
private CmisWrapper cmisWrapper;
private CmisObject cmisObject;
private boolean majorVersion;
private Object versionLabel;
private List<Object> versions;
public DocumentVersioning(CmisWrapper cmisWrapper, CmisObject cmisObject)
{
this.cmisWrapper = cmisWrapper;
this.cmisObject = cmisObject;
}
private DocumentVersioning withLatestMajorVersion()
{
this.majorVersion = true;
return this;
}
private DocumentVersioning withLatestMinorVersion()
{
this.majorVersion = false;
return this;
}
private Document getVersionOfDocument()
{
Document document = (Document) cmisObject;
if (versionLabel != null)
{
List<Document> documents = document.getAllVersions();
for (Document documentVersion : documents)
if (documentVersion.getVersionLabel().equals(versionLabel.toString()))
return documentVersion;
}
else
{
return document.getObjectOfLatestVersion(majorVersion);
}
return document;
}
private List<Object> getDocumentVersions(List<Document> documentList)
{
List<Object> versions = new ArrayList<Object>();
for (Document document : documentList)
{
versions.add(document.getVersionLabel());
}
return versions;
}
public CmisWrapper assertVersionIs(Double expectedVersion)
{
STEP(String.format("%s Verify if document '%s' has version '%s'", cmisWrapper.getProtocolName(), cmisObject.getName(), expectedVersion));
Assert.assertEquals(getVersionOfDocument().getVersionLabel(), expectedVersion.toString(), "File has version");
return cmisWrapper;
}
public CmisWrapper assertLatestMajorVersionIs(Double expectedVersion)
{
STEP(String.format("%s Verify if latest major version of document '%s' is '%s'", cmisWrapper.getProtocolName(), cmisObject.getName(), expectedVersion));
Assert.assertEquals(withLatestMajorVersion().getVersionOfDocument().getVersionLabel(), expectedVersion.toString(), "File has version");
return cmisWrapper;
}
public CmisWrapper assertLatestMinorVersionIs(Double expectedVersion)
{
STEP(String.format("%s Verify if latest minor version of document '%s' is '%s'", cmisWrapper.getProtocolName(), cmisObject.getName(), expectedVersion));
Assert.assertEquals(withLatestMinorVersion().getVersionOfDocument().getVersionLabel(), expectedVersion.toString(), "File has version");
return cmisWrapper;
}
public DocumentVersioning getAllDocumentVersions()
{
setVersions(getDocumentVersions(cmisWrapper.withCMISUtil().getAllDocumentVersions()));
return this;
}
public CmisWrapper assertHasVersions(Object... versions)
{
setVersions(getDocumentVersions(cmisWrapper.withCMISUtil().getAllDocumentVersions()));
List<Object> documentVersions = getVersions();
for (Object version : versions)
{
STEP(String.format("%s Verify if document '%s' has version '%s'", cmisWrapper.getProtocolName(), cmisObject.getName(), version));
Assert.assertTrue(documentVersions.contains(version.toString()),
String.format("Document %s does not have version %s", cmisObject.getName(), version));
}
return cmisWrapper;
}
public DocumentVersioning getAllDocumentVersionsBy(OperationContext context)
{
setVersions(getDocumentVersions(cmisWrapper.withCMISUtil().getAllDocumentVersionsBy(context)));
return this;
}
public CmisWrapper assertHasVersionsInOrder(Object... versions)
{
List<Object> documentVersions = getVersions();
List<Object> expectedVersions = Arrays.asList(versions);
STEP(String.format("%s Verify if document '%s' has versions in this order '%s'", cmisWrapper.getProtocolName(), cmisObject.getName(),
Arrays.toString(expectedVersions.toArray())));
Assert.assertTrue(documentVersions.toString().equals(expectedVersions.toString()),
String.format("Document %s does not have versions in this order %s", cmisObject.getName(), Arrays.toString(expectedVersions.toArray())));
return cmisWrapper;
}
public List<Object> getVersions()
{
return versions;
}
public void setVersions(List<Object> versions)
{
this.versions = versions;
}
}

View File

@@ -0,0 +1,39 @@
package org.alfresco.cmis.dsl;
import org.alfresco.cmis.CmisWrapper;
import org.alfresco.utility.network.Jmx;
import org.alfresco.utility.network.JmxClient;
import org.alfresco.utility.network.JmxJolokiaProxyClient;
/**
* DSL for interacting with JMX (using direct JMX call see {@link JmxClient} or {@link JmxJolokiaProxyClient}
*/
public class JmxUtil
{
@SuppressWarnings("unused")
private CmisWrapper cmisWrapper;
private Jmx jmx;
private final String jmxAuditObjectName = "Alfresco:Type=Configuration,Category=Audit,id1=default";
public JmxUtil(CmisWrapper cmisWrapper, Jmx jmx)
{
this.cmisWrapper = cmisWrapper;
this.jmx = jmx;
}
public void enableCMISAudit() throws Exception
{
if(jmx.readProperty(jmxAuditObjectName, "audit.enabled").equals(String.valueOf(false)))
{
jmx.writeProperty(jmxAuditObjectName, "audit.enabled", String.valueOf(true));
}
jmx.writeProperty(jmxAuditObjectName, "audit.cmischangelog.enabled", String.valueOf(true));
jmx.writeProperty(jmxAuditObjectName, "audit.alfresco-access.enabled", String.valueOf(true));
}
public void disableCMISAudit() throws Exception
{
jmx.writeProperty(jmxAuditObjectName, "audit.cmischangelog.enabled", String.valueOf(false));
jmx.writeProperty(jmxAuditObjectName, "audit.alfresco-access.enabled", String.valueOf(false));
}
}

View File

@@ -0,0 +1,287 @@
package org.alfresco.cmis.dsl;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import static org.alfresco.utility.Utility.checkObjectIsInitialized;
import static org.alfresco.utility.report.log.Step.STEP;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.StreamSupport;
import org.alfresco.cmis.CmisWrapper;
import org.alfresco.utility.LogFactory;
import org.alfresco.utility.data.provider.XMLTestData;
import org.alfresco.utility.exception.TestConfigurationException;
import org.alfresco.utility.model.FileModel;
import org.alfresco.utility.model.FolderModel;
import org.alfresco.utility.model.SiteModel;
import org.alfresco.utility.model.TestModel;
import org.apache.chemistry.opencmis.client.api.ItemIterable;
import org.apache.chemistry.opencmis.client.api.QueryResult;
import org.apache.chemistry.opencmis.client.api.Session;
import org.apache.chemistry.opencmis.commons.data.PropertyData;
import org.slf4j.Logger;
import org.testng.Assert;
/**
* DSL for CMIS Queries
* This will also handle execution of CMIS queries
*/
public class QueryExecutor
{
static Logger LOG = LogFactory.getLogger();
CmisWrapper cmisWrapper;
private long resultCount = -1;
private String currentQuery = "";
private List<QueryResult> results;
public QueryExecutor(CmisWrapper cmisWrapper, String query)
{
this.cmisWrapper = cmisWrapper;
currentQuery = query;
}
public QueryResultAssertion assertResultsCount()
{
resultCount = executeQuery(currentQuery).getPageNumItems();
return new QueryResultAssertion();
}
public QueryResultAssertion assertColumnIsOrdered()
{
return assertValues();
}
public QueryResultAssertion assertColumnValuesRange()
{
return assertValues();
}
public QueryResultAssertion assertValues()
{
STEP("Sending query " + currentQuery);
results = StreamSupport.stream(executeQuery(currentQuery).spliterator(), false)
.collect(toList());
resultCount = results.size();
STEP("Received results " + results.stream().map(this::resultToString).collect(toList()));
return new QueryResultAssertion();
}
/** Try to return a useful string representation of the CMIS query result. */
private String resultToString(QueryResult result)
{
if (result == null || result.getProperties() == null)
{
return "null";
}
Optional<PropertyData<?>> idProperty = result.getProperties().stream()
.filter(propertyData -> propertyData.getId().equals("cmis:objectId"))
.findFirst();
return idProperty.map(PropertyData::getValues)
.map(values -> values.stream().map(Object::toString).collect(joining(",")))
.orElse(result.getProperties().toString());
}
private ItemIterable<QueryResult> executeQuery(String query)
{
Session session = cmisWrapper.getSession();
checkObjectIsInitialized(session, "You need to authenticate first using <cmisWrapper.authenticateUser(UserModel userModel)>");
return session.query(query, false);
}
/**
* Call getNodeRef on each test data item used in test and replace that with NODE_REF keywords in your Query
*
* @param testData
* @return
*/
public QueryExecutor applyNodeRefsFrom(XMLTestData testData)
{
List<String> dataItems = extractKeywords("NODE_REF");
if (dataItems.isEmpty())
return this;
List<String> nodeRefs = new ArrayList<String>();
for (String dataItemName : dataItems)
{
currentQuery = currentQuery.replace(String.format("NODE_REF[%s]", dataItemName), "%s");
TestModel model = testData.getTestDataItemWithId(dataItemName).getModel();
if (model == null)
throw new TestConfigurationException("No TestData with ID: " + dataItemName + " found in your XML file.");
if (model instanceof SiteModel)
{
nodeRefs.add(cmisWrapper.getDataContentService().usingAdmin().usingSite((SiteModel) model).getNodeRef());
}
else if (model instanceof FolderModel)
{
nodeRefs.add(((FolderModel) model).getNodeRef());
}
else if (model instanceof FileModel)
{
nodeRefs.add(((FileModel) model).getNodeRef());
}
}
try
{
currentQuery = String.format(currentQuery, nodeRefs.toArray());
LOG.info("Injecting nodeRef IDs \n\tQuery: [{}]", currentQuery);
}
catch (Exception e)
{
throw new TestConfigurationException(
"You passed multiple keywords to your search query, please re-analyze your query search format: " + e.getMessage());
}
return this;
}
/**
* if you have in your search 'SELECT * from cmis:document where workspace://SpacesStore/NODE_REF[site1] or workspace://SpacesStore/NODE_REF[site2]'
* and pass key="NODE_REF" this method will get "site1" and "site2" as values
*
* @param key
* @return
* @throws TestConfigurationException
*/
private List<String> extractKeywords(String key) throws TestConfigurationException
{
String[] lines = currentQuery.split(key);
List<String> keywords = new ArrayList<String>();
for (int i = 0; i < lines.length; i++)
{
if (lines[i].startsWith("["))
{
String keyValue = "";
for (int j = 1; j < lines[i].length() - 1; j++)
{
String tmp = Character.toString(lines[i].charAt(j));
if (tmp.equals("]"))
break;
keyValue += tmp;
}
keywords.add(keyValue);
}
}
return keywords;
}
public class QueryResultAssertion
{
public QueryResultAssertion hasLength(long expectedValue)
{
STEP(String.format("Verify that query: '%s' has %d results count returned", currentQuery, expectedValue));
Assert.assertEquals(resultCount, expectedValue, showErrorMessage());
return this;
}
public QueryResultAssertion isGreaterThan(long expectedValue)
{
STEP(String.format("Verify that query: '%s' has more than %d results count returned", currentQuery, expectedValue));
if (expectedValue <= resultCount)
Assert.fail(String.format("%s expected to have more than %d results, but found %d", showErrorMessage(), expectedValue, resultCount));
return this;
}
public QueryResultAssertion isLowerThan(long expectedValue)
{
STEP(String.format("Verify that query: '%s' has more than %d results count returned", currentQuery, expectedValue));
if (resultCount >= expectedValue)
Assert.fail(String.format("%s expected to have less than %d results, but found %d", showErrorMessage(), expectedValue, resultCount));
return this;
}
public QueryResultAssertion isOrderedAsc(String queryName)
{
STEP(String.format("Verify that query: '%s' is returning ascending ordered values for column %s", currentQuery, queryName));
List<Object> columnValues = new ArrayList<>();
results.forEach((r) -> {
columnValues.add(r.getPropertyValueByQueryName(queryName));
});
List<Object> orderedColumnValues = columnValues.stream().sorted().collect(toList());
Assert.assertEquals(columnValues, orderedColumnValues,
String.format("%s column values expected to be in ascendent order, but found %s", queryName, columnValues.toString()));
return this;
}
public QueryResultAssertion isOrderedDesc(String queryName)
{
STEP(String.format("Verify that query: '%s' is returning descending ordered values for column %s", currentQuery, queryName));
List<Object> columnValues = new ArrayList<>();
results.forEach((r) -> {
columnValues.add(r.getPropertyValueByQueryName(queryName));
});
List<Object> reverseOrderedColumnValues = columnValues.stream().sorted(Collections.reverseOrder()).collect(toList());
Assert.assertEquals(columnValues, reverseOrderedColumnValues,
String.format("%s column values expected to be in descendent order, but found %s", queryName, columnValues.toString()));
return this;
}
public QueryResultAssertion isReturningValuesInRange(String queryName, BigDecimal minValue, BigDecimal maxValue)
{
STEP(String.format("Verify that query: '%s' is returning values for column %s in range from %.4f to %.4f", currentQuery, queryName, minValue, maxValue));
results.forEach((r) -> {
BigDecimal value = (BigDecimal) r.getPropertyValueByQueryName(queryName);
if (value.compareTo(minValue) < 0 || value.compareTo(maxValue) > 0)
{
Assert.fail(String.format("%s column values expected to be in range from %.4f to %.4f, but found %.4f", queryName, minValue, maxValue, value));
}
});
return this;
}
public <T> QueryResultAssertion isReturningValues(String queryName, Set<T> values)
{
return isReturningValues(queryName, values, false);
}
public <T> QueryResultAssertion isReturningValues(String queryName, Set<T> values, boolean multivalue)
{
STEP(String.format("Verify that query: '%s' returns the values from %s for column %s", currentQuery, values, queryName));
Function<QueryResult, Object> extractValue = (multivalue ? (r -> r.getPropertyMultivalueById(queryName)) : r -> r.getPropertyValueById(queryName));
Set<Object> resultSet = results.stream().map(extractValue).collect(toSet());
Assert.assertEquals(resultSet, values, "Values did not match - expected " + values + " got " + resultSet);
return this;
}
public <T> QueryResultAssertion isReturningOrderedValues(String queryName, List<T> values)
{
return isReturningOrderedValues(queryName, values, false);
}
public <T> QueryResultAssertion isReturningOrderedValues(String queryName, List<T> values, boolean multivalue)
{
STEP(String.format("Verify that query: '%s' returns the values from %s for column %s", currentQuery, values, queryName));
Function<QueryResult, Object> extractValue = (multivalue ? (r -> r.getPropertyMultivalueById(queryName)) : r -> r.getPropertyValueById(queryName));
List<Object> resultList = results.stream().map(extractValue).collect(toList());
// Include both lists in assertion message as TestNG does not provide this information.
Assert.assertEquals(resultList, values, "Values did not match expected " + values + " but found " + resultList);
return this;
}
private String showErrorMessage()
{
return String.format("Returned results count of Query [%s] is not the expected one:", currentQuery);
}
}
}

View File

@@ -0,0 +1,10 @@
package org.alfresco.cmis.exception;
public class InvalidCmisObjectException extends RuntimeException
{
private static final long serialVersionUID = 1L;
public InvalidCmisObjectException(String reason)
{
super(reason);
}
}

View File

@@ -0,0 +1,12 @@
package org.alfresco.cmis.exception;
public class UnrecognizedBinding extends Exception
{
private static final long serialVersionUID = 1L;
private static final String DEFAULT_MESSAGE = "Unrecognized CMIS Binding [%s]. Available binding options: BROWSER or ATOM";
public UnrecognizedBinding(String binding)
{
super(String.format(DEFAULT_MESSAGE, binding));
}
}

View File

@@ -0,0 +1,16 @@
<?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/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<context:annotation-config />
<context:component-scan base-package="org.alfresco" />
<import resource="classpath:dataprep-context.xml" />
<import resource="classpath*:alfresco-tester-context.xml" />
</beans>

View File

@@ -0,0 +1,76 @@
# dataprep related
alfresco.scheme=http
alfresco.server=localhost
alfresco.port=8081
# credentials
admin.user=admin
admin.password=admin
solrWaitTimeInSeconds=30
# in containers we cannot access directly JMX, so we will use http://jolokia.org agent
# disabling this we will use direct JMX calls to server
jmx.useJolokiaAgent=false
# Server Health section
# in ServerHealth#isServerReachable() - could also be shown.
# enable this option to view if on server there are tenants or not
serverHealth.showTenants=true
# set CMIS binding to 'browser' or 'atom'
cmis.binding=browser
cmis.basePath=/alfresco/api/-default-/public/cmis/versions/1.1/${cmis.binding}
# TEST MANAGEMENT SECTION - Test Rail
#
# (currently supporting Test Rail v5.2.1.3472 integration)
#
# Example of configuration:
# ------------------------------------------------------
# if testManagement.enabled=true we enabled TestRailExecutorListener (if used in your suite xml file)
# testManagement.updateTestExecutionResultsOnly=true (this will just update the results of a test: no step will be updated - good for performance)
# testManagement.endPoint=https://alfresco.testrail.com/
# testManagement.username=<username>
# testManagement.apiKey=<api-key>
# testManagement.project=<id-of-your-project
# 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.suiteId=23 (the id of the Master suite)
# ------------------------------------------------------
testManagement.enabled=false
testManagement.endPoint=
testManagement.username=
testManagement.apiKey=
testManagement.project=7
testManagement.includeOnlyTestCasesExecuted=true
testManagement.rateLimitInSeconds=1
testManagement.testRun=MyTestRunInTestRail
testManagement.suiteId=12
# The location of the reports path
reports.path=./target/reports
#
# Database Section
# You should provide here the database URL, that can be a differed server as alfresco.
# https://docs.oracle.com/javase/tutorial/jdbc/basics/connecting.html
#
# Current supported db.url:
#
# MySQL:
# db.url = jdbc:mysql://${alfresco.server}:3306/alfresco
#
# PostgreSQL:
# db.url = jdbc:postgresql://<your-DB-IP>:3306/alfresco
#
# Oracle:
# db.url = jdbc:oracle://<your-DB-IP>:3306/alfresco
#
# MariaDB:
# db.url = jdbc:mariadb://<your-DB-IP>:3306/alfresco
#
db.url = jdbc:mysql://${alfresco.server}:3306/alfresco
db.username = alfresco
db.password = alfresco

View File

@@ -0,0 +1,26 @@
# Root logger option
log4j.rootLogger=INFO, file, stdout
# Direct log messages to a log file
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./target/reports/alfresco-tas.log
log4j.appender.file.MaxBackupIndex=10
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%t] %d{HH:mm:ss} %-5p %c{1}:%L - %m%n
# Direct log messages to stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[%t] %d{HH:mm:ss} %-5p %c{1}:%L - %m%n
# TestRail particular log file
# Direct log messages to a log file
log4j.appender.testrailLog=org.apache.log4j.RollingFileAppender
log4j.appender.testrailLog.File=./target/reports/alfresco-testrail.log
log4j.appender.testrailLog.MaxBackupIndex=10
log4j.appender.testrailLog.layout=org.apache.log4j.PatternLayout
log4j.appender.testrailLog.layout.ConversionPattern=%d{HH:mm:ss} %-5p %c{1}:%L - %m%n
log4j.category.testrail=INFO, testrailLog
log4j.additivity.testrail=false

View File

@@ -1,9 +1,21 @@
package org.alfresco.cmis.search;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import static org.alfresco.utility.report.log.Step.STEP;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.alfresco.cmis.CmisProperties;
import org.alfresco.cmis.dsl.QueryExecutor.QueryResultAssertion;
import org.alfresco.utility.Utility;
import org.alfresco.utility.model.ContentModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -45,32 +57,76 @@ public abstract class AbstractCmisE2ETest extends AbstractE2EFunctionalTest
/**
* Repeat Elastic Query till results count returns expectedCountResults
* @param query CMIS Query to be executed
* @param expectedCountResults Number of results expected
* @param expectedResultsCount Number of results expected
* @return true when results count is equals to expectedCountResults
*/
protected boolean waitForIndexing(String query, long expectedCountResults)
protected boolean waitForIndexing(String query, long expectedResultsCount)
{
for (int searchCount = 1; searchCount <= SEARCH_MAX_ATTEMPTS; searchCount++)
try
{
waitForIndexing(query, execution -> execution.hasLength(expectedResultsCount));
return true;
}
catch (AssertionError ae)
{
STEP("Received assertion error for query '" + query + "': " + ae);
return false;
}
}
/**
* Repeat Elastic Query until we get the expected results or we hit the retry limit.
*
* @param query CMIS Query to be executed
* @param expectedResults The expected results (unordered).
*/
protected void waitForIndexing(String query, ContentModel... expectedResults)
{
Set<String> expectedNames = Arrays.stream(expectedResults).map(ContentModel::getName).collect(toSet());
waitForIndexing(query, execution -> execution.isReturningValues("cmis:name", expectedNames));
}
/**
* Repeat Elastic Query until we get the expected results in the given order or we hit the retry limit.
*
* @param query CMIS Query to be executed
* @param expectedResults The expected results (ordered).
*/
protected void waitForIndexingOrdered(String query, ContentModel... expectedResults)
{
List<String> expectedNames = Arrays.stream(expectedResults).map(ContentModel::getName).collect(toList());
waitForIndexing(query, execution -> execution.isReturningOrderedValues("cmis:name", expectedNames));
}
/**
* Repeat Elastic Query until we get the expected results or we hit the retry limit.
*
* @param query CMIS Query to be executed
* @param assertionMethod A method that will be called to check the response and which will throw an AssertionError if they aren't what we want.
*/
protected void waitForIndexing(String query, Consumer<QueryResultAssertion> assertionMethod)
{
int searchCount = 0;
while (true)
{
try
{
cmisApi.withQuery(query).assertResultsCount().equals(expectedCountResults);
return true;
assertionMethod.accept(cmisApi.withQuery(query).assertValues());
return;
}
catch (AssertionError ae)
{
LOGGER.info(String.format("WaitForIndexing in Progress: %s", ae));
searchCount++;
if (searchCount < SEARCH_MAX_ATTEMPTS)
{
LOGGER.info(String.format("WaitForIndexing in Progress: %s", ae));
Utility.waitToLoopTime(getElasticWaitTimeInSeconds(), "Wait For Indexing");
}
else
{
throw ae;
}
}
Utility.waitToLoopTime(getElasticWaitTimeInSeconds(), "Wait For Indexing");
}
return false;
}
}

View File

@@ -1,12 +1,12 @@
package org.alfresco.cmis.search;
import java.util.List;
import java.util.Set;
import org.alfresco.utility.Utility;
import org.alfresco.utility.data.provider.XMLDataConfig;
import org.alfresco.utility.data.provider.XMLTestDataProvider;
import org.alfresco.utility.model.FileModel;
import org.alfresco.utility.model.FileType;
import org.alfresco.utility.model.FolderModel;
import org.alfresco.utility.model.QueryModel;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
@@ -17,6 +17,21 @@ public class SearchInFolderTests extends AbstractCmisE2ETest
private FolderModel parentFolder, subFolder1, subFolder2, subFolder3;
private FileModel subFile1, subFile2, subFile3, subFile4, subFile5;
/**
* Create test data in the following format:
* <pre>
* testSite
* +- parentFolder
* +- subFile5 (fifthFile.txt: "fifthFile content")
* +- subFolder1
* +- subFolder2
* +- subFolder3 (subFolder)
* +- subFile1 (firstFile.xls)
* +- subFile2 (.pptx)
* +- subFile3 (.txt)
* +- subFile4 (fourthFile.docx: "fourthFileTitle", "fourthFileDescription")
* </pre>
*/
@BeforeClass(alwaysRun = true)
public void createTestData() throws Exception
{
@@ -51,12 +66,164 @@ public class SearchInFolderTests extends AbstractCmisE2ETest
dataContent.deleteSite(testSite);
}
@Test(dataProviderClass = XMLTestDataProvider.class, dataProvider = "getQueriesData")
@XMLDataConfig(file = "src/test/resources/search-in-folder.xml")
public void executeCMISQuery(QueryModel query)
@Test
public void executeCMISQuery_selectFieldsFromFolder()
{
String currentQuery = String.format(query.getValue(), parentFolder.getNodeRef());
String query = "SELECT cmis:name, cmis:parentId, cmis:path, cmis:allowedChildObjectTypeIds" +
" FROM cmis:folder where IN_FOLDER('%s') AND cmis:name = 'subFolder'";
String currentQuery = String.format(query, parentFolder.getNodeRef());
cmisApi.authenticateUser(testUser);
Assert.assertTrue(waitForIndexing(currentQuery, query.getResults()), String.format("Result count not as expected for query: %s", currentQuery));
waitForIndexing(currentQuery, subFolder3);
}
@Test
public void executeCMISQuery_selectFieldsFromDocument()
{
String query = "SELECT cmis:name, cmis:objectId, cmis:lastModifiedBy, cmis:creationDate, cmis:contentStreamFileName" +
" FROM cmis:document where IN_FOLDER('%s') AND cmis:name = 'fourthFile'";
String currentQuery = String.format(query, parentFolder.getNodeRef());
cmisApi.authenticateUser(testUser);
waitForIndexing(currentQuery, subFile4);
}
@Test
public void executeCMISQuery_selectParentId()
{
String query = "SELECT cmis:parentId FROM cmis:folder where IN_FOLDER('%s')";
String currentQuery = String.format(query, parentFolder.getNodeRef());
cmisApi.authenticateUser(testUser);
// Expect to get the same parent for each of the three matches.
String parentId = parentFolder.getNodeRef();
List<String> expectedParentIds = List.of(parentId, parentId, parentId);
waitForIndexing(currentQuery, execution -> execution.isReturningOrderedValues("cmis:parentId", expectedParentIds));
}
@Test
public void executeCMISQuery_inFolder()
{
String query = "SELECT * FROM cmis:document where IN_FOLDER('%s')";
String currentQuery = String.format(query, parentFolder.getNodeRef());
cmisApi.authenticateUser(testUser);
waitForIndexing(currentQuery, subFile1, subFile2, subFile3, subFile4, subFile5);
}
@Test
public void executeCMISQuery_orderByNameAsc()
{
String query = "SELECT * FROM cmis:document where IN_FOLDER('%s') AND cmis:name NOT LIKE 'file%%' ORDER BY cmis:name ASC";
String currentQuery = String.format(query, parentFolder.getNodeRef());
cmisApi.authenticateUser(testUser);
waitForIndexingOrdered(currentQuery, subFile5, subFile1, subFile4);
}
@Test
public void executeCMISQuery_orderByNameDesc()
{
String query = "SELECT * FROM cmis:document where IN_FOLDER('%s') AND cmis:name NOT LIKE 'file%%' ORDER BY cmis:name DESC";
String currentQuery = String.format(query, parentFolder.getNodeRef());
cmisApi.authenticateUser(testUser);
waitForIndexingOrdered(currentQuery, subFile4, subFile1, subFile5);
}
@Test
public void executeCMISQuery_orderByLastModifiedAsc()
{
String query = "SELECT * FROM cmis:folder where IN_FOLDER('%s') ORDER BY cmis:lastModificationDate ASC";
String currentQuery = String.format(query, parentFolder.getNodeRef());
cmisApi.authenticateUser(testUser);
waitForIndexingOrdered(currentQuery, subFolder1, subFolder2, subFolder3);
}
@Test
public void executeCMISQuery_orderByLastModifiedDesc()
{
String query = "SELECT * FROM cmis:folder where IN_FOLDER('%s') ORDER BY cmis:lastModificationDate DESC";
String currentQuery = String.format(query, parentFolder.getNodeRef());
cmisApi.authenticateUser(testUser);
waitForIndexingOrdered(currentQuery, subFolder3, subFolder2, subFolder1);
}
@Test
public void executeCMISQuery_orderByCreatedBy()
{
String query = "SELECT * FROM cmis:document where IN_FOLDER('%s') ORDER BY cmis:createdBy DESC";
String currentQuery = String.format(query, parentFolder.getNodeRef());
cmisApi.authenticateUser(testUser);
// All the results were created by the same user, so we can't assert anything about the order.
waitForIndexing(currentQuery, subFile5, subFile1, subFile2, subFile3, subFile4);
}
@Test
public void executeCMISQuery_documentNameNotNull()
{
String query = "SELECT * FROM cmis:document where IN_FOLDER('%s') AND cmis:name IS NOT NULL";
String currentQuery = String.format(query, parentFolder.getNodeRef());
cmisApi.authenticateUser(testUser);
waitForIndexing(currentQuery, subFile1, subFile2, subFile3, subFile4, subFile5);
}
@Test
public void executeCMISQuery_folderNameNotNull()
{
String query = "SELECT * FROM cmis:folder where IN_FOLDER('%s') AND cmis:name IS NOT NULL";
String currentQuery = String.format(query, parentFolder.getNodeRef());
cmisApi.authenticateUser(testUser);
waitForIndexing(currentQuery, subFolder1, subFolder2, subFolder3);
}
@Test
public void executeCMISQuery_nameLike()
{
String query = "SELECT * FROM cmis:document where IN_FOLDER('%s') AND cmis:name LIKE 'fourthFile'";
String currentQuery = String.format(query, parentFolder.getNodeRef());
cmisApi.authenticateUser(testUser);
waitForIndexingOrdered(currentQuery, subFile4);
}
@Test
public void executeCMISQuery_doubleNegative()
{
String query = "SELECT * FROM cmis:folder where IN_FOLDER('%s') AND NOT(cmis:name NOT IN ('subFolder'))";
String currentQuery = String.format(query, parentFolder.getNodeRef());
cmisApi.authenticateUser(testUser);
waitForIndexingOrdered(currentQuery, subFolder3);
}
@Test
public void executeCMISQuery_nameInList()
{
String query = "SELECT * FROM cmis:document where IN_FOLDER('%s') AND cmis:name IN ('fourthFile', 'fifthFile.txt')";
String currentQuery = String.format(query, parentFolder.getNodeRef());
cmisApi.authenticateUser(testUser);
waitForIndexing(currentQuery, subFile4, subFile5);
}
@Test
public void executeCMISQuery_nameNotInList()
{
String query = "SELECT * FROM cmis:document where IN_FOLDER('%s') AND cmis:name NOT IN ('fourthFile', 'fifthFile.txt')";
String currentQuery = String.format(query, parentFolder.getNodeRef());
cmisApi.authenticateUser(testUser);
waitForIndexing(currentQuery, subFile1, subFile2, subFile3);
}
@Test
public void executeCMISQuery_nameDifferentFrom()
{
String query = "SELECT * FROM cmis:folder where IN_FOLDER('%s') AND cmis:name <> 'subFolder'";
String currentQuery = String.format(query, parentFolder.getNodeRef());
cmisApi.authenticateUser(testUser);
waitForIndexing(currentQuery, subFolder1, subFolder2);
}
@Test
public void executeCMISQuery_selectSecondaryObjectTypeIds()
{
String query = "SELECT cmis:secondaryObjectTypeIds FROM cmis:folder where IN_FOLDER('%s') AND cmis:name = 'subFolder'";
String currentQuery = String.format(query, parentFolder.getNodeRef());
cmisApi.authenticateUser(testUser);
Set<List<String>> expectedSecondaryObjectTypeIds = Set.of(List.of("P:cm:titled", "P:sys:localized"));
waitForIndexing(currentQuery, execution -> execution.isReturningValues("cmis:secondaryObjectTypeIds", expectedSecondaryObjectTypeIds, true));
Assert.assertTrue(waitForIndexing(currentQuery, 1), String.format("Result count not as expected for query: %s", currentQuery));
}
}

View File

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

View File

@@ -9,7 +9,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-tests</artifactId>
<version>17.125</version>
<version>17.155</version>
</parent>
<developers>
@@ -73,6 +73,7 @@
<dependency>
<groupId>org.alfresco.tas</groupId>
<artifactId>cmis</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>

View File

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

View File

@@ -2,10 +2,13 @@ package org.alfresco.rest.actions;
import static org.testng.Assert.assertFalse;
import com.google.common.collect.ImmutableMap;
import java.util.List;
import com.google.common.collect.ImmutableMap;
import org.alfresco.dataprep.CMISUtil;
import org.alfresco.rest.RestTest;
import org.alfresco.rest.model.RestActionConstraintDataModel;
import org.alfresco.rest.model.RestActionConstraintModel;
import org.alfresco.rest.model.RestActionDefinitionModel;
import org.alfresco.rest.model.RestActionDefinitionModelsCollection;
import org.alfresco.rest.model.RestNodeModel;
@@ -148,4 +151,66 @@ public class ActionsTests extends RestTest
restActionDefinition.getDescription().equals("This will add an aspect to the matched item.");
restActionDefinition.getTitle().equals("Add aspect");
}
@TestRail(section = {TestGroup.REST_API, TestGroup.ACTIONS}, executionType = ExecutionType.SANITY,
description = "Sanity test for ACTIONS endpoint GET action-conditions/{actionConstraintName}")
@Test(groups = {TestGroup.REST_API, TestGroup.ACTIONS, TestGroup.SANITY})
public void testGetSingleActionConstraint()
{
final UserModel testUser = dataUser.createRandomTestUser();
restClient.authenticateUser(testUser);
final String compareOperationsName = "ac-compare-operations";
final RestActionConstraintModel actionConstraintCompareOperations =
restClient.withCoreAPI().usingActions().getActionConstraintByName(compareOperationsName);
restClient.assertStatusCodeIs(HttpStatus.OK);
final RestActionConstraintModel expectedComparatorConstraints = new RestActionConstraintModel();
expectedComparatorConstraints.setConstraintName(compareOperationsName);
expectedComparatorConstraints.setConstraintValues(getComparatorConstraints());
actionConstraintCompareOperations.assertThat().isEqualTo(expectedComparatorConstraints);
}
@TestRail(section = {TestGroup.REST_API, TestGroup.ACTIONS}, executionType = ExecutionType.SANITY,
description = "Sanity test for ACTIONS endpoint GET action-conditions/{actionConstraintName} - non existing constraint name")
@Test(groups = {TestGroup.REST_API, TestGroup.ACTIONS, TestGroup.SANITY})
public void testGetSingleNonExistingActionConstraint()
{
final UserModel testUser = dataUser.createRandomTestUser();
restClient.authenticateUser(testUser);
restClient.withCoreAPI().usingActions().getActionConstraintByName("dummy-name");
restClient.assertStatusCodeIs(HttpStatus.NOT_FOUND);
}
private List<RestActionConstraintDataModel> getComparatorConstraints()
{
final RestActionConstraintDataModel equalsConstraint = new RestActionConstraintDataModel();
equalsConstraint.setValue("EQUALS");
equalsConstraint.setLabel("Equals");
final RestActionConstraintDataModel containsConstraint = new RestActionConstraintDataModel();
containsConstraint.setValue("CONTAINS");
containsConstraint.setLabel("Contains");
final RestActionConstraintDataModel beginsConstraint = new RestActionConstraintDataModel();
beginsConstraint.setValue("BEGINS");
beginsConstraint.setLabel("Begins With");
final RestActionConstraintDataModel endsConstraint = new RestActionConstraintDataModel();
endsConstraint.setValue("ENDS");
endsConstraint.setLabel("Ends With");
final RestActionConstraintDataModel greaterThanConstraint = new RestActionConstraintDataModel();
greaterThanConstraint.setValue("GREATER_THAN");
greaterThanConstraint.setLabel("Greater Than");
final RestActionConstraintDataModel greaterThanEqualConstraint = new RestActionConstraintDataModel();
greaterThanEqualConstraint.setValue("GREATER_THAN_EQUAL");
greaterThanEqualConstraint.setLabel("Greater Than Or Equal To");
final RestActionConstraintDataModel lessThanConstraint = new RestActionConstraintDataModel();
lessThanConstraint.setValue("LESS_THAN");
lessThanConstraint.setLabel("Less Than");
final RestActionConstraintDataModel lessThanEqualConstraint = new RestActionConstraintDataModel();
lessThanEqualConstraint.setValue("LESS_THAN_EQUAL");
lessThanEqualConstraint.setLabel("Less Than Or Equal To");
return List.of(equalsConstraint, containsConstraint, beginsConstraint, endsConstraint, greaterThanConstraint,
greaterThanEqualConstraint, lessThanConstraint, lessThanEqualConstraint);
}
}

View File

@@ -1,21 +1,20 @@
package org.alfresco.rest.actions.access;
import org.alfresco.rest.RestTest;
import org.alfresco.utility.model.FolderModel;
import org.alfresco.utility.model.UserModel;
import org.springframework.http.HttpStatus;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.alfresco.rest.core.RestWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import static org.alfresco.rest.actions.access.AccessRestrictionUtil.ERROR_MESSAGE_ACCESS_RESTRICTED;
import static org.alfresco.rest.actions.access.AccessRestrictionUtil.MAIL_ACTION;
import static org.alfresco.rest.actions.access.AccessRestrictionUtil.createMailParameters;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import org.alfresco.rest.RestTest;
import org.alfresco.rest.core.RestWrapper;
import org.alfresco.utility.model.FolderModel;
import org.alfresco.utility.model.UserModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
public class V1AdminAccessRestrictionTest extends RestTest {
private UserModel adminUser;
@@ -45,7 +44,7 @@ public class V1AdminAccessRestrictionTest extends RestTest {
.executeAction(MAIL_ACTION, testFolder, createMailParameters(adminUser, testUser));
restClient.onResponse()
.assertThat().statusCode(HttpStatus.INTERNAL_SERVER_ERROR.value())
.assertThat().statusCode(HttpStatus.FORBIDDEN.value())
.assertThat().body("entry.id", nullValue());
restClient.assertLastError().containsSummary(ERROR_MESSAGE_ACCESS_RESTRICTED);
}

View File

@@ -27,7 +27,20 @@ package org.alfresco.rest.rules;
import static java.util.stream.Collectors.toList;
import static org.alfresco.rest.rules.RulesTestsUtils.*;
import static org.alfresco.rest.actions.access.AccessRestrictionUtil.ERROR_MESSAGE_ACCESS_RESTRICTED;
import static org.alfresco.rest.rules.RulesTestsUtils.ID;
import static org.alfresco.rest.rules.RulesTestsUtils.INVERTED;
import static org.alfresco.rest.rules.RulesTestsUtils.IS_SHARED;
import static org.alfresco.rest.rules.RulesTestsUtils.RULE_NAME_DEFAULT;
import static org.alfresco.rest.rules.RulesTestsUtils.createAddAudioAspectAction;
import static org.alfresco.rest.rules.RulesTestsUtils.createCompositeCondition;
import static org.alfresco.rest.rules.RulesTestsUtils.createRuleModel;
import static org.alfresco.rest.rules.RulesTestsUtils.createRuleModelWithDefaultValues;
import static org.alfresco.rest.rules.RulesTestsUtils.createRuleModelWithModifiedValues;
import static org.alfresco.rest.rules.RulesTestsUtils.createRuleWithPrivateAction;
import static org.alfresco.rest.rules.RulesTestsUtils.createSimpleCondition;
import static org.alfresco.rest.rules.RulesTestsUtils.createVariousActions;
import static org.alfresco.rest.rules.RulesTestsUtils.createVariousConditions;
import static org.alfresco.utility.constants.UserRole.SiteCollaborator;
import static org.alfresco.utility.constants.UserRole.SiteConsumer;
import static org.alfresco.utility.constants.UserRole.SiteContributor;
@@ -41,8 +54,6 @@ import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.FORBIDDEN;
import static org.springframework.http.HttpStatus.NOT_FOUND;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.IntStream;
@@ -93,7 +104,6 @@ public class CreateRulesTests extends RestTest
.createSingleRule(ruleModel);
RestRuleModel expectedRuleModel = createRuleModelWithModifiedValues();
expectedRuleModel.setConditions(createEmptyConditionModel());
restClient.assertStatusCodeIs(CREATED);
rule.assertThat().isEqualTo(expectedRuleModel, ID, IS_SHARED)
.assertThat().field(ID).isNotNull()
@@ -353,7 +363,7 @@ public class CreateRulesTests extends RestTest
STEP(String.format("Add a user with '%s' role in the private site's folder", userRole.toString()));
UserModel userWithRole = dataUser.createRandomTestUser();
dataUser.addUserToSite(userWithRole, privateSite, userRole);
RestRuleModel ruleModel = createRuleModel("testRule", List.of(createDefaultActionModel()));
RestRuleModel ruleModel = createRuleModel("testRule", List.of(createAddAudioAspectAction()));
return restClient.authenticateUser(userWithRole).withCoreAPI().usingNode(privateFolder).usingDefaultRuleSet().createSingleRule(ruleModel);
}
@@ -364,26 +374,13 @@ public class CreateRulesTests extends RestTest
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void createRuleWithActions()
{
final Map<String, Serializable> copyParams =
Map.of("destination-folder", "dummy-folder-node", "deep-copy", true);
final RestActionBodyExecTemplateModel copyAction = createCustomActionModel("copy", copyParams);
final Map<String, Serializable> checkOutParams =
Map.of("destination-folder", "dummy-folder-node", "assoc-name", "cm:checkout", "assoc-type",
"cm:contains");
final RestActionBodyExecTemplateModel checkOutAction = createCustomActionModel("check-out", checkOutParams);
final Map<String, Serializable> scriptParams = Map.of("script-ref", "dummy-script-node-id");
final RestActionBodyExecTemplateModel scriptAction = createCustomActionModel("script", scriptParams);
final RestRuleModel ruleModel = createRuleModelWithDefaultValues();
ruleModel.setActions(Arrays.asList(copyAction, checkOutAction, scriptAction));
final UserModel admin = dataUser.getAdminUser();
final RestRuleModel rule = restClient.authenticateUser(admin).withCoreAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.createSingleRule(ruleModel);
.createSingleRule(createVariousActions());
final RestRuleModel expectedRuleModel = createRuleModelWithDefaultValues();
expectedRuleModel.setActions(Arrays.asList(copyAction, checkOutAction, scriptAction));
expectedRuleModel.setConditions(createEmptyConditionModel());
RestRuleModel expectedRuleModel = createRuleModelWithDefaultValues();
expectedRuleModel.setActions(createVariousActions().getActions());
expectedRuleModel.setTriggers(List.of("inbound"));
restClient.assertStatusCodeIs(CREATED);
@@ -391,6 +388,27 @@ public class CreateRulesTests extends RestTest
.assertThat().field(IS_SHARED).isNull();
}
/** Check that a normal user cannot create rules that use private actions. */
@Test
public void createRuleWithActions_userCannotUsePrivateAction()
{
restClient.authenticateUser(user).withCoreAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.createSingleRule(createRuleWithPrivateAction());
restClient.assertStatusCodeIs(FORBIDDEN)
.assertLastError().containsSummary(ERROR_MESSAGE_ACCESS_RESTRICTED);
}
/** Check that an administrator can create rules that use private actions. */
@Test
public void createRuleWithActions_adminCanUsePrivateAction()
{
restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.createSingleRule(createRuleWithPrivateAction());
restClient.assertStatusCodeIs(CREATED);
}
/**
* Check we get error when attempt to create a rule without any actions.
*/
@@ -459,7 +477,6 @@ public class CreateRulesTests extends RestTest
.createSingleRule(ruleModel);
RestRuleModel expectedRuleModel = createRuleModelWithDefaultValues();
expectedRuleModel.setConditions(createCompositeCondition(null));
expectedRuleModel.setTriggers(List.of("inbound"));
restClient.assertStatusCodeIs(CREATED);
rule.assertThat().isEqualTo(expectedRuleModel, ID, IS_SHARED);

View File

@@ -0,0 +1,320 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.rest.rules;
import static org.alfresco.rest.rules.RulesTestsUtils.AUDIO_ASPECT;
import static org.alfresco.rest.rules.RulesTestsUtils.LOCKABLE_ASPECT;
import static org.alfresco.rest.rules.RulesTestsUtils.RULE_NAME_DEFAULT;
import static org.alfresco.rest.rules.RulesTestsUtils.assertThat;
import static org.alfresco.rest.rules.RulesTestsUtils.createAddAspectAction;
import static org.alfresco.rest.rules.RulesTestsUtils.createCustomActionModel;
import static org.alfresco.rest.rules.RulesTestsUtils.createRuleExecutionRequest;
import static org.alfresco.rest.rules.RulesTestsUtils.createRuleModel;
import static org.alfresco.rest.rules.RulesTestsUtils.createRuleModelWithDefaultValues;
import static org.alfresco.utility.report.log.Step.STEP;
import java.util.List;
import java.util.Map;
import org.alfresco.dataprep.CMISUtil;
import org.alfresco.rest.RestTest;
import org.alfresco.rest.model.RestActionBodyExecTemplateModel;
import org.alfresco.rest.model.RestNodeModel;
import org.alfresco.rest.model.RestRuleExecutionModel;
import org.alfresco.rest.model.RestRuleModel;
import org.alfresco.utility.constants.UserRole;
import org.alfresco.utility.model.FileModel;
import org.alfresco.utility.model.FolderModel;
import org.alfresco.utility.model.SiteModel;
import org.alfresco.utility.model.TestGroup;
import org.alfresco.utility.model.UserModel;
import org.springframework.http.HttpStatus;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
/**
* Tests for POST /nodes/{nodeId}/rule-executions.
*/
@Test(groups = { TestGroup.RULES})
public class ExecuteRulesTests extends RestTest
{
private UserModel user;
private SiteModel site;
private FolderModel parentFolder;
private FolderModel childFolder;
private FileModel parentFolderFile;
private FileModel childFolderFile;
private RestRuleModel parentFolderRule;
private RestRuleModel childFolderRule;
@BeforeClass(alwaysRun = true)
public void dataPreparation()
{
STEP("Create user and a site");
user = dataUser.createRandomTestUser();
site = dataSite.usingUser(user).createPublicRandomSite();
}
@BeforeMethod
public void setUp()
{
STEP("Create parent folder, rule folder and file in it");
parentFolder = dataContent.usingUser(user).usingSite(site).createFolder();
childFolder = dataContent.usingUser(user).usingResource(parentFolder).createFolder();
parentFolderFile = dataContent.usingUser(user).usingResource(parentFolder).createContent(CMISUtil.DocumentType.TEXT_PLAIN);
childFolderFile = dataContent.usingUser(user).usingResource(childFolder).createContent(CMISUtil.DocumentType.TEXT_PLAIN);
STEP("Create rules for parent and rule folders");
RestActionBodyExecTemplateModel addLockableAspectAction = createAddAspectAction(LOCKABLE_ASPECT);
RestRuleModel ruleModel = createRuleModel(RULE_NAME_DEFAULT, List.of(addLockableAspectAction));
ruleModel.setIsInheritable(true);
parentFolderRule = restClient.authenticateUser(user).withCoreAPI().usingNode(parentFolder).usingDefaultRuleSet().createSingleRule(ruleModel);
childFolderRule = restClient.authenticateUser(user).withCoreAPI().usingNode(childFolder).usingDefaultRuleSet().createSingleRule(createRuleModelWithDefaultValues());
}
/**
* Execute one rule with one action trying to add audio aspect to a file.
*/
@Test(groups = { TestGroup.REST_API, TestGroup.RULES, TestGroup.ACTIONS, TestGroup.SANITY })
public void executeRules_onlyOwnedRules()
{
STEP("Check if file aspects don't contain Audio one");
RestNodeModel fileNode = restClient.authenticateUser(user).withCoreAPI().usingNode(childFolderFile).getNode();
restClient.assertStatusCodeIs(HttpStatus.OK);
assertThat(fileNode).notContainsAspects(AUDIO_ASPECT);
STEP("Execute rule");
RestRuleExecutionModel executionResult = restClient.authenticateUser(user).withCoreAPI().usingNode(childFolder).executeRules(createRuleExecutionRequest());
restClient.assertStatusCodeIs(HttpStatus.CREATED);
executionResult.assertThat().field("isEachSubFolderIncluded").is(false);
STEP("Check if only Audio aspect was added");
fileNode = restClient.authenticateUser(user).withCoreAPI().usingNode(childFolderFile).getNode();
restClient.assertStatusCodeIs(HttpStatus.OK);
assertThat(fileNode).containsAspects(AUDIO_ASPECT);
}
/**
* Execute owned rule adding Audio aspect and inherited rule adding Lockable aspect.
*/
@Test(groups = { TestGroup.REST_API, TestGroup.RULES, TestGroup.ACTIONS, TestGroup.SANITY })
public void executeRules_includeInheritedRules()
{
STEP("Check if file aspects don't contain Audio and Lockable ones");
RestNodeModel fileNode = restClient.authenticateUser(user).withCoreAPI().usingNode(childFolderFile).getNode();
restClient.assertStatusCodeIs(HttpStatus.OK);
assertThat(fileNode).notContainsAspects(AUDIO_ASPECT, LOCKABLE_ASPECT);
STEP("Execute rules including inherited rules");
RestRuleExecutionModel ruleExecutionRequest = createRuleExecutionRequest();
RestRuleExecutionModel executionResult = restClient.authenticateUser(user).withCoreAPI().usingNode(childFolder).executeRules(ruleExecutionRequest);
restClient.assertStatusCodeIs(HttpStatus.CREATED);
executionResult.assertThat().field("isEachSubFolderIncluded").is(false);
STEP("Check if Audio and Lockable aspects were added");
fileNode = restClient.authenticateUser(user).withCoreAPI().usingNode(childFolderFile).getNode();
restClient.assertStatusCodeIs(HttpStatus.OK);
assertThat(fileNode).containsAspects(AUDIO_ASPECT, LOCKABLE_ASPECT);
}
/**
* Execute rules on parent folder (add Lockable aspect) including sub-folder folders (add Audio aspect).
*/
@Test(groups = { TestGroup.REST_API, TestGroup.RULES, TestGroup.ACTIONS, TestGroup.SANITY })
public void executeRules_includeSubFolderRules()
{
STEP("Check if parent folder's file aspects don't contain Audio and Lockable ones");
RestNodeModel fileNode = restClient.authenticateUser(user).withCoreAPI().usingNode(parentFolderFile).getNode();
restClient.assertStatusCodeIs(HttpStatus.OK);
assertThat(fileNode).notContainsAspects(AUDIO_ASPECT, LOCKABLE_ASPECT);
STEP("Check if child folder's file aspects don't contain Audio and Lockable ones");
fileNode = restClient.authenticateUser(user).withCoreAPI().usingNode(childFolderFile).getNode();
restClient.assertStatusCodeIs(HttpStatus.OK);
assertThat(fileNode).notContainsAspects(AUDIO_ASPECT, LOCKABLE_ASPECT);
STEP("Execute rules on parent folder including sub-folders");
RestRuleExecutionModel ruleExecutionRequest = createRuleExecutionRequest();
ruleExecutionRequest.setIsEachSubFolderIncluded(true);
RestRuleExecutionModel executionResult = restClient.authenticateUser(user).withCoreAPI().usingNode(parentFolder).executeRules(ruleExecutionRequest);
restClient.assertStatusCodeIs(HttpStatus.CREATED);
executionResult.assertThat().field("isEachSubFolderIncluded").is(true);
STEP("Check if Lockable aspects was added to parent folder's file");
fileNode = restClient.authenticateUser(user).withCoreAPI().usingNode(parentFolderFile).getNode();
restClient.assertStatusCodeIs(HttpStatus.OK);
assertThat(fileNode)
.containsAspects(LOCKABLE_ASPECT)
.notContainsAspects(AUDIO_ASPECT);
STEP("Check if Audio and Lockable aspects were added to child folder's file");
fileNode = restClient.authenticateUser(user).withCoreAPI().usingNode(childFolderFile).getNode();
restClient.assertStatusCodeIs(HttpStatus.OK);
assertThat(fileNode)
.containsAspects(AUDIO_ASPECT, LOCKABLE_ASPECT);
}
/**
* Try to execute disabled rule and check if nothing changed.
*/
@Test(groups = { TestGroup.REST_API, TestGroup.RULES, TestGroup.ACTIONS })
public void executeRules_disabledRule()
{
STEP("Disable child rules");
RestRuleModel updatedChildRule = createRuleModelWithDefaultValues();
updatedChildRule.setIsEnabled(false);
restClient.authenticateUser(user).withCoreAPI().usingNode(childFolder).usingDefaultRuleSet().updateRule(childFolderRule.getId(), updatedChildRule);
STEP("Check if file aspects don't contain Audio one");
RestNodeModel fileNode = restClient.authenticateUser(user).withCoreAPI().usingNode(childFolderFile).getNode();
restClient.assertStatusCodeIs(HttpStatus.OK);
assertThat(fileNode).notContainsAspects(AUDIO_ASPECT);
STEP("Execute rule");
RestRuleExecutionModel executionResult = restClient.authenticateUser(user).withCoreAPI().usingNode(childFolder).executeRules(createRuleExecutionRequest());
restClient.assertStatusCodeIs(HttpStatus.CREATED);
executionResult.assertThat().field("isEachSubFolderIncluded").is(false);
STEP("Check if Audio aspect is still missing");
fileNode = restClient.authenticateUser(user).withCoreAPI().usingNode(childFolderFile).getNode();
restClient.assertStatusCodeIs(HttpStatus.OK);
assertThat(fileNode).notContainsAspects(AUDIO_ASPECT);
}
/**
* Try to execute inherited parent folder's rule which is not inheritable.
*/
@Test(groups = { TestGroup.REST_API, TestGroup.RULES, TestGroup.ACTIONS })
public void executeRules_notInheritableRule()
{
STEP("Set parent rule as not inheritable");
RestRuleModel updatedParentRule = createRuleModelWithDefaultValues();
updatedParentRule.setIsInheritable(false);
restClient.authenticateUser(user).withCoreAPI().usingNode(parentFolder).usingDefaultRuleSet().updateRule(parentFolderRule.getId(), updatedParentRule);
STEP("Check if file aspects don't contain Audio and Lockable ones");
RestNodeModel fileNode = restClient.authenticateUser(user).withCoreAPI().usingNode(childFolderFile).getNode();
restClient.assertStatusCodeIs(HttpStatus.OK);
assertThat(fileNode).notContainsAspects(AUDIO_ASPECT, LOCKABLE_ASPECT);
STEP("Execute child folder rules including inherited rules");
RestRuleExecutionModel executionResult = restClient.authenticateUser(user).withCoreAPI().usingNode(childFolder).executeRules(createRuleExecutionRequest());
restClient.assertStatusCodeIs(HttpStatus.CREATED);
executionResult.assertThat().field("isEachSubFolderIncluded").is(false);
STEP("Check if Audio aspect is present and Lockable is still missing");
fileNode = restClient.authenticateUser(user).withCoreAPI().usingNode(childFolderFile).getNode();
restClient.assertStatusCodeIs(HttpStatus.OK);
assertThat(fileNode)
.containsAspects(AUDIO_ASPECT)
.notContainsAspects(LOCKABLE_ASPECT);
}
/**
* Try to execute private folder's rules by user not added to site and receive 403.
*/
@Test(groups = { TestGroup.REST_API, TestGroup.RULES, TestGroup.ACTIONS })
public void executeRules_privateFolderResultsWith403()
{
STEP("Using admin create private site, folder and rule");
SiteModel privateSite = dataSite.usingAdmin().createPrivateRandomSite();
FolderModel privateFolder = dataContent.usingAdmin().usingSite(privateSite).createFolder();
dataContent.usingAdmin().usingResource(privateFolder).createContent(CMISUtil.DocumentType.TEXT_PLAIN);
restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI().usingNode(privateFolder).usingDefaultRuleSet().createSingleRule(createRuleModelWithDefaultValues());
STEP("Try to execute private folder's rules by user");
restClient.authenticateUser(user).withCoreAPI().usingNode(privateFolder).executeRules(createRuleExecutionRequest());
restClient.assertStatusCodeIs(HttpStatus.FORBIDDEN);
}
/**
* Try to execute private folder's rules as site contributor and receive 403.
*/
@Test(groups = { TestGroup.REST_API, TestGroup.RULES, TestGroup.ACTIONS })
public void executeRules_privateFolderAsContributorResultsWith403()
{
STEP("Using admin create private site, folder, file in it, rule and add user to site as contributor");
UserModel contributor = dataUser.createRandomTestUser();
SiteModel privateSite = dataSite.usingAdmin().createPrivateRandomSite();
FolderModel privateFolder = dataContent.usingAdmin().usingSite(privateSite).createFolder();
dataContent.usingAdmin().usingResource(privateFolder).createContent(CMISUtil.DocumentType.TEXT_PLAIN);
restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI().usingNode(privateFolder).usingDefaultRuleSet().createSingleRule(createRuleModelWithDefaultValues());
dataUser.usingAdmin().addUserToSite(contributor, privateSite, UserRole.SiteContributor);
STEP("Try to execute private folder's rules by contributor");
restClient.authenticateUser(contributor).withCoreAPI().usingNode(privateFolder).executeRules(createRuleExecutionRequest());
restClient.assertStatusCodeIs(HttpStatus.FORBIDDEN);
}
/**
* Execute private folder's rules as site collaborator.
*/
@Test(groups = { TestGroup.REST_API, TestGroup.RULES, TestGroup.ACTIONS })
public void executeRules_privateFolderAsCollaborator()
{
STEP("Using admin create private site, folder, file in it, rule and add user to site as collaborator");
UserModel collaborator = dataUser.createRandomTestUser();
UserModel admin = dataUser.getAdminUser();
SiteModel privateSite = dataSite.usingAdmin().createPrivateRandomSite();
FolderModel privateFolder = dataContent.usingAdmin().usingSite(privateSite).createFolder();
FileModel privateFile = dataContent.usingAdmin().usingResource(privateFolder).createContent(CMISUtil.DocumentType.TEXT_PLAIN);
restClient.authenticateUser(admin).withCoreAPI().usingNode(privateFolder).usingDefaultRuleSet().createSingleRule(createRuleModelWithDefaultValues());
dataUser.usingAdmin().addUserToSite(collaborator, privateSite, UserRole.SiteCollaborator);
STEP("Check if file aspects don't contain Audio one");
RestNodeModel fileNode = restClient.authenticateUser(admin).withCoreAPI().usingNode(privateFile).getNode();
restClient.assertStatusCodeIs(HttpStatus.OK);
assertThat(fileNode).notContainsAspects(AUDIO_ASPECT);
STEP("Execute private folder's rules by collaborator");
restClient.authenticateUser(collaborator).withCoreAPI().usingNode(privateFolder).executeRules(createRuleExecutionRequest());
restClient.assertStatusCodeIs(HttpStatus.CREATED);
STEP("Check if Audio aspect is present");
fileNode = restClient.authenticateUser(admin).withCoreAPI().usingNode(privateFile).getNode();
restClient.assertStatusCodeIs(HttpStatus.OK);
assertThat(fileNode).containsAspects(AUDIO_ASPECT);
}
/**
* Try to execute rule with broken action and receive 500.
*/
@Test(groups = { TestGroup.REST_API, TestGroup.RULES, TestGroup.ACTIONS })
public void executeRules_brokenActionResultsWith500()
{
STEP("Update folder rule with broken action");
RestActionBodyExecTemplateModel brokenAction = createCustomActionModel("set-property-value", Map.of("aspect-name", AUDIO_ASPECT));
childFolderRule.setActions(List.of(brokenAction));
restClient.authenticateUser(user).withCoreAPI().usingNode(childFolder).usingDefaultRuleSet().updateRule(childFolderRule.getId(), childFolderRule);
restClient.assertStatusCodeIs(HttpStatus.OK);
STEP("Try to execute rule with broken action");
restClient.authenticateUser(user).withCoreAPI().usingNode(childFolder).executeRules(createRuleExecutionRequest());
restClient.assertStatusCodeIs(HttpStatus.INTERNAL_SERVER_ERROR);
}
}

View File

@@ -31,6 +31,7 @@ import static org.alfresco.rest.rules.RulesTestsUtils.createRuleModelWithDefault
import static org.alfresco.rest.rules.RulesTestsUtils.createRuleModelWithModifiedValues;
import static org.alfresco.utility.report.log.Step.STEP;
import static org.junit.Assert.assertTrue;
import static org.springframework.http.HttpStatus.FORBIDDEN;
import static org.springframework.http.HttpStatus.NOT_FOUND;
import static org.springframework.http.HttpStatus.OK;
@@ -62,6 +63,8 @@ public class GetRuleSetsTests extends RestTest
private UserModel user;
private SiteModel site;
private FolderModel ruleFolder;
/** A folder with a rule in a private site owned by admin. */
private FolderModel privateFolder;
private FolderModel inheritingChildFolder;
private FolderModel notInheritingChildFolder;
private RestRuleModel rule;
@@ -93,6 +96,11 @@ public class GetRuleSetsTests extends RestTest
.getListOfRuleSets();
ruleSets.assertThat().entriesListCountIs(1);
ruleSetId = ruleSets.getEntries().get(0).onModel().getId();
STEP("Use admin to create a private site containing a rule in a rule set that can be inherited.");
SiteModel privateSite = dataSite.usingAdmin().createPrivateRandomSite();
privateFolder = dataContent.usingAdmin().usingSite(privateSite).createFolder();
coreAPIForAdmin().usingNode(privateFolder).usingDefaultRuleSet().createSingleRule(createRuleModelWithModifiedValues());
}
/** Check we can get an empty list of rule sets. */
@@ -135,6 +143,48 @@ public class GetRuleSetsTests extends RestTest
restClient.assertStatusCodeIs(NOT_FOUND);
}
/** Check that we get a 403 error when trying to get rule sets for a folder we don't have read access to. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
public void getRuleSetsWithoutPermission()
{
STEP("Check a user cannot list rule sets without read access.");
coreAPIForUser().usingNode(privateFolder).getListOfRuleSets();
restClient.assertStatusCodeIs(FORBIDDEN);
}
/** Check that we can still list some rule sets if we don't have permission to view them all. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
public void permissionsAreRespectedWhenListingRuleSets()
{
STEP("Create a public site containing a parent and child folder with rule inheritance enabled.");
SiteModel publicSite = dataSite.usingUser(user).createPublicRandomSite();
FolderModel parentFolder = dataContent.usingUser(user).usingSite(publicSite).createFolder();
FolderModel childFolder = dataContent.usingUser(user).usingResource(parentFolder).createFolder();
RestRuleSettingsModel enabled = new RestRuleSettingsModel();
enabled.setValue(true);
coreAPIForUser().usingNode(parentFolder).usingRuleSetting(IS_INHERITANCE_ENABLED).updateSetting(enabled);
STEP("Link the parent folder to a private rule set.");
RestRuleSetLinkModel linkModel = new RestRuleSetLinkModel();
linkModel.setId(privateFolder.getNodeRef());
coreAPIForAdmin().usingNode(parentFolder).createRuleLink(linkModel);
STEP("Create a rule on the child folder.");
coreAPIForUser().usingNode(childFolder).usingDefaultRuleSet().createSingleRule(createRuleModelWithDefaultValues());
STEP("Check admin can view both rule sets.");
RestRuleSetModelsCollection adminViewOfRuleSets = coreAPIForAdmin().usingNode(childFolder).getListOfRuleSets();
restClient.assertStatusCodeIs(OK);
RestRuleSetModel parentRuleSet = adminViewOfRuleSets.getEntries().get(0).onModel();
RestRuleSetModel childRuleSet = adminViewOfRuleSets.getEntries().get(1).onModel();
STEP("Check the normal user can only view the child rule set.");
RestRuleSetModelsCollection userViewOfRuleSets = coreAPIForUser().usingNode(childFolder).getListOfRuleSets();
restClient.assertStatusCodeIs(OK);
userViewOfRuleSets.assertThat().entriesListContains("id", childRuleSet.getId())
.and().entriesListDoesNotContain("id", parentRuleSet.getId());
}
/** Check we can get the id of the folder that owns a list of rule sets. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
public void getRuleSetsAndOwningFolders()
@@ -354,7 +404,7 @@ public class GetRuleSetsTests extends RestTest
RestRuleSetLinkModel ruleSetLink = new RestRuleSetLinkModel();
ruleSetLink.setId(ruleFolder.getNodeRef());
coreAPIForUser().usingNode(publicFolder).createRuleLink(ruleSetLink);
coreAPIForUser().usingNode(privateFolder).createRuleLink(ruleSetLink);
coreAPIForAdmin().usingNode(privateFolder).createRuleLink(ruleSetLink);
STEP("Get the rule set and linkedToBy field");
RestRuleSetModel ruleSet = coreAPIForUser().usingNode(ruleFolder)
@@ -447,6 +497,94 @@ public class GetRuleSetsTests extends RestTest
ruleSet.assertThat().field("isInherited").is(false);
}
/** Check that a user can see that a rule set is linked to even if they don't have permission to view the linking folder. */
@Test
public void getRuleSetAndIsLinkedToWithoutPermission()
{
STEP("Create a site owned by admin and add user as a contributor");
SiteModel siteModel = dataSite.usingAdmin().createPrivateRandomSite();
dataUser.addUserToSite(user, siteModel, UserRole.SiteContributor);
STEP("Create a folder with a rule set");
FolderModel ruleFolder = dataContent.usingUser(user).usingSite(siteModel).createFolder();
RestRuleModel ruleModel = createRuleModelWithDefaultValues();
coreAPIForUser().usingNode(ruleFolder).usingDefaultRuleSet().createSingleRule(ruleModel);
STEP("Create a private folder linking to the rule set");
FolderModel linkingFolder = dataContent.usingAdmin().usingSite(siteModel).createFolder();
RestRuleSetLinkModel linkModel = new RestRuleSetLinkModel();
linkModel.setId(ruleFolder.getNodeRef());
coreAPIForAdmin().usingNode(linkingFolder).createRuleLink(linkModel);
STEP("Remove the user from the site");
dataUser.removeUserFromSite(user, siteModel);
STEP("Get the rule set and isLinkedTo field");
RestRuleSetModel ruleSet = coreAPIForUser().usingNode(ruleFolder)
.include("isLinkedTo", "linkedToBy", "owningFolder")
.getDefaultRuleSet();
restClient.assertStatusCodeIs(OK);
ruleSet.assertThat().field("isLinkedTo").is(true)
.assertThat().field("linkedToBy").isEmpty();
}
/**
* Check that if a rule set is owned and inherited but not linked to then isLinkedTo returns false.
*/
@Test
public void getRuleSetAndIsLinkedToCanBeFalse()
{
STEP("Create a site, a folder with a rule and a child folder that inherits it");
SiteModel siteModel = dataSite.usingUser(user).createPublicRandomSite();
FolderModel ruleFolder = dataContent.usingUser(user).usingSite(siteModel).createFolder();
RestRuleModel ruleModel = createRuleModelWithDefaultValues();
coreAPIForUser().usingNode(ruleFolder).usingDefaultRuleSet().createSingleRule(ruleModel);
dataContent.usingUser(user).usingResource(ruleFolder).createFolder();
STEP("Get the rule set and isLinkedTo field");
RestRuleSetModel ruleSet = coreAPIForUser().usingNode(ruleFolder)
.include("isLinkedTo")
.getDefaultRuleSet();
restClient.assertStatusCodeIs(OK);
ruleSet.assertThat().field("isLinkedTo").is(false);
}
/** Check that we can only view a rule set if have read permission. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
public void permissionsChecksForFolderWithPrivateAndPublicRuleSets()
{
STEP("Create a public site containing a parent and child folder with rule inheritance enabled.");
SiteModel publicSite = dataSite.usingUser(user).createPublicRandomSite();
FolderModel parentFolder = dataContent.usingUser(user).usingSite(publicSite).createFolder();
FolderModel childFolder = dataContent.usingUser(user).usingResource(parentFolder).createFolder();
RestRuleSettingsModel enabled = new RestRuleSettingsModel();
enabled.setValue(true);
coreAPIForUser().usingNode(parentFolder).usingRuleSetting(IS_INHERITANCE_ENABLED).updateSetting(enabled);
STEP("Link the parent folder to a private rule set.");
RestRuleSetLinkModel linkModel = new RestRuleSetLinkModel();
linkModel.setId(privateFolder.getNodeRef());
coreAPIForAdmin().usingNode(parentFolder).createRuleLink(linkModel);
STEP("Create a rule on the child folder.");
coreAPIForUser().usingNode(childFolder).usingDefaultRuleSet().createSingleRule(createRuleModelWithDefaultValues());
STEP("Use the admin user to get both rule sets.");
RestRuleSetModelsCollection adminViewOfRuleSets = coreAPIForAdmin().usingNode(childFolder).getListOfRuleSets();
RestRuleSetModel parentRuleSet = adminViewOfRuleSets.getEntries().get(0).onModel();
RestRuleSetModel childRuleSet = adminViewOfRuleSets.getEntries().get(1).onModel();
STEP("Check the normal user can only view the child rule set.");
coreAPIForUser().usingNode(childFolder).getRuleSet(parentRuleSet.getId());
restClient.assertStatusCodeIs(FORBIDDEN);
coreAPIForUser().usingNode(childFolder).getRuleSet(childRuleSet.getId());
restClient.assertStatusCodeIs(OK);
}
private RestCoreAPI coreAPIForUser()
{
return restClient.authenticateUser(user).withCoreAPI();

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
@@ -63,6 +63,8 @@ public class GetRulesTests extends RestTest
private RestRuleModel createdRuleA;
private static final String IGNORE_ID = "id";
private static final String IGNORE_IS_SHARED = "isShared";
private static final String ACTIONS = "actions";
private static final String CONDITIONS = "conditions";
@BeforeClass(alwaysRun = true)
public void dataPreparation()
@@ -150,11 +152,11 @@ public class GetRulesTests extends RestTest
rules.getEntries().get(i).onModel()
.assertThat().field("isShared").isNotNull()
.assertThat().field("description").isNull()
.assertThat().field("enabled").is(false)
.assertThat().field("cascade").is(false)
.assertThat().field("asynchronous").is(false)
.assertThat().field("isEnabled").is(true)
.assertThat().field("isInheritable").is(false)
.assertThat().field("isAsynchronous").is(false)
.assertThat().field("errorScript").isNull()
.assertThat().field("shared").isNull()
.assertThat().field("isShared").is(false)
.assertThat().field("triggers").is("[inbound]"));
}
@@ -190,7 +192,6 @@ public class GetRulesTests extends RestTest
RestRuleModel expectedRuleModel = createRuleModelWithModifiedValues();
expectedRuleModel.setTriggers(List.of("update"));
expectedRuleModel.setConditions(createEmptyConditionModel());
restClient.assertStatusCodeIs(CREATED);
rule.assertThat().isEqualTo(expectedRuleModel, IGNORE_ID, IGNORE_IS_SHARED)
@@ -212,7 +213,6 @@ public class GetRulesTests extends RestTest
RestRuleModel expectedRuleModel = createRuleModelWithDefaultValues();
expectedRuleModel.setTriggers(List.of("inbound"));
expectedRuleModel.setConditions(createEmptyConditionModel());
restClient.assertStatusCodeIs(CREATED);
@@ -307,4 +307,56 @@ public class GetRulesTests extends RestTest
restClient.assertStatusCodeIs(OK);
rules.assertThat().entriesListContains("name", "Private site rule");
}
/**
* Check we can GET Rule's actions.
*/
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void getRuleActions()
{
STEP("Create a rule with a few actions");
FolderModel folder = dataContent.usingUser(user).usingSite(site).createFolder();
final RestRuleModel rule = restClient.authenticateUser(user).withCoreAPI().usingNode(folder).usingDefaultRuleSet()
.createSingleRule(createVariousActions());
STEP("Retrieve the created rule via the GET endpoint");
final RestRuleModel getRuleBody = restClient.authenticateUser(user).withCoreAPI().usingNode(folder).usingDefaultRuleSet().getSingleRule(rule.getId());
STEP("Assert that actions are returned as expected from the GET endpoint");
restClient.assertStatusCodeIs(OK);
getRuleBody.assertThat().field(ACTIONS).contains("actionDefinitionId=copy")
.assertThat().field(ACTIONS).contains("destination-folder=dummy-folder-node")
.assertThat().field(ACTIONS).contains("deep-copy=true")
.assertThat().field(ACTIONS).contains("actionDefinitionId=check-out")
.assertThat().field(ACTIONS).contains("destination-folder=fake-folder-node")
.assertThat().field(ACTIONS).contains("assoc-name=cm:checkout");
}
/**
* Check we can GET rule's conditions.
*/
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void getRulesConditions()
{
STEP("Create a rule with several conditions");
RestRuleModel ruleModel = createRuleModelWithDefaultValues();
ruleModel.setConditions(createVariousConditions());
FolderModel folder = dataContent.usingUser(user).usingSite(site).createFolder();
RestRuleModel rule = restClient.authenticateUser(user).withCoreAPI().usingNode(folder).usingDefaultRuleSet()
.createSingleRule(ruleModel);
STEP("Retrieve the created rule via the GET endpoint");
final RestRuleModel getRuleBody = restClient.authenticateUser(user).withCoreAPI().usingNode(folder).usingDefaultRuleSet().getSingleRule(rule.getId());
STEP("Assert that conditions are retrieved using the GET endpoint");
restClient.assertStatusCodeIs(OK);
getRuleBody.assertThat().field(CONDITIONS).contains("comparator=ends")
.assertThat().field(CONDITIONS).contains("field=cm:creator")
.assertThat().field(CONDITIONS).contains("parameter=ski")
.assertThat().field(CONDITIONS).contains("comparator=begins")
.assertThat().field(CONDITIONS).contains("field=cm:modelVersion")
.assertThat().field(CONDITIONS).contains("parameter=1.");
}
}

View File

@@ -0,0 +1,204 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.rest.rules;
import static java.util.stream.Collectors.toList;
import static org.alfresco.rest.rules.RulesTestsUtils.createRuleModelWithDefaultValues;
import static org.alfresco.utility.report.log.Step.STEP;
import static org.springframework.http.HttpStatus.FORBIDDEN;
import static org.springframework.http.HttpStatus.OK;
import java.util.List;
import java.util.stream.IntStream;
import com.google.common.collect.Lists;
import org.alfresco.rest.RestTest;
import org.alfresco.rest.model.RestRuleModel;
import org.alfresco.rest.model.RestRuleSetModel;
import org.alfresco.utility.constants.UserRole;
import org.alfresco.utility.model.FolderModel;
import org.alfresco.utility.model.SiteModel;
import org.alfresco.utility.model.TestGroup;
import org.alfresco.utility.model.UserModel;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
@Test (groups = { TestGroup.RULES })
public class ReorderRules extends RestTest
{
private UserModel user;
private SiteModel site;
@BeforeClass (alwaysRun = true)
public void dataPreparation()
{
STEP("Create a user and site.");
user = dataUser.createRandomTestUser();
site = dataSite.usingUser(user).createPublicRandomSite();
}
/** Check we can get the ordered list of rules in a rule set. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
public void getOrderedRuleIds()
{
STEP("Create a folder containing three rules in the existing site");
FolderModel folder = dataContent.usingUser(user).usingSite(site).createFolder();
List<RestRuleModel> rules = createRulesInFolder(folder, user);
STEP("Get the default rule set for the folder including the ordered rule ids");
RestRuleSetModel ruleSet = restClient.authenticateUser(user).withCoreAPI().usingNode(folder)
.include("ruleIds").getDefaultRuleSet();
List<String> expectedRuleIds = rules.stream().map(RestRuleModel::getId).collect(toList());
restClient.assertStatusCodeIs(OK);
ruleSet.assertThat().field("ruleIds").is(expectedRuleIds);
}
/** Check that a user can view the order of the rules in a rule set if they only have read permission. */
@Test
public void getRuleSetAndRuleIdsWithReadOnlyPermission()
{
STEP("Create a site owned by admin and add user as a consumer");
SiteModel siteModel = dataSite.usingAdmin().createPrivateRandomSite();
dataUser.addUserToSite(user, siteModel, UserRole.SiteConsumer);
STEP("Use admin to create a folder with a rule set and three rules in it");
FolderModel ruleFolder = dataContent.usingAdmin().usingSite(siteModel).createFolder();
dataContent.usingAdmin().usingResource(ruleFolder).createFolder();
List<RestRuleModel> rules = createRulesInFolder(ruleFolder, dataUser.getAdminUser());
STEP("Get the rule set with the ordered list of rules");
RestRuleSetModel ruleSet = restClient.authenticateUser(user).withCoreAPI().usingNode(ruleFolder)
.include("ruleIds").getDefaultRuleSet();
restClient.assertStatusCodeIs(OK);
List<String> ruleIds = rules.stream().map(RestRuleModel::getId).collect(toList());
ruleSet.assertThat().field("ruleIds").is(ruleIds);
}
/** Check we can reorder the rules in a rule set. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
public void reorderRules()
{
STEP("Create a folder containing three rules in the existing site");
FolderModel folder = dataContent.usingUser(user).usingSite(site).createFolder();
List<RestRuleModel> rules = createRulesInFolder(folder, user);
STEP("Reverse the order of the rules within the rule set");
List<String> reversedRuleIds = Lists.reverse(rules.stream().map(RestRuleModel::getId).collect(toList()));
RestRuleSetModel ruleSetBody = new RestRuleSetModel();
ruleSetBody.setId("-default-");
ruleSetBody.setRuleIds(reversedRuleIds);
RestRuleSetModel ruleSet = restClient.authenticateUser(user).withCoreAPI().usingNode(folder)
.include("ruleIds").updateRuleSet(ruleSetBody);
restClient.assertStatusCodeIs(OK);
ruleSet.assertThat().field("ruleIds").is(reversedRuleIds);
}
/** Check we can reorder the rules in a rule set by editing the response from the GET. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
public void reorderRulesUsingResponseFromGET()
{
STEP("Create a folder containing three rules in the existing site");
FolderModel folder = dataContent.usingUser(user).usingSite(site).createFolder();
List<RestRuleModel> rules = createRulesInFolder(folder, user);
STEP("Get the rule set with its id.");
RestRuleSetModel ruleSetResponse = restClient.authenticateUser(user).withCoreAPI().usingNode(folder)
.include("ruleIds").getDefaultRuleSet();
STEP("Reverse the order of the rules within the rule set");
ruleSetResponse.setRuleIds(Lists.reverse(ruleSetResponse.getRuleIds()));
RestRuleSetModel ruleSet = restClient.authenticateUser(user).withCoreAPI().usingNode(folder)
.include("ruleIds").updateRuleSet(ruleSetResponse);
restClient.assertStatusCodeIs(OK);
List<String> reversedRuleIds = Lists.reverse(rules.stream().map(RestRuleModel::getId).collect(toList()));
ruleSet.assertThat().field("ruleIds").is(reversedRuleIds);
}
/** Check that a user cannot reorder the rules in a rule set if they only have read permission. */
@Test
public void reorderRulesWithoutPermission()
{
STEP("Create a site owned by admin and add user as a consumer");
SiteModel siteModel = dataSite.usingAdmin().createPrivateRandomSite();
dataUser.addUserToSite(user, siteModel, UserRole.SiteContributor);
STEP("Use admin to create a folder with a rule set and three rules in it");
FolderModel ruleFolder = dataContent.usingAdmin().usingSite(siteModel).createFolder();
dataContent.usingAdmin().usingResource(ruleFolder).createFolder();
List<RestRuleModel> rules = createRulesInFolder(ruleFolder, dataUser.getAdminUser());
STEP("Try to reorder the rules as the contributor");
List<String> reversedRuleIds = Lists.reverse(rules.stream().map(RestRuleModel::getId).collect(toList()));
RestRuleSetModel ruleSetBody = new RestRuleSetModel();
ruleSetBody.setId("-default-");
ruleSetBody.setRuleIds(reversedRuleIds);
restClient.authenticateUser(user).withCoreAPI().usingNode(ruleFolder)
.include("ruleIds").updateRuleSet(ruleSetBody);
restClient.assertStatusCodeIs(FORBIDDEN);
}
/** Check that a user can reorder the rules in a rule set if they have write permission. */
@Test
public void reorderRulesWithPermission()
{
STEP("Create a site owned by admin and add user as a collaborator");
SiteModel siteModel = dataSite.usingAdmin().createPrivateRandomSite();
dataUser.addUserToSite(user, siteModel, UserRole.SiteCollaborator);
STEP("Use admin to create a folder with a rule set and three rules in it");
FolderModel ruleFolder = dataContent.usingAdmin().usingSite(siteModel).createFolder();
dataContent.usingAdmin().usingResource(ruleFolder).createFolder();
List<RestRuleModel> rules = createRulesInFolder(ruleFolder, dataUser.getAdminUser());
STEP("Try to reorder the rules as the contributor");
List<String> reversedRuleIds = Lists.reverse(rules.stream().map(RestRuleModel::getId).collect(toList()));
RestRuleSetModel ruleSetBody = new RestRuleSetModel();
ruleSetBody.setId("-default-");
ruleSetBody.setRuleIds(reversedRuleIds);
RestRuleSetModel ruleSet = restClient.authenticateUser(user).withCoreAPI().usingNode(ruleFolder)
.include("ruleIds").updateRuleSet(ruleSetBody);
restClient.assertStatusCodeIs(OK);
ruleSet.assertThat().field("ruleIds").is(reversedRuleIds);
}
/** Create three rules in the given folder. */
private List<RestRuleModel> createRulesInFolder(FolderModel folder, UserModel user)
{
return IntStream.range(0, 3).mapToObj(index ->
{
RestRuleModel ruleModel = createRuleModelWithDefaultValues();
return restClient.authenticateUser(user).withCoreAPI().usingNode(folder).usingDefaultRuleSet().createSingleRule(ruleModel);
}).collect(toList());
}
}

View File

@@ -26,10 +26,15 @@
package org.alfresco.rest.rules;
import static org.alfresco.rest.rules.RulesTestsUtils.createRuleModel;
import static org.alfresco.rest.rules.RulesTestsUtils.createRuleModelWithDefaultValues;
import static org.alfresco.utility.constants.UserRole.SiteConsumer;
import static org.alfresco.utility.report.log.Step.STEP;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
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.NO_CONTENT;
import static org.springframework.http.HttpStatus.OK;
import org.alfresco.dataprep.CMISUtil;
import org.alfresco.rest.RestTest;
@@ -169,7 +174,6 @@ public class RuleSetLinksTests extends RestTest
.get(0).onModel().assertThat().isEqualTo(expectedRuleSet);
}
/**
* Check we get 404 when linking to a non-existing rule set/folder.
*/
@@ -310,4 +314,191 @@ public class RuleSetLinksTests extends RestTest
.get(0).onModel().assertThat().isEqualTo(expectedRuleSet);
}
/**
* Check we get an error when trying to link to a rule set that we can't view.
*/
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
public void linkToRuleSetWithoutPermission()
{
STEP("Use admin to create a private site with a folder containing a rule.");
SiteModel privateSite = dataSite.usingAdmin().createPrivateRandomSite();
FolderModel privateFolder = dataContent.usingAdmin().usingSite(privateSite).createFolder();
restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI().usingNode(privateFolder).usingDefaultRuleSet()
.createSingleRule(createRuleModelWithDefaultValues());
STEP("Use a normal user to try to link to the rule.");
FolderModel publicFolder = dataContent.usingUser(user).usingSite(site).createFolder();
RestRuleSetLinkModel request = new RestRuleSetLinkModel();
request.setId(privateFolder.getNodeRef());
restClient.authenticateUser(user).withCoreAPI().usingNode(publicFolder).createRuleLink(request);
restClient.assertStatusCodeIs(FORBIDDEN);
}
/**
* Check we are able to link to a rule set with only read permission.
*/
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
public void linkToRuleSetWithOnlyReadPermission()
{
STEP("Use admin to create a private site with a folder containing a rule.");
SiteModel privateSite = dataSite.usingAdmin().createPrivateRandomSite();
FolderModel privateFolder = dataContent.usingAdmin().usingSite(privateSite).createFolder();
restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI().usingNode(privateFolder).usingDefaultRuleSet()
.createSingleRule(createRuleModelWithDefaultValues());
STEP("Add the normal user as a consumer.");
dataUser.usingAdmin().addUserToSite(user, privateSite, SiteConsumer);
STEP("Use a normal user to try to link to the rule.");
FolderModel publicFolder = dataContent.usingUser(user).usingSite(site).createFolder();
RestRuleSetLinkModel request = new RestRuleSetLinkModel();
request.setId(privateFolder.getNodeRef());
restClient.authenticateUser(user).withCoreAPI().usingNode(publicFolder).createRuleLink(request);
restClient.assertStatusCodeIs(CREATED);
}
/**
* Check we can DELETE/unlink a ruleset
*
* DELETE /nodes/{folderNodeId}/rule-set-links/{ruleSetId}.
*/
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void unlinkRuleSet()
{
STEP("Create folders in existing site");
final FolderModel ruleFolder = dataContent.usingUser(user).usingSite(site).createFolder();
final FolderModel folder = dataContent.usingUser(user).usingSite(site).createFolder();
STEP("Create a rule in the rule folder.");
RestRuleModel ruleModel = createRuleModel("ruleName");
RestRuleModel rule = restClient.authenticateUser(user).withCoreAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.createSingleRule(ruleModel);
STEP("Get the rule sets for the folder and find the rule set id");
final RestRuleSetModelsCollection ruleSets = restClient.authenticateUser(user).withCoreAPI().usingNode(ruleFolder)
.getListOfRuleSets();
ruleSets.assertThat().entriesListCountIs(1);
final String ruleSetId = ruleSets.getEntries().get(0).onModel().getId();
STEP("Link to a rule folder");
final RestRuleSetLinkModel request = new RestRuleSetLinkModel();
request.setId(ruleFolder.getNodeRef());
final RestRuleSetLinkModel ruleLink = restClient.authenticateUser(user).withCoreAPI().usingNode(folder).createRuleLink(request);
STEP("Unlink the rule set");
restClient.authenticateUser(user).withCoreAPI().usingNode(folder).unlinkRuleSet(ruleSetId);
STEP("Assert unlink result");
restClient.assertStatusCodeIs(NO_CONTENT);
STEP("GET the rule set and isLinkedTo field.");
RestRuleSetModel ruleSet = restClient.authenticateUser(user).withCoreAPI().usingNode(ruleFolder)
.include("isLinkedTo", "linkedToBy", "owningFolder")
.getDefaultRuleSet();
STEP("Assert linkedTo is false.");
restClient.assertStatusCodeIs(OK);
ruleSet.assertThat().field("isLinkedTo").is(false)
.assertThat().field("linkedToBy").isEmpty();;
}
/**
* Check a 400 is thrown when using folder/content id instead of a ruleSetId.
*
* DELETE /nodes/{folderNodeId}/rule-set-links/{ruleSetId}
*/
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void unlinkUsingDocumentId()
{
STEP("Create folders in existing site");
final FolderModel folder = dataContent.usingUser(user).usingSite(site).createFolder();
STEP("Attempt to unlink the rule set");
restClient.authenticateUser(user).withCoreAPI().usingNode(folder).unlinkRuleSet(folder.getNodeRef());
STEP("Assert unlink result");
restClient.assertStatusCodeIs(BAD_REQUEST)
.assertLastError().containsSummary("NodeId of a rule set is expected!");
}
/**
* Check a 404 is thrown when using non-existent id instead of a ruleSetId.
*
* DELETE /nodes/{folderNodeId}/rule-set-links/{ruleSetId}
*/
//TODO This test may need to be modified once ACS-3616 is done
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void unlinkUsingRandomId()
{
STEP("Create folders in existing site");
final FolderModel folder = dataContent.usingUser(user).usingSite(site).createFolder();
STEP("Attempt to unlink the rule set");
restClient.authenticateUser(user).withCoreAPI().usingNode(folder).unlinkRuleSet("non-existent-id");
STEP("Assert unlink result");
restClient.assertStatusCodeIs(NOT_FOUND)
.assertLastError().containsSummary("The entity with id:");
}
/**
* Check we cannot unlink from a rule set that we can't view.
*/
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
public void unlinkFromRuleSetWithoutPermission()
{
STEP("Use admin to create a private site with a folder containing a rule.");
SiteModel privateSite = dataSite.usingAdmin().createPrivateRandomSite();
FolderModel privateFolder = dataContent.usingAdmin().usingSite(privateSite).createFolder();
restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI().usingNode(privateFolder).usingDefaultRuleSet()
.createSingleRule(createRuleModelWithDefaultValues());
STEP("Add the user as a consumer.");
dataUser.usingAdmin().addUserToSite(user, privateSite, SiteConsumer);
STEP("Use the consumer to create a folder with a link to the private rule set.");
FolderModel publicFolder = dataContent.usingUser(user).usingSite(site).createFolder();
RestRuleSetLinkModel request = new RestRuleSetLinkModel();
request.setId(privateFolder.getNodeRef());
restClient.authenticateUser(user).withCoreAPI().usingNode(publicFolder).createRuleLink(request);
restClient.assertStatusCodeIs(CREATED);
STEP("Remove the user from the private site.");
dataUser.usingAdmin().removeUserFromSite(user, privateSite);
STEP("Use the user to try to unlink from the rule set.");
restClient.authenticateUser(user).withCoreAPI().usingNode(publicFolder).unlinkRuleSet("-default-");
restClient.assertStatusCodeIs(FORBIDDEN);
}
/**
* Check we can unlink from a rule set if we only have read permission for it.
*/
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
public void unlinkFromRuleSetWithOnlyReadPermission()
{
STEP("Use admin to create a private site with a folder containing a rule.");
SiteModel privateSite = dataSite.usingAdmin().createPrivateRandomSite();
FolderModel privateFolder = dataContent.usingAdmin().usingSite(privateSite).createFolder();
restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI().usingNode(privateFolder).usingDefaultRuleSet()
.createSingleRule(createRuleModelWithDefaultValues());
STEP("Add the user as a consumer.");
dataUser.usingAdmin().addUserToSite(user, privateSite, SiteConsumer);
STEP("Use the consumer to create a folder with a link to the private rule set.");
FolderModel publicFolder = dataContent.usingUser(user).usingSite(site).createFolder();
RestRuleSetLinkModel request = new RestRuleSetLinkModel();
request.setId(privateFolder.getNodeRef());
restClient.authenticateUser(user).withCoreAPI().usingNode(publicFolder).createRuleLink(request);
restClient.assertStatusCodeIs(CREATED);
STEP("Use the consumer to try to unlink from the rule set.");
restClient.authenticateUser(user).withCoreAPI().usingNode(publicFolder).unlinkRuleSet("-default-");
restClient.assertStatusCodeIs(NO_CONTENT);
}
}

View File

@@ -25,12 +25,19 @@
*/
package org.alfresco.rest.rules;
import static org.alfresco.rest.actions.access.AccessRestrictionUtil.MAIL_ACTION;
import static org.alfresco.rest.actions.access.AccessRestrictionUtil.createMailParameters;
import static org.alfresco.utility.model.UserModel.getRandomUserModel;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.alfresco.rest.model.RestActionBodyExecTemplateModel;
import org.alfresco.rest.model.RestCompositeConditionDefinitionModel;
import org.alfresco.rest.model.RestNodeModel;
import org.alfresco.rest.model.RestRuleExecutionModel;
import org.alfresco.rest.model.RestRuleModel;
import org.alfresco.rest.model.RestSimpleConditionDefinitionModel;
@@ -51,19 +58,27 @@ public class RulesTestsUtils
static final String AND = "and";
static final String ID = "id";
static final String IS_SHARED = "isShared";
static final String AUDIO_ASPECT = "audio:audio";
static final String LOCKABLE_ASPECT = "cm:lockable";
/**
* Create a rule model filled with default values.
*
* @return The created rule model.
*/
public static RestRuleModel createRuleModelWithModifiedValues()
{
RestRuleModel ruleModel = createRuleModelWithDefaultValues();
return createRuleModelWithModifiedValues(List.of(createAddAudioAspectAction()));
}
/**
* Create a rule model filled with custom constant values.
*
* @param actions - rule's actions.
* @return The created rule model.
*/
public static RestRuleModel createRuleModelWithModifiedValues(List<RestActionBodyExecTemplateModel> actions)
{
RestRuleModel ruleModel = createRuleModel(RULE_NAME_DEFAULT, actions);
ruleModel.setDescription(RULE_DESCRIPTION_DEFAULT);
ruleModel.setEnabled(RULE_ENABLED_DEFAULT);
ruleModel.setCascade(RULE_CASCADE_DEFAULT);
ruleModel.setAsynchronous(RULE_ASYNC_DEFAULT);
ruleModel.setIsEnabled(RULE_ENABLED_DEFAULT);
ruleModel.setIsInheritable(RULE_CASCADE_DEFAULT);
ruleModel.setIsAsynchronous(RULE_ASYNC_DEFAULT);
ruleModel.setIsShared(RULE_SHARED_DEFAULT);
ruleModel.setTriggers(RULE_TRIGGERS_DEFAULT);
ruleModel.setErrorScript(RULE_ERROR_SCRIPT_DEFAULT);
@@ -73,26 +88,27 @@ public class RulesTestsUtils
public static RestRuleModel createRuleModelWithDefaultValues()
{
return createRuleModel(RULE_NAME_DEFAULT, List.of(createDefaultActionModel()));
return createRuleModel(RULE_NAME_DEFAULT);
}
public static RestRuleModel createRuleModel(String name)
{
return createRuleModel(name, List.of(createDefaultActionModel()));
return createRuleModel(name, List.of(createAddAudioAspectAction()));
}
/**
* Create a rule model.
*
* @param name The name for the rule.
* @param restActionModels Rule's actions.
* @param actions Rule's actions.
* @return The created rule model.
*/
public static RestRuleModel createRuleModel(String name, List<RestActionBodyExecTemplateModel> restActionModels)
public static RestRuleModel createRuleModel(String name, List<RestActionBodyExecTemplateModel> actions)
{
RestRuleModel ruleModel = new RestRuleModel();
ruleModel.setIsEnabled(true);
ruleModel.setName(name);
ruleModel.setActions(restActionModels);
ruleModel.setActions(actions);
return ruleModel;
}
@@ -101,12 +117,14 @@ public class RulesTestsUtils
*
* @return The created action model.
*/
public static RestActionBodyExecTemplateModel createDefaultActionModel()
public static RestActionBodyExecTemplateModel createAddAudioAspectAction()
{
RestActionBodyExecTemplateModel restActionModel = new RestActionBodyExecTemplateModel();
restActionModel.setActionDefinitionId("set-property-value");
restActionModel.setParams(Map.of("aspect-name", "cm:audio"));
return restActionModel;
return createAddAspectAction(AUDIO_ASPECT);
}
public static RestActionBodyExecTemplateModel createAddAspectAction(String aspect)
{
return createCustomActionModel("add-features", Map.of("aspect-name", aspect));
}
public static RestActionBodyExecTemplateModel createCustomActionModel(String actionDefinitionId, Map<String, Serializable> params)
@@ -138,12 +156,41 @@ public class RulesTestsUtils
createSimpleCondition("tag", "equals", "uat")
)),
createCompositeCondition(INVERTED, List.of(
createSimpleCondition("aspect", "equals", "audio:audio"),
createSimpleCondition("aspect", "equals", AUDIO_ASPECT),
createSimpleCondition("cm:modelVersion", "begins", "1.")
))
));
}
public static RestRuleModel createVariousActions()
{
final Map<String, Serializable> copyParams =
Map.of("destination-folder", "dummy-folder-node", "deep-copy", true);
final RestActionBodyExecTemplateModel copyAction = createCustomActionModel("copy", copyParams);
final Map<String, Serializable> checkOutParams =
Map.of("destination-folder", "fake-folder-node", "assoc-name", "cm:checkout", "assoc-type",
"cm:contains");
final RestActionBodyExecTemplateModel checkOutAction = createCustomActionModel("check-out", checkOutParams);
final Map<String, Serializable> scriptParams = Map.of("script-ref", "dummy-script-node-id");
final RestActionBodyExecTemplateModel scriptAction = createCustomActionModel("script", scriptParams);
// The counter action takes no parameters, so check we can omit the "params" entry.
final RestActionBodyExecTemplateModel counterAction = createCustomActionModel("counter", null);
final RestRuleModel ruleModel = createRuleModelWithDefaultValues();
ruleModel.setActions(Arrays.asList(copyAction, checkOutAction, scriptAction, counterAction));
return ruleModel;
}
public static RestRuleModel createRuleWithPrivateAction()
{
RestActionBodyExecTemplateModel mailAction = new RestActionBodyExecTemplateModel();
mailAction.setActionDefinitionId(MAIL_ACTION);
mailAction.setParams(createMailParameters(getRandomUserModel(), getRandomUserModel()));
RestRuleModel ruleModel = createRuleModelWithDefaultValues();
ruleModel.setActions(Arrays.asList(mailAction));
return ruleModel;
}
public static RestSimpleConditionDefinitionModel createSimpleCondition(String field, String comparator, String parameter)
{
RestSimpleConditionDefinitionModel simpleCondition = new RestSimpleConditionDefinitionModel();
@@ -164,6 +211,19 @@ public class RulesTestsUtils
return createCompositeCondition(AND, inverted, null, simpleConditions);
}
public static RestRuleExecutionModel createRuleExecutionRequest()
{
return createRuleExecutionRequest(false);
}
public static RestRuleExecutionModel createRuleExecutionRequest(boolean eachSubFolderIncluded)
{
RestRuleExecutionModel ruleExecutionBody = new RestRuleExecutionModel();
ruleExecutionBody.setIsEachSubFolderIncluded(eachSubFolderIncluded);
return ruleExecutionBody;
}
private static RestCompositeConditionDefinitionModel createCompositeCondition(String booleanMode, boolean inverted,
List<RestCompositeConditionDefinitionModel> compositeConditions, List<RestSimpleConditionDefinitionModel> simpleConditions)
{
@@ -175,4 +235,31 @@ public class RulesTestsUtils
return compositeCondition;
}
public static NodeAssertion assertThat(RestNodeModel node)
{
return new NodeAssertion(node);
}
public static class NodeAssertion
{
private final RestNodeModel node;
private NodeAssertion(RestNodeModel node)
{
this.node = node;
}
public NodeAssertion containsAspects(String ...expectedAspects)
{
Arrays.stream(expectedAspects).forEach(aspect -> node.assertThat().field("aspectNames").contains(aspect));
return this;
}
public NodeAssertion notContainsAspects(String ...unexpectedAspects)
{
Arrays.stream(unexpectedAspects).forEach(aspect -> node.assertThat().field("aspectNames").notContains(aspect));
return this;
}
}
}

View File

@@ -25,28 +25,40 @@
*/
package org.alfresco.rest.rules;
import static org.alfresco.rest.actions.access.AccessRestrictionUtil.ERROR_MESSAGE_ACCESS_RESTRICTED;
import static org.alfresco.rest.rules.RulesTestsUtils.ID;
import static org.alfresco.rest.rules.RulesTestsUtils.INBOUND;
import static org.alfresco.rest.rules.RulesTestsUtils.INVERTED;
import static org.alfresco.rest.rules.RulesTestsUtils.IS_SHARED;
import static org.alfresco.rest.rules.RulesTestsUtils.RULE_ASYNC_DEFAULT;
import static org.alfresco.rest.rules.RulesTestsUtils.RULE_CASCADE_DEFAULT;
import static org.alfresco.rest.rules.RulesTestsUtils.RULE_ENABLED_DEFAULT;
import static org.alfresco.rest.rules.RulesTestsUtils.createDefaultActionModel;
import static org.alfresco.rest.rules.RulesTestsUtils.createCompositeCondition;
import static org.alfresco.rest.rules.RulesTestsUtils.createCustomActionModel;
import static org.alfresco.rest.rules.RulesTestsUtils.createAddAudioAspectAction;
import static org.alfresco.rest.rules.RulesTestsUtils.createRuleModel;
import static org.alfresco.rest.rules.RulesTestsUtils.createRuleModelWithModifiedValues;
import static org.alfresco.rest.rules.RulesTestsUtils.createRuleWithPrivateAction;
import static org.alfresco.rest.rules.RulesTestsUtils.createSimpleCondition;
import static org.alfresco.rest.rules.RulesTestsUtils.createVariousConditions;
import static org.alfresco.utility.constants.UserRole.SiteCollaborator;
import static org.alfresco.utility.report.log.Step.STEP;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.FORBIDDEN;
import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
import static org.springframework.http.HttpStatus.NOT_FOUND;
import static org.springframework.http.HttpStatus.OK;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import com.google.common.collect.ImmutableMap;
import org.alfresco.rest.RestTest;
import org.alfresco.rest.model.RestActionBodyExecTemplateModel;
import org.alfresco.rest.model.RestCompositeConditionDefinitionModel;
import org.alfresco.rest.model.RestRuleModel;
import org.alfresco.utility.model.FolderModel;
import org.alfresco.utility.model.SiteModel;
@@ -229,7 +241,7 @@ public class UpdateRulesTests extends RestTest
* Check we get error when attempt to update a rule to one with invalid action.
*/
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void updateRuleWithInvalidActionsShouldFail()
public void updateRuleWithInvalidActionDefinitionShouldFail()
{
RestRuleModel rule = createAndSaveRule("Rule name");
@@ -281,23 +293,279 @@ public class UpdateRulesTests extends RestTest
rule.setTriggers(List.of(INBOUND));
final String updatedDescription = "Updated description";
rule.setDescription(updatedDescription);
rule.setEnabled(!RULE_ENABLED_DEFAULT);
rule.setCascade(!RULE_CASCADE_DEFAULT);
rule.setAsynchronous(!RULE_ASYNC_DEFAULT);
rule.setIsEnabled(!RULE_ENABLED_DEFAULT);
rule.setIsInheritable(!RULE_CASCADE_DEFAULT);
rule.setIsAsynchronous(!RULE_ASYNC_DEFAULT);
final String updatedErrorScript = "updated-error-script";
rule.setErrorScript(updatedErrorScript);
final RestRuleModel updatedRule = restClient.authenticateUser(user).withCoreAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.include(IS_SHARED)
.updateRule(rule.getId(), rule);
restClient.assertStatusCodeIs(OK);
updatedRule.assertThat().isEqualTo(rule, ID, IS_SHARED)
updatedRule.assertThat().isEqualTo(rule, ID)
.assertThat().field(ID).isNotNull();
}
/** Check we can use the POST response and update rule by adding conditions. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES, TestGroup.SANITY })
public void updateRuleAddConditions()
{
final RestRuleModel rule = createAndSaveRule(createRuleModelWithModifiedValues());
STEP("Try to update the rule and add conditions.");
rule.setConditions(createVariousConditions());
final RestRuleModel updatedRule = restClient.authenticateUser(user).withCoreAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.updateRule(rule.getId(), rule);
restClient.assertStatusCodeIs(OK);
updatedRule.assertThat().isEqualTo(rule, ID)
.assertThat().field(ID).isNotNull();
}
/** Check we can use the POST response and update a rule rule without any conditions by adding null conditions. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES, TestGroup.SANITY })
public void updateRuleAddNullConditions()
{
final RestRuleModel rule = createAndSaveRule(createRuleModelWithModifiedValues());
STEP("Try to update the rule and add null conditions.");
rule.setConditions(null);
final RestRuleModel updatedRule = restClient.authenticateUser(user).withCoreAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.updateRule(rule.getId(), rule);
restClient.assertStatusCodeIs(OK);
updatedRule.assertThat().isEqualTo(rule, ID)
.assertThat().field(ID).isNotNull();
}
/** Check we can use the POST response and update rule by modifying conditions. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES, TestGroup.SANITY })
public void updateRuleModifyConditions()
{
final RestRuleModel ruleModelWithInitialValues = createRuleModelWithModifiedValues();
ruleModelWithInitialValues.setConditions(createVariousConditions());
final RestRuleModel rule = createAndSaveRule(ruleModelWithInitialValues);
STEP("Try to update the rule and modify conditions.");
final RestCompositeConditionDefinitionModel compositeCondition = createCompositeCondition(
List.of(createCompositeCondition(false, List.of(createSimpleCondition("tag", "equals", "sample_tag")))));
rule.setConditions(compositeCondition);
final RestRuleModel updatedRule = restClient.authenticateUser(user).withCoreAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.updateRule(rule.getId(), rule);
restClient.assertStatusCodeIs(OK);
updatedRule.assertThat().isEqualTo(rule, ID)
.assertThat().field(ID).isNotNull();
}
/** Check we can use the POST response and update rule by removing all conditions. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES, TestGroup.SANITY })
public void updateRuleRemoveAllConditions()
{
final RestRuleModel ruleModelWithInitialValues = createRuleModelWithModifiedValues();
ruleModelWithInitialValues.setConditions(createVariousConditions());
final RestRuleModel rule = createAndSaveRule(ruleModelWithInitialValues);
STEP("Try to update the rule and remove all conditions.");
rule.setConditions(null);
final RestRuleModel updatedRule = restClient.authenticateUser(user).withCoreAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.updateRule(rule.getId(), rule);
restClient.assertStatusCodeIs(OK);
updatedRule.assertThat().isEqualTo(rule, ID)
.assertThat().field(ID).isNotNull();
}
/** Check we get a 400 error when using the POST response and update rule by adding condition with invalid category. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES, TestGroup.SANITY })
public void updateRuleWithInvalidCategoryInConditionAndFail()
{
final RestRuleModel ruleModelWithInitialValues = createRuleModelWithModifiedValues();
ruleModelWithInitialValues.setConditions(createVariousConditions());
final RestRuleModel rule = createAndSaveRule(ruleModelWithInitialValues);
STEP("Try to update the rule with invalid condition.");
final RestCompositeConditionDefinitionModel conditions = createCompositeCondition(
List.of(createCompositeCondition(!INVERTED, List.of(createSimpleCondition("category", "equals", "fake-category-id")))));
rule.setConditions(conditions);
restClient.authenticateUser(user).withCoreAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.updateRule(rule.getId(), rule);
restClient.assertStatusCodeIs(BAD_REQUEST);
restClient.assertLastError().containsSummary("Category in condition is invalid");
}
/** Check we get a 400 error when using the POST response and update rule by adding condition without comparator when it is required. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES, TestGroup.SANITY })
public void updateRuleWithConditionWithoutComparatorAndFail()
{
final RestRuleModel ruleModelWithInitialValues = createRuleModelWithModifiedValues();
ruleModelWithInitialValues.setConditions(createVariousConditions());
final RestRuleModel rule = createAndSaveRule(ruleModelWithInitialValues);
STEP("Try to update the rule with invalid condition (null comparator when required non-null).");
final RestCompositeConditionDefinitionModel conditions = createCompositeCondition(
List.of(createCompositeCondition(!INVERTED, List.of(createSimpleCondition("size", null, "65500")))));
rule.setConditions(conditions);
restClient.authenticateUser(user).withCoreAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.updateRule(rule.getId(), rule);
restClient.assertStatusCodeIs(BAD_REQUEST);
restClient.assertLastError().containsSummary("Comparator in condition must not be blank");
}
/** Check we get a 400 error when using the POST response and update rule by adding condition without field. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES, TestGroup.SANITY })
public void updateRuleWithConditionWithoutFieldAndFail()
{
final RestRuleModel ruleModelWithInitialValues = createRuleModelWithModifiedValues();
ruleModelWithInitialValues.setConditions(createVariousConditions());
final RestRuleModel rule = createAndSaveRule(ruleModelWithInitialValues);
STEP("Try to update the rule with invalid condition (null field).");
final RestCompositeConditionDefinitionModel conditions = createCompositeCondition(
List.of(createCompositeCondition(!INVERTED, List.of(createSimpleCondition(null, "greater_than", "65500")))));
rule.setConditions(conditions);
restClient.authenticateUser(user).withCoreAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.updateRule(rule.getId(), rule);
restClient.assertStatusCodeIs(BAD_REQUEST);
restClient.assertLastError().containsSummary("Field in condition must not be blank");
}
/** Check we get a 400 error when using the POST response and update rule by adding condition without parameter value. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES, TestGroup.SANITY })
public void updateRuleWithConditionWithoutParamValueAndFail()
{
final RestRuleModel ruleModelWithInitialValues = createRuleModelWithModifiedValues();
ruleModelWithInitialValues.setConditions(createVariousConditions());
final RestRuleModel rule = createAndSaveRule(ruleModelWithInitialValues);
STEP("Try to update the rule with invalid condition (null parameter).");
final RestCompositeConditionDefinitionModel conditions = createCompositeCondition(
List.of(createCompositeCondition(!INVERTED, List.of(createSimpleCondition("size", "greater_than", "")))));
rule.setConditions(conditions);
restClient.authenticateUser(user).withCoreAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.updateRule(rule.getId(), rule);
restClient.assertStatusCodeIs(BAD_REQUEST);
restClient.assertLastError().containsSummary("Parameter in condition must not be blank");
}
/**
* Check we can update a rule by adding several actions.
*/
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void updateRuleAddActions()
{
final RestRuleModel rule = createAndSaveRule(createRuleModelWithModifiedValues());
STEP("Try to update the rule by adding several actions");
final Map<String, Serializable> copyParams =
Map.of("destination-folder", "dummy-folder-node", "deep-copy", true);
final RestActionBodyExecTemplateModel copyAction = createCustomActionModel("copy", copyParams);
final Map<String, Serializable> scriptParams = Map.of("script-ref", "dummy-script-node-id");
final RestActionBodyExecTemplateModel scriptAction = createCustomActionModel("script", scriptParams);
rule.setActions(Arrays.asList(copyAction, scriptAction));
final RestRuleModel updatedRule = restClient.authenticateUser(user).withCoreAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.updateRule(rule.getId(), rule);
restClient.assertStatusCodeIs(OK);
updatedRule.assertThat().isEqualTo(rule, ID)
.assertThat().field(ID).isNotNull();
}
/**
* Check we get a 400 error when attempting to update a rule by adding action with not allowed parameter.
*/
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void updateRuleAddCheckoutActionForOutboundShouldFail()
{
final RestRuleModel rule = createAndSaveRule(createRuleModelWithModifiedValues());
STEP("Try to update the rule by adding checkout action");
final Map<String, Serializable> checkOutParams =
Map.of("destination-folder", "dummy-folder-node", "assoc-name", "cm:checkout", "assoc-type",
"cm:contains");
final RestActionBodyExecTemplateModel checkOutAction = createCustomActionModel("check-out", checkOutParams);
final Map<String, Serializable> scriptParams = Map.of("script-ref", "dummy-script-node-id");
rule.setActions(List.of(checkOutAction));
restClient.authenticateUser(user).withCoreAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.updateRule(rule.getId(), rule);
restClient.assertStatusCodeIs(BAD_REQUEST);
restClient.assertLastError().containsSummary("Check out action cannot be performed for the rule type outbound!");
}
/**
* Check we get a 500 error when attempting to update a rule by adding action with parameter with non existing namespace in value.
* In near future we need to fix this kind of negative path to return a 4xx error.
*/
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void updateRuleAddActionWithInvalidParamShouldFail()
{
final RestRuleModel rule = createAndSaveRule(createRuleModelWithModifiedValues());
STEP("Try to update the rule by adding action with invalid parameter (non-existing namespace in value)");
final RestActionBodyExecTemplateModel action = new RestActionBodyExecTemplateModel();
action.setActionDefinitionId("add-features");
action.setParams(Map.of("aspect-name", "dummy:dummy"));
rule.setActions(List.of(action));
restClient.authenticateUser(user).withCoreAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.updateRule(rule.getId(), rule);
restClient.assertStatusCodeIs(INTERNAL_SERVER_ERROR);
restClient.assertLastError().containsSummary("Namespace prefix dummy is not mapped to a namespace URI");
}
/** Check that a normal user cannot create rules that use private actions. */
@Test
public void updateRuleWithActions_userCannotUsePrivateAction()
{
STEP("Using admin create a rule with a private action.");
RestRuleModel rule = restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.createSingleRule(createRuleWithPrivateAction());
STEP("Try to update the rule with a normal user.");
rule.setName("Updated name");
restClient.authenticateUser(user).withCoreAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.updateRule(rule.getId(), rule);
restClient.assertStatusCodeIs(FORBIDDEN)
.assertLastError().containsSummary(ERROR_MESSAGE_ACCESS_RESTRICTED);
}
/** Check that an administrator can create rules that use private actions. */
@Test
public void updateRuleWithActions_adminCanUsePrivateAction()
{
STEP("Using admin create a rule with a private action.");
RestRuleModel rule = restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.createSingleRule(createRuleWithPrivateAction());
STEP("Try to update the rule with the admin user.");
rule.setName("Updated name");
RestRuleModel updatedRule = restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.updateRule(rule.getId(), rule);
restClient.assertStatusCodeIs(OK);
updatedRule.assertThat().field("name").is("Updated name");
}
private RestRuleModel createAndSaveRule(String name)
{
return createAndSaveRule(name, List.of(createDefaultActionModel()));
return createAndSaveRule(name, List.of(createAddAudioAspectAction()));
}
/**

View File

@@ -35,10 +35,11 @@ public class GetDeploymentsSanityTests extends RestTest
restClient.assertStatusCodeIs(HttpStatus.OK);
deployments.assertThat().entriesListIsNotEmpty();
deployments.getOneRandomEntry().onModel().assertThat()
.fieldsCount().is(3).and()
.fieldsCount().is(4).and()
.field("id").isNotEmpty().and()
.field("deployedAt").isNotEmpty().and()
.field("name").isNotEmpty();
.field("name").isNotEmpty().and()
.field("category").isNotEmpty();
}
}

View File

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

View File

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

20
pom.xml
View File

@@ -2,7 +2,7 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>alfresco-community-repo</artifactId>
<version>17.125</version>
<version>17.155</version>
<packaging>pom</packaging>
<name>Alfresco Community Repo Parent</name>
@@ -51,8 +51,8 @@
<dependency.alfresco-log-sanitizer.version>0.2</dependency.alfresco-log-sanitizer.version>
<dependency.activiti-engine.version>5.23.0</dependency.activiti-engine.version>
<dependency.activiti.version>5.23.0</dependency.activiti.version>
<dependency.alfresco-transform-service.version>2.0.0-A2</dependency.alfresco-transform-service.version>
<dependency.alfresco-transform-core.version>3.0.0-A2</dependency.alfresco-transform-core.version>
<dependency.alfresco-transform-service.version>2.0.0-A3</dependency.alfresco-transform-service.version>
<dependency.alfresco-transform-core.version>3.0.0-A3</dependency.alfresco-transform-core.version>
<dependency.alfresco-greenmail.version>6.4</dependency.alfresco-greenmail.version>
<dependency.acs-event-model.version>0.0.16</dependency.acs-event-model.version>
@@ -110,9 +110,9 @@
<dependency.jakarta-json-path.version>2.7.0</dependency.jakarta-json-path.version>
<dependency.jakarta-rpc-api.version>1.1.4</dependency.jakarta-rpc-api.version>
<alfresco.googledrive.version>3.2.3-A2</alfresco.googledrive.version>
<alfresco.googledrive.version>3.2.3-A3</alfresco.googledrive.version>
<alfresco.aos-module.version>1.4.1</alfresco.aos-module.version>
<alfresco.api-explorer.version>7.2.1</alfresco.api-explorer.version> <!-- Also in alfresco-enterprise-share -->
<alfresco.api-explorer.version>7.3.0-A1</alfresco.api-explorer.version> <!-- Also in alfresco-enterprise-share -->
<alfresco.maven-plugin.version>2.2.0</alfresco.maven-plugin.version>
<license-maven-plugin.version>2.0.1.alfresco-2</license-maven-plugin.version>
@@ -123,8 +123,7 @@
<dependency.mariadb.version>2.7.4</dependency.mariadb.version>
<dependency.tas-utility.version>3.0.56</dependency.tas-utility.version>
<dependency.rest-assured.version>5.2.0</dependency.rest-assured.version>
<dependency.tas-restapi.version>1.122</dependency.tas-restapi.version>
<dependency.tas-cmis.version>1.32</dependency.tas-cmis.version>
<dependency.tas-restapi.version>1.133</dependency.tas-restapi.version>
<dependency.tas-email.version>1.9</dependency.tas-email.version>
<dependency.tas-webdav.version>1.7</dependency.tas-webdav.version>
<dependency.tas-ftp.version>1.7</dependency.tas-ftp.version>
@@ -151,7 +150,7 @@
<connection>scm:git:https://github.com/Alfresco/alfresco-community-repo.git</connection>
<developerConnection>scm:git:https://github.com/Alfresco/alfresco-community-repo.git</developerConnection>
<url>https://github.com/Alfresco/alfresco-community-repo</url>
<tag>17.125</tag>
<tag>17.155</tag>
</scm>
<distributionManagement>
@@ -715,11 +714,6 @@
<artifactId>restapi</artifactId>
<version>${dependency.tas-restapi.version}</version>
</dependency>
<dependency>
<groupId>org.alfresco.tas</groupId>
<artifactId>cmis</artifactId>
<version>${dependency.tas-cmis.version}</version>
</dependency>
<dependency>
<groupId>org.alfresco.tas</groupId>
<artifactId>email</artifactId>

View File

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

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2018 Alfresco Software Limited
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
@@ -29,8 +29,10 @@ package org.alfresco.rest.api;
import org.alfresco.rest.api.model.Action;
import org.alfresco.rest.api.model.ActionDefinition;
import org.alfresco.rest.api.model.ActionParameterConstraint;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.service.Experimental;
import org.alfresco.service.cmr.repository.NodeRef;
public interface Actions
@@ -45,7 +47,10 @@ public interface Actions
{
NAME,
TITLE
};
}
Action executeAction(Action action, Parameters parameters);
@Experimental
ActionParameterConstraint getActionConstraint(String constraintName);
}

View File

@@ -59,8 +59,23 @@ public interface RuleSets
*/
RuleSet getRuleSetById(String folderNodeId, String ruleSetId, List<String> includes);
/**
* Update a rule set - for example to reorder the rules within it.
*
* @param folderNodeId Folder node ID
* @param ruleSet The updated rule set.
* @param includes List of fields to include in the response.
* @return The updated rule set from the server.
*/
RuleSet updateRuleSet(String folderNodeId, RuleSet ruleSet, List<String> includes);
/**
* Link a rule set to a folder
*/
RuleSetLink linkToRuleSet(String folderNodeId, String linkToNodeId);
/**
* Removes the link between a rule set and a folder
*/
void unlinkRuleSet(String folderNodeId, String ruleSetId);
}

View File

@@ -29,6 +29,7 @@ package org.alfresco.rest.api;
import java.util.List;
import org.alfresco.rest.api.model.rules.Rule;
import org.alfresco.rest.api.model.rules.RuleExecution;
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Paging;
@@ -65,10 +66,10 @@ public interface Rules
Rule getRuleById(String folderNodeId, String ruleSetId, String ruleId, List<String> includes);
/**
* Create new rules (and potentially a rule set if "_default_" is supplied).
* Create new rules (and potentially a rule set if "-default-" is supplied).
*
* @param folderNodeId The node id of a folder.
* @param ruleSetId The id of a rule set (or "_default_" to use/create the default rule set for the folder).
* @param ruleSetId The id of a rule set (or "-default-" to use/create the default rule set for the folder).
* @param rule The definition of the rule.
* @param includes The list of optional fields to include in the response.
* @return The newly created rules.
@@ -81,7 +82,7 @@ public interface Rules
* Update a rule.
*
* @param folderNodeId The id of a folder.
* @param ruleSetId The id of a rule set within the folder (or "_default_" to use the default rule set for the folder).
* @param ruleSetId The id of a rule set within the folder (or "-default-" to use the default rule set for the folder).
* @param ruleId The rule id.
* @param rule The new version of the rule.
* @param includes The list of optional fields to include in the response.
@@ -94,7 +95,15 @@ public interface Rules
*
* @param folderNodeId - folder node ID
* @param ruleSetId - rule set ID
* @param ruleId - rule ID *
* @param ruleId - rule ID
*/
void deleteRuleById(String folderNodeId, String ruleSetId, String ruleId);
/**
* Execute rules for given folder node.
*
* @param folderNodeId - the ID of a folder
* @param eachSubFolderIncluded - indicates if rules should be executed also on sub-folders
*/
RuleExecution executeRules(final String folderNodeId, final boolean eachSubFolderIncluded);
}

View File

@@ -0,0 +1,59 @@
/*
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.rest.api.actions;
import javax.servlet.http.HttpServletResponse;
import org.alfresco.rest.api.Actions;
import org.alfresco.rest.api.model.ActionParameterConstraint;
import org.alfresco.rest.framework.WebApiDescription;
import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
import org.alfresco.rest.framework.resource.EntityResource;
import org.alfresco.rest.framework.resource.actions.interfaces.EntityResourceAction;
import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.service.Experimental;
@EntityResource(name="action-parameter-constraints", title = "Action parameter constraints")
@Experimental
public class ActionParameterConstraintsEntityResource implements EntityResourceAction.ReadById<ActionParameterConstraint>
{
private final Actions actions;
public ActionParameterConstraintsEntityResource(Actions actions)
{
this.actions = actions;
}
@WebApiDescription(title = "Get single action parameter constraint",
description = "Retrieves a single action parameter constraint by constraint name",
successStatus = HttpServletResponse.SC_OK)
@Experimental
@Override
public ActionParameterConstraint readById(String constraintName, Parameters parameters) throws EntityNotFoundException
{
return actions.getActionConstraint(constraintName);
}
}

View File

@@ -27,15 +27,22 @@ package org.alfresco.rest.api.impl;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.action.access.ActionAccessRestriction;
import org.alfresco.repo.action.constraint.FolderContentsParameterConstraint;
import org.alfresco.rest.api.Actions;
import org.alfresco.rest.api.impl.rules.ActionParameterConverter;
import org.alfresco.rest.api.model.Action;
import org.alfresco.rest.api.model.ActionDefinition;
import org.alfresco.rest.api.model.ActionParameterConstraint;
import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
import org.alfresco.rest.framework.core.exceptions.NotFoundException;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.ListPage;
import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.rest.framework.resource.parameters.SortColumn;
import org.alfresco.service.Experimental;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.action.ParameterConstraint;
import org.alfresco.service.cmr.action.ParameterDefinition;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryException;
@@ -59,6 +66,7 @@ import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@@ -68,11 +76,14 @@ import static java.util.Comparator.nullsFirst;
public class ActionsImpl implements Actions
{
static final String CONSTRAINT_NOT_EXISTS = "Action parameter constraints for name %s do not exist.";
private ActionService actionService;
private DictionaryService dictionaryService;
private NamespaceService namespaceService;
private NodeService nodeService;
private NamespacePrefixResolver prefixResolver;
private ActionParameterConverter actionParameterConverter;
public void setActionService(ActionService actionService)
{
@@ -99,6 +110,11 @@ public class ActionsImpl implements Actions
this.prefixResolver = prefixResolver;
}
public void setActionParameterConverter(ActionParameterConverter actionParameterConverter)
{
this.actionParameterConverter = actionParameterConverter;
}
@Override
public ActionDefinition getActionDefinitionById(String actionDefinitionId)
{
@@ -125,10 +141,10 @@ public class ActionsImpl implements Actions
return result;
}
private ActionDefinition getActionDefinition(
private ActionDefinition getActionDefinition(
org.alfresco.service.cmr.action.ActionDefinition actionDefinitionId)
{
{
List<ActionDefinition.ParameterDefinition> paramDefs =
actionDefinitionId.
getParameterDefinitions().
@@ -145,7 +161,7 @@ public class ActionsImpl implements Actions
actionDefinitionId.getTrackStatus(),
paramDefs);
}
@Override
public CollectionWithPagingInfo<ActionDefinition> getActionDefinitions(NodeRef nodeRef, Parameters params)
{
@@ -174,7 +190,7 @@ public class ActionsImpl implements Actions
sortKey = Actions.SortKey.valueOf(sorting.get(0).column.toUpperCase());
sortAsc = sorting.get(0).asc;
}
Comparator<? super ActionDefinition> comparator;
switch (sortKey)
{
@@ -187,7 +203,7 @@ public class ActionsImpl implements Actions
default:
throw new IllegalArgumentException("Invalid sort key, must be either 'title' or 'name'.");
}
if (!sortAsc)
{
comparator = comparator.reversed();
@@ -220,7 +236,7 @@ public class ActionsImpl implements Actions
skip(skip).
limit(maxItems).
collect(Collectors.toList());
boolean hasMoreItems = actionDefinitions.size() > (skip + maxItems);
return CollectionWithPagingInfo.asPaged(
@@ -297,6 +313,52 @@ public class ActionsImpl implements Actions
return result;
}
@Override
@Experimental
public ActionParameterConstraint getActionConstraint(String constraintName)
{
final ParameterConstraint parameterConstraint = actionService.getParameterConstraint(constraintName);
if (Objects.isNull(parameterConstraint))
{
throw new NotFoundException(String.format(CONSTRAINT_NOT_EXISTS, constraintName));
}
return mapToActionConstraint(parameterConstraint);
}
@Experimental
private ActionParameterConstraint mapToActionConstraint(ParameterConstraint parameterConstraint)
{
final ActionParameterConstraint constraint = new ActionParameterConstraint();
constraint.setConstraintName(parameterConstraint.getName());
constraint.setConstraintValues(getConstraintDataList(parameterConstraint));
return constraint;
}
@Experimental
private List<ActionParameterConstraint.ConstraintData> getConstraintDataList(final ParameterConstraint parameterConstraint)
{
final Map<String, String> constraintValues = parameterConstraint.getValues();
if (parameterConstraint instanceof FolderContentsParameterConstraint)
{
return convertNodeRefConstraintValues(constraintValues).entrySet().stream()
.map(e -> new ActionParameterConstraint.ConstraintData(e.getKey(), e.getValue()))
.collect(Collectors.toList());
} else
{
return constraintValues.entrySet().stream()
.map(e -> new ActionParameterConstraint.ConstraintData(e.getKey(), e.getValue()))
.collect(Collectors.toList());
}
}
@Experimental
private Map<String, String> convertNodeRefConstraintValues(final Map<String, String> inputValues)
{
return inputValues.entrySet().stream()
.collect(Collectors.toMap(e -> actionParameterConverter.convertParamFromServiceModel(new NodeRef(e.getKey())).toString(),
Map.Entry::getValue));
}
private Map<String, Serializable> extractActionParams(org.alfresco.service.cmr.action.ActionDefinition actionDefinition, Map<String, String> params)
{
Map<String, Serializable> parameterValues = new HashMap<>();
@@ -391,7 +453,7 @@ public class ActionsImpl implements Actions
map(this::toShortQName).
collect(Collectors.toList());
}
private String toShortQName(QName type)
{
return type.toPrefixString(prefixResolver);

View File

@@ -0,0 +1,111 @@
/*
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.rest.api.impl.mapper.rules;
import static java.util.Collections.emptyMap;
import static org.alfresco.repo.action.access.ActionAccessRestriction.ACTION_CONTEXT_PARAM_NAME;
import java.io.Serializable;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.alfresco.repo.action.ActionImpl;
import org.alfresco.repo.action.CompositeActionImpl;
import org.alfresco.rest.api.impl.rules.ActionParameterConverter;
import org.alfresco.rest.api.model.mapper.RestModelMapper;
import org.alfresco.rest.api.model.rules.Action;
import org.alfresco.service.Experimental;
import org.alfresco.util.GUID;
import org.apache.commons.collections.CollectionUtils;
@Experimental
public class RestRuleActionModelMapper implements RestModelMapper<Action, org.alfresco.service.cmr.action.Action>
{
private final ActionParameterConverter parameterConverter;
public RestRuleActionModelMapper(ActionParameterConverter parameterConverter)
{
this.parameterConverter = parameterConverter;
}
/**
* Converts service POJO action to REST model action.
*
* @param actionModel - {@link org.alfresco.service.cmr.action.Action} service POJO
* @return {@link Action} REST model
*/
@Override
public Action toRestModel(org.alfresco.service.cmr.action.Action actionModel)
{
if (actionModel == null)
{
return null;
}
final Action.Builder builder = Action.builder().actionDefinitionId(actionModel.getActionDefinitionName());
if (actionModel.getParameterValues() != null)
{
final Map<String, Serializable> convertedParams = actionModel.getParameterValues()
.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> parameterConverter.convertParamFromServiceModel(e.getValue())));
convertedParams.remove(ACTION_CONTEXT_PARAM_NAME);
builder.params(convertedParams);
}
return builder.create();
}
/**
* Convert the REST model objects to composite action service POJO.
*
* @param actions List of actions.
* @return The composite action service POJO.
*/
@Override
public org.alfresco.service.cmr.action.Action toServiceModel(Collection<Action> actions)
{
if (CollectionUtils.isEmpty(actions))
{
return null;
}
final org.alfresco.service.cmr.action.CompositeAction compositeAction = new CompositeActionImpl(null, GUID.generate());
actions.forEach(action -> compositeAction.addAction(toServiceAction(action)));
return compositeAction;
}
private org.alfresco.service.cmr.action.Action toServiceAction(Action action)
{
final Map<String, Serializable> params = Optional.ofNullable(action.getParams()).orElse(emptyMap());
final Map<String, Serializable> convertedParams =
parameterConverter.getConvertedParams(params, action.getActionDefinitionId());
return new ActionImpl(null, GUID.generate(), action.getActionDefinitionId(), convertedParams);
}
}

View File

@@ -0,0 +1,162 @@
/*
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.rest.api.impl.mapper.rules;
import static org.alfresco.repo.action.evaluator.NoConditionEvaluator.NAME;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import org.alfresco.rest.api.model.mapper.RestModelMapper;
import org.alfresco.rest.api.model.rules.CompositeCondition;
import org.alfresco.rest.api.model.rules.ConditionOperator;
import org.alfresco.rest.api.model.rules.SimpleCondition;
import org.alfresco.service.Experimental;
import org.alfresco.service.cmr.action.ActionCondition;
import org.apache.commons.collections.CollectionUtils;
@Experimental
public class RestRuleCompositeConditionModelMapper implements RestModelMapper<CompositeCondition, ActionCondition>
{
private final RestModelMapper<SimpleCondition, ActionCondition> simpleConditionMapper;
public RestRuleCompositeConditionModelMapper(
RestModelMapper<SimpleCondition, ActionCondition> simpleConditionMapper)
{
this.simpleConditionMapper = simpleConditionMapper;
}
/**
* Converts Action conditions (service POJO) list to composite condition (REST model).
*
* @param actionConditions - list of {@link ActionCondition} service POJOs
* @return {@link CompositeCondition} REST model
*/
@Override
public CompositeCondition toRestModel(final Collection<ActionCondition> actionConditions)
{
if (CollectionUtils.isEmpty(actionConditions))
{
return null;
}
final List<ActionCondition> filteredActions = actionConditions.stream()
.filter(Objects::nonNull)
.filter(c -> !NAME.equals(c.getActionConditionDefinitionName()))
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(filteredActions))
{
return null;
}
final CompositeCondition conditions = new CompositeCondition();
conditions.setCompositeConditions(new ArrayList<>());
// group action conditions by inversion flag
filteredActions.stream()
.collect(Collectors.groupingBy(ActionCondition::getInvertCondition))
// map action condition sub lists
.forEach((inverted, actionConditionsPart) -> Optional
.ofNullable(ofActionConditions(actionConditionsPart, inverted, ConditionOperator.AND))
// if composite condition present add to final list
.ifPresent(compositeCondition -> conditions.getCompositeConditions().add(compositeCondition)));
if (CollectionUtils.isEmpty(conditions.getCompositeConditions()))
{
conditions.setCompositeConditions(null);
}
return conditions;
}
@Override
public List<ActionCondition> toServiceModels(final CompositeCondition compositeCondition)
{
final List<ActionCondition> actionConditions = new ArrayList<>();
if (compositeCondition == null)
{
return actionConditions;
}
if (CollectionUtils.isNotEmpty(compositeCondition.getSimpleConditions()))
{
compositeCondition.getSimpleConditions()
.forEach(simpleCondition -> actionConditions.add(mapSimpleCondition(simpleCondition, compositeCondition.isInverted())));
}
if (CollectionUtils.isNotEmpty(compositeCondition.getCompositeConditions()))
{
compositeCondition.getCompositeConditions().forEach(condition -> actionConditions.addAll(toServiceModels(condition)));
}
return actionConditions;
}
private ActionCondition mapSimpleCondition(final SimpleCondition simpleCondition, final boolean inverted)
{
final ActionCondition actionCondition = simpleConditionMapper.toServiceModel(simpleCondition);
actionCondition.setInvertCondition(inverted);
return actionCondition;
}
private CompositeCondition ofActionConditions(final List<ActionCondition> actionConditions, final boolean inverted,
final ConditionOperator conditionOperator)
{
if (CollectionUtils.isEmpty(actionConditions))
{
return null;
}
return ofSimpleConditions(simpleConditionMapper.toRestModels(actionConditions), inverted, conditionOperator);
}
/**
* Creates a composite condition instance of simple conditions.
*
* @param simpleConditions - list of {@link SimpleCondition}
* @param inverted - determines if condition should be inverted
* @param conditionOperator - determines the operation, see {@link ConditionOperator}
* @return {@link CompositeCondition}
*/
private CompositeCondition ofSimpleConditions(final List<SimpleCondition> simpleConditions, final boolean inverted,
final ConditionOperator conditionOperator)
{
return of(simpleConditions, null, inverted, conditionOperator);
}
private CompositeCondition of(final List<SimpleCondition> simpleConditions, final List<CompositeCondition> compositeConditions,
final boolean inverted, final ConditionOperator conditionOperator)
{
if (CollectionUtils.isEmpty(simpleConditions) && CollectionUtils.isEmpty(compositeConditions))
{
return null;
}
return CompositeCondition.builder()
.inverted(inverted)
.booleanMode(conditionOperator)
.simpleConditions(simpleConditions)
.compositeConditions(compositeConditions)
.create();
}
}

View File

@@ -0,0 +1,164 @@
/*
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.rest.api.impl.mapper.rules;
import java.io.Serializable;
import java.util.Map;
import java.util.stream.Collectors;
import org.alfresco.repo.action.ActionImpl;
import org.alfresco.repo.action.executer.ScriptActionExecuter;
import org.alfresco.rest.api.Nodes;
import org.alfresco.rest.api.impl.rules.ActionParameterConverter;
import org.alfresco.rest.api.model.mapper.RestModelMapper;
import org.alfresco.rest.api.model.rules.Action;
import org.alfresco.rest.api.model.rules.CompositeCondition;
import org.alfresco.rest.api.model.rules.Rule;
import org.alfresco.rest.api.model.rules.RuleTrigger;
import org.alfresco.service.Experimental;
import org.alfresco.service.cmr.action.ActionCondition;
import org.alfresco.service.cmr.action.CompositeAction;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.util.GUID;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@Experimental
public class RestRuleModelMapper implements RestModelMapper<Rule, org.alfresco.service.cmr.rule.Rule>
{
private static Log log = LogFactory.getLog(RestRuleModelMapper.class);
private final RestModelMapper<CompositeCondition, ActionCondition> compositeConditionMapper;
private final RestModelMapper<Action, org.alfresco.service.cmr.action.Action> actionMapper;
private final Nodes nodes;
private final ActionParameterConverter actionParameterConverter;
public RestRuleModelMapper(
RestModelMapper<CompositeCondition, ActionCondition> compositeConditionMapper,
RestModelMapper<Action, org.alfresco.service.cmr.action.Action> actionMapper,
Nodes nodes,
ActionParameterConverter actionParameterConverter)
{
this.compositeConditionMapper = compositeConditionMapper;
this.actionMapper = actionMapper;
this.nodes = nodes;
this.actionParameterConverter = actionParameterConverter;
}
/**
* Converts service POJO rule to REST model rule.
*
* @param serviceRule - {@link org.alfresco.service.cmr.rule.Rule} service POJO
* @return {@link Rule} REST model
*/
@Override
public Rule toRestModel(org.alfresco.service.cmr.rule.Rule serviceRule)
{
if (serviceRule == null)
{
return null;
}
final Rule.Builder builder = Rule.builder()
.name(serviceRule.getTitle())
.description(serviceRule.getDescription())
.isEnabled(!serviceRule.getRuleDisabled())
.isInheritable(serviceRule.isAppliedToChildren())
.isAsynchronous(serviceRule.getExecuteAsynchronously());
if (serviceRule.getNodeRef() != null)
{
builder.id(serviceRule.getNodeRef().getId());
}
if (CollectionUtils.isNotEmpty(serviceRule.getRuleTypes()))
{
builder.triggers(serviceRule.getRuleTypes().stream().map(RuleTrigger::of).collect(Collectors.toList()));
}
if (serviceRule.getAction() != null)
{
builder.conditions(compositeConditionMapper.toRestModel(serviceRule.getAction().getActionConditions()));
if (serviceRule.getAction().getCompensatingAction() != null &&
serviceRule.getAction().getCompensatingAction().getParameterValue(ScriptActionExecuter.PARAM_SCRIPTREF) != null)
{
String errorScript = actionParameterConverter.convertParamFromServiceModel(
serviceRule.getAction().getCompensatingAction().getParameterValue(ScriptActionExecuter.PARAM_SCRIPTREF)).toString();
builder.errorScript(errorScript);
}
if (serviceRule.getAction() instanceof CompositeAction && ((CompositeAction) serviceRule.getAction()).getActions() != null)
{
builder.actions(
((CompositeAction) serviceRule.getAction()).getActions().stream()
.map(actionMapper::toRestModel)
.collect(Collectors.toList()));
} else {
log.warn("Rule Action should be of 'CompositeAction' type but found: " + serviceRule.getAction().getClass());
}
}
return builder.create();
}
/**
* Convert the REST model object to the equivalent service POJO.
*
* @param restRuleModel {@link Rule} REST model.
* @return The rule service POJO.
*/
@Override
public org.alfresco.service.cmr.rule.Rule toServiceModel(Rule restRuleModel)
{
final org.alfresco.service.cmr.rule.Rule serviceRule = new org.alfresco.service.cmr.rule.Rule();
final NodeRef nodeRef = (restRuleModel.getId() != null) ? nodes.validateOrLookupNode(restRuleModel.getId(), null) : null;
serviceRule.setNodeRef(nodeRef);
serviceRule.setTitle(restRuleModel.getName());
serviceRule.setDescription(restRuleModel.getDescription());
serviceRule.setRuleDisabled(!restRuleModel.getIsEnabled());
serviceRule.applyToChildren(restRuleModel.getIsInheritable());
serviceRule.setExecuteAsynchronously(restRuleModel.getIsAsynchronous());
serviceRule.setRuleTypes(restRuleModel.getTriggers());
serviceRule.setAction(actionMapper.toServiceModel(restRuleModel.getActions()));
if (restRuleModel.getErrorScript() != null)
{
final org.alfresco.service.cmr.action.Action compensatingAction =
new ActionImpl(null, GUID.generate(), ScriptActionExecuter.NAME);
final Map<String, Serializable> scriptParam = actionParameterConverter
.getConvertedParams(Map.of(ScriptActionExecuter.PARAM_SCRIPTREF, restRuleModel.getErrorScript()),
compensatingAction.getActionDefinitionName());
compensatingAction.setParameterValues(scriptParam);
serviceRule.getAction().setCompensatingAction(compensatingAction);
}
if (restRuleModel.getConditions() != null)
{
compositeConditionMapper.toServiceModels(restRuleModel.getConditions())
.forEach(condition -> serviceRule.getAction().addActionCondition(condition));
}
return serviceRule;
}
}

View File

@@ -31,6 +31,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import com.rometools.utils.Strings;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.action.ActionConditionImpl;
import org.alfresco.repo.action.evaluator.CompareMimeTypeEvaluator;
@@ -57,6 +58,12 @@ import org.apache.commons.collections.MapUtils;
@Experimental
public class RestRuleSimpleConditionModelMapper implements RestModelMapper<SimpleCondition, ActionCondition>
{
static final String CATEGORY_INVALID_MSG = "Category in condition is invalid";
static final String PARAM_CATEGORY = "category";
static final String PARAM_MIMETYPE = "mimetype";
static final String FIELD_NOT_NULL = "Field in condition must not be blank";
static final String PARAMETER_NOT_NULL = "Parameter in condition must not be blank";
static final String COMPARATOR_NOT_NULL = "Comparator in condition must not be blank";
private final NamespaceService namespaceService;
private final Nodes nodes;
@@ -99,14 +106,12 @@ public class RestRuleSimpleConditionModelMapper implements RestModelMapper<Simpl
public ActionCondition toServiceModel(SimpleCondition restModel)
{
final String field = restModel.getField();
if (field == null)
{
return null;
}
checkStringNotBlank(field, FIELD_NOT_NULL);
Map<String, Serializable> parameterValues = new HashMap<>();
final Map<String, Serializable> parameterValues = new HashMap<>();
String conditionDefinitionId;
String parameter = restModel.getParameter();
final String parameter = restModel.getParameter();
checkStringNotBlank(parameter, PARAMETER_NOT_NULL);
switch (field)
{
@@ -118,21 +123,21 @@ public class RestRuleSimpleConditionModelMapper implements RestModelMapper<Simpl
conditionDefinitionId = HasTagEvaluator.NAME;
parameterValues.put(HasTagEvaluator.PARAM_TAG, parameter);
break;
case SimpleCondition.PARAM_CATEGORY:
case PARAM_CATEGORY:
conditionDefinitionId = InCategoryEvaluator.NAME;
parameterValues.put(InCategoryEvaluator.PARAM_CATEGORY_ASPECT, ContentModel.ASPECT_GEN_CLASSIFIABLE);
try
{
parameterValues.put(InCategoryEvaluator.PARAM_CATEGORY_VALUE, nodes.validateOrLookupNode(parameter, null));
} catch (EntityNotFoundException e) {
throw new InvalidArgumentException(SimpleCondition.CATEGORY_INVALID_MSG);
throw new InvalidArgumentException(CATEGORY_INVALID_MSG);
}
break;
case IsSubTypeEvaluator.PARAM_TYPE:
conditionDefinitionId = IsSubTypeEvaluator.NAME;
parameterValues.put(IsSubTypeEvaluator.PARAM_TYPE, QName.createQName(parameter, namespaceService));
break;
case SimpleCondition.PARAM_MIMETYPE:
case PARAM_MIMETYPE:
conditionDefinitionId = CompareMimeTypeEvaluator.NAME;
parameterValues.put(ComparePropertyValueEvaluator.PARAM_PROPERTY, ContentModel.TYPE_CONTENT);
parameterValues.put(ComparePropertyValueEvaluator.PARAM_VALUE, parameter);
@@ -151,6 +156,7 @@ public class RestRuleSimpleConditionModelMapper implements RestModelMapper<Simpl
// else create common property evaluator
parameterValues.put(ComparePropertyValueEvaluator.PARAM_PROPERTY, QName.createQName(field, namespaceService));
}
checkStringNotBlank(restModel.getComparator(), COMPARATOR_NOT_NULL);
parameterValues.put(ComparePropertyValueEvaluator.PARAM_OPERATION, restModel.getComparator().toUpperCase());
parameterValues.put(ComparePropertyValueEvaluator.PARAM_VALUE, parameter);
break;
@@ -158,6 +164,13 @@ public class RestRuleSimpleConditionModelMapper implements RestModelMapper<Simpl
return new ActionConditionImpl(UUID.randomUUID().toString(), conditionDefinitionId, parameterValues);
}
private void checkStringNotBlank(final String string, final String message) {
if (Strings.isBlank(string))
{
throw new InvalidArgumentException(message);
}
}
private static SimpleCondition createComparePropertyValueCondition(final ActionCondition actionCondition, final NamespaceService namespaceService)
{
final SimpleCondition.Builder builder = SimpleCondition.builder();
@@ -176,7 +189,7 @@ public class RestRuleSimpleConditionModelMapper implements RestModelMapper<Simpl
private static SimpleCondition createCompareMimeTypeCondition(final ActionCondition actionCondition)
{
return SimpleCondition.builder()
.field(SimpleCondition.PARAM_MIMETYPE)
.field(PARAM_MIMETYPE)
.comparator(ComparePropertyValueOperation.EQUALS.toString().toLowerCase())
.parameter(actionCondition.getParameterValues().get(ComparePropertyValueEvaluator.PARAM_VALUE).toString())
.create();
@@ -203,7 +216,7 @@ public class RestRuleSimpleConditionModelMapper implements RestModelMapper<Simpl
private static SimpleCondition createInCategoryCondition(final ActionCondition actionCondition)
{
return SimpleCondition.builder()
.field(SimpleCondition.PARAM_CATEGORY)
.field(PARAM_CATEGORY)
.comparator(ComparePropertyValueOperation.EQUALS.toString().toLowerCase())
.parameter(((NodeRef) actionCondition.getParameterValues().get(InCategoryEvaluator.PARAM_CATEGORY_VALUE)).getId())
.create();

View File

@@ -65,7 +65,7 @@ public class ActionParameterConverter
this.namespaceService = namespaceService;
}
Map<String, Serializable> getConvertedParams(Map<String, Serializable> params, String name)
public Map<String, Serializable> getConvertedParams(Map<String, Serializable> params, String name)
{
final Map<String, Serializable> parameters = new HashMap<>(params.size());
final ParameterizedItemDefinition definition;

View File

@@ -28,13 +28,13 @@ package org.alfresco.rest.api.impl.rules;
import java.util.List;
import org.alfresco.rest.api.model.mapper.RestModelMapper;
import org.alfresco.rest.api.model.rules.CompositeCondition;
import org.alfresco.rest.api.model.rules.Rule;
import org.alfresco.rest.api.model.rules.SimpleCondition;
import org.alfresco.service.Experimental;
import org.alfresco.service.cmr.action.ActionCondition;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.rule.RuleService;
import org.alfresco.service.namespace.NamespaceService;
/** Responsible for creating {@link Rule} objects. */
@Experimental
@@ -43,11 +43,11 @@ public class RuleLoader
public static final String IS_SHARED = "isShared";
private RuleService ruleService;
private NodeValidator nodeValidator;
private RestModelMapper<SimpleCondition, ActionCondition> simpleConditionMapper;
private RestModelMapper<Rule, org.alfresco.service.cmr.rule.Rule> ruleMapper;
public Rule loadRule(org.alfresco.service.cmr.rule.Rule ruleModel, List<String> includes)
{
Rule rule = Rule.from(ruleModel, simpleConditionMapper);
final Rule rule = ruleMapper.toRestModel(ruleModel);
if (includes != null && includes.contains(IS_SHARED))
{
NodeRef ruleSet = ruleService.getRuleSetNode(ruleModel.getNodeRef());
@@ -67,9 +67,9 @@ public class RuleLoader
this.nodeValidator = nodeValidator;
}
public void setSimpleConditionMapper(
RestModelMapper<SimpleCondition, ActionCondition> simpleConditionMapper)
public void setRuleMapper(
RestModelMapper<Rule, org.alfresco.service.cmr.rule.Rule> ruleMapper)
{
this.simpleConditionMapper = simpleConditionMapper;
this.ruleMapper = ruleMapper;
}
}

View File

@@ -25,14 +25,16 @@
*/
package org.alfresco.rest.api.impl.rules;
import static java.util.stream.Collectors.toList;
import static org.alfresco.rest.api.model.rules.InclusionType.INHERITED;
import static org.alfresco.rest.api.model.rules.InclusionType.LINKED;
import static org.alfresco.rest.api.model.rules.InclusionType.OWNED;
import java.util.List;
import java.util.stream.Collectors;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.rest.api.impl.mapper.rules.RestRuleModelMapper;
import org.alfresco.rest.api.model.rules.RuleSet;
import org.alfresco.service.Experimental;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
@@ -49,7 +51,10 @@ public class RuleSetLoader
protected static final String INHERITED_BY = "inheritedBy";
protected static final String LINKED_TO_BY = "linkedToBy";
protected static final String IS_INHERITED = "isInherited";
protected static final String IS_LINKED_TO = "isLinkedTo";
protected static final String RULE_IDS = "ruleIds";
private static final int MAX_INHERITED_BY_SIZE = 100;
private static final int MAX_LINKED_TO_BY_SIZE = 100;
private NodeService nodeService;
private RuleService ruleService;
@@ -93,17 +98,20 @@ public class RuleSetLoader
}
if (includes.contains(LINKED_TO_BY))
{
List<NodeRef> linkedToBy = nodeService.getParentAssocs(ruleSetNodeRef)
.stream()
.map(ChildAssociationRef::getParentRef)
.filter(folder -> !folder.equals(parentRef))
.collect(Collectors.toList());
ruleSet.setLinkedToBy(linkedToBy);
ruleSet.setLinkedToBy(loadLinkedToBy(ruleSetNodeRef));
}
if (includes.contains(IS_INHERITED))
{
ruleSet.setIsInherited(loadIsInherited(ruleSetNodeRef));
}
if (includes.contains(IS_LINKED_TO))
{
ruleSet.setIsLinkedTo(loadIsLinkedTo(ruleSetNodeRef, parentRef));
}
if (includes.contains(RULE_IDS))
{
ruleSet.setRuleIds(loadRuleIds(parentRef));
}
}
return ruleSet;
}
@@ -113,11 +121,41 @@ public class RuleSetLoader
return ruleService.getFoldersInheritingRuleSet(ruleSetNodeRef, MAX_INHERITED_BY_SIZE);
}
private List<NodeRef> loadLinkedToBy(NodeRef ruleSetNodeRef)
{
return ruleService.getFoldersLinkingToRuleSet(ruleSetNodeRef, MAX_LINKED_TO_BY_SIZE);
}
private boolean loadIsInherited(NodeRef ruleSetNodeRef)
{
return AuthenticationUtil.runAsSystem(() -> !ruleService.getFoldersInheritingRuleSet(ruleSetNodeRef, 1).isEmpty());
}
/**
* Check if any parents of the rule set node are not the owning folder.
*
* @param ruleSetNodeRef The rule set node.
* @param parentRef The owning folder.
* @return True if another folder links to the rule set.
*/
private Boolean loadIsLinkedTo(NodeRef ruleSetNodeRef, NodeRef parentRef)
{
return AuthenticationUtil.runAsSystem(() ->
nodeService.getParentAssocs(ruleSetNodeRef)
.stream()
.map(ChildAssociationRef::getParentRef)
.anyMatch(folder -> !folder.equals(parentRef))
);
}
public List<String> loadRuleIds(NodeRef folderNodeRef)
{
return ruleService.getRules(folderNodeRef, false).stream()
.map(org.alfresco.service.cmr.rule.Rule::getNodeRef)
.map(NodeRef::getId)
.collect(toList());
}
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
@@ -127,4 +165,5 @@ public class RuleSetLoader
{
this.ruleService = ruleService;
}
}

View File

@@ -28,11 +28,18 @@ package org.alfresco.rest.api.impl.rules;
import static java.util.stream.Collectors.toList;
import static org.alfresco.service.cmr.repository.StoreRef.STORE_REF_WORKSPACE_SPACESSTORE;
import static org.alfresco.util.collections.CollectionUtils.isEmpty;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.IntStream;
import org.alfresco.repo.rule.RuleModel;
import org.alfresco.repo.rule.RuntimeRuleService;
import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.rest.api.RuleSets;
import org.alfresco.rest.api.model.rules.RuleSet;
import org.alfresco.rest.api.model.rules.RuleSetLink;
@@ -44,10 +51,14 @@ import org.alfresco.service.Experimental;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.rule.RuleService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Experimental
public class RuleSetsImpl implements RuleSets
{
private static final Logger LOGGER = LoggerFactory.getLogger(RuleSetsImpl.class);
private RuleSetLoader ruleSetLoader;
private RuleService ruleService;
private NodeValidator validator;
@@ -61,15 +72,42 @@ public class RuleSetsImpl implements RuleSets
List<RuleSet> ruleSets = ruleService.getNodesSupplyingRuleSets(folderNode)
.stream()
.map(ruleService::getRuleSetNode)
.map(supplyingNode -> loadRuleSet(supplyingNode, folderNode, includes))
.filter(Objects::nonNull)
.map(nodeRef -> ruleSetLoader.loadRuleSet(nodeRef, folderNode, includes))
.distinct()
.collect(toList());
return ListPage.of(ruleSets, paging);
}
/**
* Load the specified rule set if the user has permission.
*
* @param supplyingNode The folder supplying a rule set.
* @param folderNode The folder being supplied with rule sets.
* @param includes The list of optional fields to include for each rule set in the response.
* @return The rule set from the DB or null if the folder has no rule set, or the current user does not have permission to view it.
*/
private RuleSet loadRuleSet(NodeRef supplyingNode, NodeRef folderNode, List<String> includes)
{
NodeRef ruleSetNode = ruleService.getRuleSetNode(supplyingNode);
// Check if the folder has no rule sets.
if (ruleSetNode == null)
{
return null;
}
try
{
return ruleSetLoader.loadRuleSet(ruleSetNode, folderNode, includes);
}
catch (AccessDeniedException e)
{
LOGGER.debug("User does not have permission to view rule set with id {}.", ruleSetNode, e);
return null;
}
}
@Override
public RuleSet getRuleSetById(String folderNodeId, String ruleSetId, List<String> includes)
{
@@ -79,6 +117,43 @@ public class RuleSetsImpl implements RuleSets
return ruleSetLoader.loadRuleSet(ruleSetNode, folderNode, includes);
}
@Override
public RuleSet updateRuleSet(String folderNodeId, RuleSet ruleSet, List<String> includes)
{
// Editing the order of the rules doesn't require permission to edit the rule set itself.
NodeRef folderNode = validator.validateFolderNode(folderNodeId, false);
NodeRef ruleSetNode = validator.validateRuleSetNode(ruleSet.getId(), folderNode);
RuleSet returnedRuleSet = ruleSetLoader.loadRuleSet(ruleSetNode, folderNode, includes);
// Currently the only field that can be updated is ruleIds to reorder the rules.
List<String> suppliedRuleIds = ruleSet.getRuleIds();
if (!isEmpty(suppliedRuleIds))
{
// Check there are no duplicate rule ids in the request.
Set<String> suppliedRuleIdSet = new HashSet<>(suppliedRuleIds);
// Check that the set of rule ids hasn't changed.
Set<String> existingRuleIds = new HashSet<>(ruleSetLoader.loadRuleIds(folderNode));
if (suppliedRuleIdSet.size() != suppliedRuleIds.size() || !suppliedRuleIdSet.equals(existingRuleIds))
{
throw new InvalidArgumentException("Unexpected set of rule ids - received " + suppliedRuleIds + " but expected " + existingRuleIds);
}
IntStream.range(0, suppliedRuleIds.size()).forEach(index ->
{
NodeRef ruleNode = new NodeRef(STORE_REF_WORKSPACE_SPACESSTORE, suppliedRuleIds.get(index));
ruleService.setRulePosition(folderNode, ruleNode, index);
});
if (includes.contains(RuleSetLoader.RULE_IDS))
{
returnedRuleSet.setRuleIds(suppliedRuleIds);
}
}
return returnedRuleSet;
}
@Override
public RuleSetLink linkToRuleSet(String folderNodeId, String linkToNodeId)
{
@@ -86,8 +161,8 @@ public class RuleSetsImpl implements RuleSets
final NodeRef folderNodeRef = validator.validateFolderNode(folderNodeId,true);
final boolean isRuleSetNode = validator.isRuleSetNode(linkToNodeId);
final NodeRef linkToNodeRef = isRuleSetNode
? validator.validateRuleSetNode(linkToNodeId, true)
: validator.validateFolderNode(linkToNodeId, true);
? validator.validateRuleSetNode(linkToNodeId, false)
: validator.validateFolderNode(linkToNodeId, false);
//The target node should have pre-existing rules to link to
if (!ruleService.hasRules(linkToNodeRef)) {
@@ -110,6 +185,22 @@ public class RuleSetsImpl implements RuleSets
return ruleSetLink;
}
@Override
public void unlinkRuleSet(String folderNodeId, String ruleSetId)
{
final NodeRef folderNodeRef = validator.validateFolderNode(folderNodeId,true);
final NodeRef ruleSetNodeRef = validator.validateRuleSetNode(ruleSetId, folderNodeRef);
//The folder should be linked to a rule set
if (!ruleService.isLinkedToRuleNode(folderNodeRef))
{
throw new InvalidArgumentException("The folder is not linked to a rule set.");
}
//The following line also handles the deletion of the parent-child association that gets created during linking
nodeService.removeAspect(folderNodeRef,RuleModel.ASPECT_RULES);
}
public void setRuleSetLoader(RuleSetLoader ruleSetLoader)
{
this.ruleSetLoader = ruleSetLoader;

View File

@@ -26,28 +26,30 @@
package org.alfresco.rest.api.impl.rules;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.alfresco.rest.api.Nodes;
import org.alfresco.repo.action.ActionImpl;
import org.alfresco.repo.action.access.ActionAccessRestriction;
import org.alfresco.repo.action.executer.ExecuteAllRulesActionExecuter;
import org.alfresco.rest.api.Rules;
import org.alfresco.rest.api.model.mapper.RestModelMapper;
import org.alfresco.rest.api.model.rules.Rule;
import org.alfresco.rest.api.model.rules.RuleExecution;
import org.alfresco.rest.api.model.rules.RuleSet;
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
import org.alfresco.rest.api.model.rules.SimpleCondition;
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.ListPage;
import org.alfresco.rest.framework.resource.parameters.Paging;
import org.alfresco.service.Experimental;
import org.alfresco.service.cmr.action.ActionCondition;
import org.alfresco.service.cmr.action.CompositeAction;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.rule.RuleService;
import org.alfresco.util.collections.CollectionUtils;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.util.GUID;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -57,13 +59,12 @@ public class RulesImpl implements Rules
private static final Logger LOGGER = LoggerFactory.getLogger(RulesImpl.class);
private static final String MUST_HAVE_AT_LEAST_ONE_ACTION = "A rule must have at least one action";
private Nodes nodes;
private ActionService actionService;
private RuleService ruleService;
private NodeValidator validator;
private RuleLoader ruleLoader;
private ActionParameterConverter actionParameterConverter;
private ActionPermissionValidator actionPermissionValidator;
private RestModelMapper<SimpleCondition, ActionCondition> simpleConditionMapper;
private RestModelMapper<Rule, org.alfresco.service.cmr.rule.Rule> ruleMapper;
@Override
public CollectionWithPagingInfo<Rule> getRules(final String folderNodeId,
@@ -76,7 +77,7 @@ public class RulesImpl implements Rules
NodeRef owningFolder = ruleService.getOwningNodeRef(ruleSetNode);
final List<Rule> rules = ruleService.getRules(owningFolder, false).stream()
.map(ruleModel -> loadRuleAndConvertActionParams(ruleModel, includes))
.map(ruleModel -> ruleLoader.loadRule(ruleModel, includes))
.collect(Collectors.toList());
return ListPage.of(rules, paging);
@@ -89,7 +90,7 @@ public class RulesImpl implements Rules
final NodeRef ruleSetNodeRef = validator.validateRuleSetNode(ruleSetId, folderNodeRef);
final NodeRef ruleNodeRef = validator.validateRuleNode(ruleId, ruleSetNodeRef);
return loadRuleAndConvertActionParams(ruleService.getRule(ruleNodeRef), includes);
return ruleLoader.loadRule(ruleService.getRule(ruleNodeRef), includes);
}
@Override
@@ -105,7 +106,7 @@ public class RulesImpl implements Rules
return rules.stream()
.map(this::mapToServiceModelAndValidateActions)
.map(rule -> ruleService.saveRule(folderNodeRef, rule))
.map(rule -> loadRuleAndConvertActionParams(rule, includes))
.map(rule -> ruleLoader.loadRule(rule, includes))
.collect(Collectors.toList());
}
@@ -131,36 +132,39 @@ public class RulesImpl implements Rules
ruleService.removeRule(folderNodeRef, rule);
}
@Override
public RuleExecution executeRules(final String folderNodeId, final boolean eachSubFolderIncluded)
{
final NodeRef folderNodeRef = validator.validateFolderNode(folderNodeId, false);
final Map<String, Serializable> parameterValues = new HashMap<>();
parameterValues.put(ExecuteAllRulesActionExecuter.PARAM_RUN_ALL_RULES_ON_CHILDREN, eachSubFolderIncluded);
parameterValues.put(ExecuteAllRulesActionExecuter.PARAM_EXECUTE_INHERITED_RULES, true);
final ActionImpl action = new ActionImpl(null, GUID.generate(), ExecuteAllRulesActionExecuter.NAME);
action.setNodeRef(folderNodeRef);
action.setParameterValues(parameterValues);
ActionAccessRestriction.setActionContext(action, ActionAccessRestriction.V1_ACTION_CONTEXT);
actionService.executeAction(action, folderNodeRef, true, false);
return RuleExecution.builder()
.eachSubFolderIncluded(eachSubFolderIncluded)
.create();
}
private org.alfresco.service.cmr.rule.Rule mapToServiceModelAndValidateActions(Rule rule)
{
if (CollectionUtils.isEmpty(rule.getActions()))
{
throw new InvalidArgumentException(MUST_HAVE_AT_LEAST_ONE_ACTION);
}
final org.alfresco.service.cmr.rule.Rule serviceModelRule = rule.toServiceModel(nodes, simpleConditionMapper);
final CompositeAction compositeAction = (CompositeAction) serviceModelRule.getAction();
compositeAction.getActions().forEach(action -> action.setParameterValues(
actionParameterConverter.getConvertedParams(action.getParameterValues(), action.getActionDefinitionName())));
final org.alfresco.service.cmr.rule.Rule serviceModelRule = ruleMapper.toServiceModel(rule);
return actionPermissionValidator.validateRulePermissions(serviceModelRule);
}
private Rule loadRuleAndConvertActionParams(org.alfresco.service.cmr.rule.Rule ruleModel, List<String> includes)
public void setActionService(ActionService actionService)
{
final Rule rule = ruleLoader.loadRule(ruleModel, includes);
rule.getActions()
.forEach(a -> a.setParams(a.getParams().entrySet()
.stream()
.collect(Collectors
.toMap(Map.Entry::getKey, e -> actionParameterConverter.convertParamFromServiceModel(e.getValue())))
)
);
return rule;
}
public void setNodes(Nodes nodes)
{
this.nodes = nodes;
this.actionService = actionService;
}
public void setRuleService(RuleService ruleService)
@@ -178,19 +182,14 @@ public class RulesImpl implements Rules
this.ruleLoader = ruleLoader;
}
public void setActionParameterConverter(ActionParameterConverter actionParameterConverter)
{
this.actionParameterConverter = actionParameterConverter;
}
public void setActionPermissionValidator(ActionPermissionValidator actionPermissionValidator)
{
this.actionPermissionValidator = actionPermissionValidator;
}
public void setSimpleConditionMapper(
RestModelMapper<SimpleCondition, ActionCondition> simpleConditionMapper)
public void setRuleMapper(
RestModelMapper<Rule, org.alfresco.service.cmr.rule.Rule> ruleMapper)
{
this.simpleConditionMapper = simpleConditionMapper;
this.ruleMapper = ruleMapper;
}
}

View File

@@ -0,0 +1,99 @@
/*
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2021 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.api.model;
import java.util.List;
import org.alfresco.service.Experimental;
/**
* Representation of action parameter constraint.
* Helps to constraint the list of allowable values for an action parameter.
* When action parameter has constraints defined (@see ActionDefinition.ParameterDefinition#getParameterConstraintName())
* they will be listed here.
*
* @author mpichura
*/
@Experimental
public class ActionParameterConstraint
{
/**
* Constraint name.
*/
private String constraintName;
/**
* List of objects representing constraint values along with additional data
*/
private List<ConstraintData> constraintValues;
public List<ConstraintData> getConstraintValues()
{
return constraintValues;
}
public void setConstraintValues(List<ConstraintData> constraintValues)
{
this.constraintValues = constraintValues;
}
public String getConstraintName()
{
return constraintName;
}
public void setConstraintName(String constraintName)
{
this.constraintName = constraintName;
}
public static class ConstraintData
{
public ConstraintData(final String value, final String label)
{
this.value = value;
this.label = label;
}
/**
* Actual constraint value
*/
private String value;
/**
* A label associated to constraint's value
*/
private String label;
public String getValue()
{
return value;
}
public String getLabel()
{
return label;
}
}
}

View File

@@ -284,7 +284,8 @@ public class RepositoryInfo
.setMaxDocs(licenseDescriptor.getMaxDocs())
.setMaxUsers(licenseDescriptor.getMaxUsers())
.setClusterEnabled(licenseDescriptor.isClusterEnabled())
.setCryptodocEnabled(licenseDescriptor.isCryptodocEnabled());
.setCryptodocEnabled(licenseDescriptor.isCryptodocEnabled())
.setCustomEmbeddedWorkflowEnabled(licenseDescriptor.isCustomEmbeddedWorkflowEnabled());
}
public Date getIssuedAt()
@@ -343,6 +344,7 @@ public class RepositoryInfo
private Long maxDocs;
private boolean isClusterEnabled;
private boolean isCryptodocEnabled;
private boolean isCustomEmbeddedWorkflowEnabled;
public LicenseEntitlement()
{
@@ -392,6 +394,17 @@ public class RepositoryInfo
return this;
}
public boolean getIsCustomEmbeddedWorkflowEnabled()
{
return isCustomEmbeddedWorkflowEnabled;
}
public LicenseEntitlement setCustomEmbeddedWorkflowEnabled(boolean customEmbeddedWorkflowEnabled)
{
isCustomEmbeddedWorkflowEnabled = customEmbeddedWorkflowEnabled;
return this;
}
@Override
public String toString()
{
@@ -400,6 +413,7 @@ public class RepositoryInfo
.append(", maxDocs=").append(maxDocs)
.append(", isClusterEnabled=").append(isClusterEnabled)
.append(", isCryptodocEnabled=").append(isCryptodocEnabled)
.append(", isCustomEmbeddedWorkflowEnabled=").append(isCustomEmbeddedWorkflowEnabled)
.append(']');
return sb.toString();
}

View File

@@ -28,6 +28,7 @@ package org.alfresco.rest.api.model.mapper;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.alfresco.service.Experimental;
@@ -36,8 +37,12 @@ import org.apache.commons.lang3.NotImplementedException;
@Experimental
public interface RestModelMapper<R, S>
{
R toRestModel(S serviceModel);
S toServiceModel(R restModel);
default R toRestModel(S serviceModel) {
throw new NotImplementedException();
}
default S toServiceModel(R restModel) {
throw new NotImplementedException();
}
default R toRestModel(Collection<S> serviceModels) {
throw new NotImplementedException();
}
@@ -45,9 +50,21 @@ public interface RestModelMapper<R, S>
throw new NotImplementedException();
}
default List<R> toRestModels(Collection<S> serviceModels) {
return serviceModels.stream().map(this::toRestModel).collect(Collectors.toList());
return serviceModels.stream()
.filter(Objects::nonNull)
.map(this::toRestModel)
.collect(Collectors.toList());
}
default List<S> toServiceModels(Collection<R> restModels) {
return restModels.stream().map(this::toServiceModel).collect(Collectors.toList());
return restModels.stream()
.filter(Objects::nonNull)
.map(this::toServiceModel)
.collect(Collectors.toList());
}
default List<R> toRestModels(S serviceModel) {
throw new NotImplementedException();
}
default List<S> toServiceModels(R restModel) {
throw new NotImplementedException();
}
}

View File

@@ -26,19 +26,11 @@
package org.alfresco.rest.api.model.rules;
import static org.alfresco.repo.action.access.ActionAccessRestriction.ACTION_CONTEXT_PARAM_NAME;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.alfresco.repo.action.ActionImpl;
import org.alfresco.repo.action.CompositeActionImpl;
import org.alfresco.service.Experimental;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.util.GUID;
@Experimental
public class Action
@@ -46,58 +38,6 @@ public class Action
private String actionDefinitionId;
private Map<String, Serializable> params;
/**
* Converts service POJO action to REST model action.
*
* @param actionModel - {@link org.alfresco.service.cmr.action.Action} service POJO
* @return {@link Action} REST model
*/
public static Action from(final org.alfresco.service.cmr.action.Action actionModel)
{
if (actionModel == null)
{
return null;
}
final Action.Builder builder = builder().actionDefinitionId(actionModel.getActionDefinitionName());
if (actionModel.getParameterValues() != null)
{
Map<String, Serializable> params = new HashMap<>(actionModel.getParameterValues());
params.remove(ACTION_CONTEXT_PARAM_NAME);
builder.params(params);
}
return builder.create();
}
/**
* Convert the REST model object to the equivalent service POJO.
*
* @param nodeRef The node reference.
* @return The action service POJO.
*/
public org.alfresco.service.cmr.action.Action toServiceModel(final NodeRef nodeRef)
{
return new ActionImpl(nodeRef, GUID.generate(), this.actionDefinitionId, params);
}
/**
* Convert the REST model objects to composite action service POJO.
*
* @param actions List of actions.
* @return The composite action service POJO.
*/
public static org.alfresco.service.cmr.action.Action toCompositeAction(final List<Action> actions) {
if (actions == null)
{
return null;
}
final org.alfresco.service.cmr.action.CompositeAction compositeAction = new CompositeActionImpl(null, GUID.generate());
actions.forEach(action -> compositeAction.addAction(action.toServiceModel(null)));
return compositeAction;
}
public String getActionDefinitionId()
{
return actionDefinitionId;

View File

@@ -26,16 +26,10 @@
package org.alfresco.rest.api.model.rules;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import org.alfresco.rest.api.model.mapper.RestModelMapper;
import org.alfresco.service.Experimental;
import org.alfresco.service.cmr.action.ActionCondition;
import org.apache.commons.collections.CollectionUtils;
@Experimental
public class CompositeCondition
@@ -45,91 +39,6 @@ public class CompositeCondition
private List<CompositeCondition> compositeConditions;
private List<SimpleCondition> simpleConditions;
/**
* Converts Action conditions (service POJO) list to composite condition (REST model).
*
* @param actionConditions - list of {@link ActionCondition} service POJOs
* @return {@link CompositeCondition} REST model
*/
public static CompositeCondition from(final List<ActionCondition> actionConditions, final RestModelMapper<SimpleCondition, ActionCondition> simpleConditionMapper)
{
if (actionConditions == null)
{
return null;
}
final CompositeCondition conditions = new CompositeCondition();
conditions.compositeConditions = new ArrayList<>();
// group action conditions by inversion flag
actionConditions.stream().filter(Objects::nonNull).collect(Collectors.groupingBy(ActionCondition::getInvertCondition))
// map action condition sub lists
.forEach((inverted, actionConditionsPart) -> Optional.ofNullable(CompositeCondition.ofActionConditions(actionConditionsPart, simpleConditionMapper, inverted, ConditionOperator.AND))
// if composite condition present add to final list
.ifPresent(compositeCondition -> conditions.compositeConditions.add(compositeCondition)));
if (conditions.compositeConditions.isEmpty()) {
conditions.compositeConditions = null;
}
return conditions;
}
private static CompositeCondition ofActionConditions(final List<ActionCondition> actionConditions,
final RestModelMapper<SimpleCondition, ActionCondition> simpleConditionMapper,
final boolean inverted, final ConditionOperator conditionOperator)
{
if (actionConditions == null)
{
return null;
}
return ofSimpleConditions(SimpleCondition.listOf(actionConditions, simpleConditionMapper), inverted, conditionOperator);
}
/**
* Creates a composite condition instance of simple conditions.
*
* @param simpleConditions - list of {@link SimpleCondition}
* @param inverted - determines if condition should be inverted
* @param conditionOperator - determines the operation, see {@link ConditionOperator}
* @return {@link CompositeCondition}
*/
public static CompositeCondition ofSimpleConditions(final List<SimpleCondition> simpleConditions, final boolean inverted, final ConditionOperator conditionOperator)
{
return of(simpleConditions, null, inverted, conditionOperator);
}
private static CompositeCondition of(final List<SimpleCondition> simpleConditions, final List<CompositeCondition> compositeConditions,
final boolean inverted, final ConditionOperator conditionOperator)
{
if (CollectionUtils.isEmpty(simpleConditions) && CollectionUtils.isEmpty(compositeConditions))
{
return null;
}
return builder()
.inverted(inverted)
.booleanMode(conditionOperator)
.simpleConditions(simpleConditions)
.compositeConditions(compositeConditions)
.create();
}
public List<ActionCondition> toServiceModels(final RestModelMapper<SimpleCondition, ActionCondition> simpleConditionMapper)
{
final List<ActionCondition> actionConditions = new ArrayList<>();
if (CollectionUtils.isNotEmpty(simpleConditions))
{
simpleConditions.forEach(simpleCondition -> actionConditions.add(simpleCondition.toServiceModel(inverted, simpleConditionMapper)));
}
if (CollectionUtils.isNotEmpty(compositeConditions))
{
compositeConditions.forEach(compositeCondition -> actionConditions.addAll(compositeCondition.toServiceModels(simpleConditionMapper)));
}
return actionConditions;
}
public boolean isInverted()
{
return inverted;

View File

@@ -30,17 +30,8 @@ import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.alfresco.repo.action.ActionImpl;
import org.alfresco.repo.action.executer.ScriptActionExecuter;
import org.alfresco.rest.api.Nodes;
import org.alfresco.rest.api.model.mapper.RestModelMapper;
import org.alfresco.rest.framework.resource.UniqueId;
import org.alfresco.service.Experimental;
import org.alfresco.service.cmr.action.ActionCondition;
import org.alfresco.service.cmr.action.CompositeAction;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.util.GUID;
@Experimental
public class Rule
@@ -48,93 +39,15 @@ public class Rule
private String id;
private String name;
private String description;
private boolean enabled;
private boolean cascade;
private boolean asynchronous;
private boolean isEnabled;
private boolean isInheritable;
private boolean isAsynchronous;
private Boolean isShared;
private String errorScript;
private List<RuleTrigger> triggers = List.of(RuleTrigger.INBOUND);
private CompositeCondition conditions;
private List<Action> actions;
/**
* Converts service POJO rule to REST model rule.
*
* @param ruleModel - {@link org.alfresco.service.cmr.rule.Rule} service POJO
* @return {@link Rule} REST model
*/
public static Rule from(final org.alfresco.service.cmr.rule.Rule ruleModel, final RestModelMapper<SimpleCondition, ActionCondition> simpleConditionMapper)
{
if (ruleModel == null)
{
return null;
}
final Rule.Builder builder = builder()
.name(ruleModel.getTitle())
.description(ruleModel.getDescription())
.enabled(!ruleModel.getRuleDisabled())
.cascade(ruleModel.isAppliedToChildren())
.asynchronous(ruleModel.getExecuteAsynchronously());
if (ruleModel.getNodeRef() != null) {
builder.id(ruleModel.getNodeRef().getId());
}
if (ruleModel.getRuleTypes() != null)
{
builder.triggers(ruleModel.getRuleTypes().stream().map(RuleTrigger::of).collect(Collectors.toList()));
}
if (ruleModel.getAction() != null)
{
builder.conditions(CompositeCondition.from(ruleModel.getAction().getActionConditions(), simpleConditionMapper));
if (ruleModel.getAction().getCompensatingAction() != null && ruleModel.getAction().getCompensatingAction().getParameterValue(ScriptActionExecuter.PARAM_SCRIPTREF) != null)
{
builder.errorScript(ruleModel.getAction().getCompensatingAction().getParameterValue(ScriptActionExecuter.PARAM_SCRIPTREF).toString());
}
if (ruleModel.getAction() instanceof CompositeAction && ((CompositeAction) ruleModel.getAction()).getActions() != null)
{
builder.actions(((CompositeAction) ruleModel.getAction()).getActions().stream().map(Action::from).collect(Collectors.toList()));
}
}
return builder.create();
}
/**
* Convert the REST model object to the equivalent service POJO.
*
* @param nodes The nodes API.
* @return The rule service POJO.
*/
public org.alfresco.service.cmr.rule.Rule toServiceModel(final Nodes nodes, final RestModelMapper<SimpleCondition, ActionCondition> simpleConditionMapper)
{
final org.alfresco.service.cmr.rule.Rule ruleModel = new org.alfresco.service.cmr.rule.Rule();
final NodeRef nodeRef = (id != null) ? nodes.validateOrLookupNode(id, null) : null;
ruleModel.setNodeRef(nodeRef);
ruleModel.setTitle(name);
ruleModel.setDescription(description);
ruleModel.setRuleDisabled(!enabled);
ruleModel.applyToChildren(cascade);
ruleModel.setExecuteAsynchronously(asynchronous);
if (triggers != null)
{
ruleModel.setRuleTypes(triggers.stream().map(RuleTrigger::getValue).collect(Collectors.toList()));
}
ruleModel.setAction(Action.toCompositeAction(actions));
if (errorScript != null)
{
final org.alfresco.service.cmr.action.Action compensatingAction = new ActionImpl(null, GUID.generate(), ScriptActionExecuter.NAME);
compensatingAction.setParameterValue(ScriptActionExecuter.PARAM_SCRIPTREF, errorScript);
ruleModel.getAction().setCompensatingAction(compensatingAction);
}
if (conditions != null)
{
conditions.toServiceModels(simpleConditionMapper).forEach(condition -> ruleModel.getAction().addActionCondition(condition));
}
return ruleModel;
}
@UniqueId
public String getId()
{
@@ -166,34 +79,34 @@ public class Rule
this.description = description;
}
public boolean isEnabled()
public boolean getIsEnabled()
{
return enabled;
return isEnabled;
}
public void setEnabled(boolean enabled)
public void setIsEnabled(boolean isEnabled)
{
this.enabled = enabled;
this.isEnabled = isEnabled;
}
public boolean isCascade()
public boolean getIsInheritable()
{
return cascade;
return isInheritable;
}
public void setCascade(boolean cascade)
public void setIsInheritable(boolean isInheritable)
{
this.cascade = cascade;
this.isInheritable = isInheritable;
}
public boolean isAsynchronous()
public boolean getIsAsynchronous()
{
return asynchronous;
return isAsynchronous;
}
public void setAsynchronous(boolean asynchronous)
public void setIsAsynchronous(boolean isAsynchronous)
{
this.asynchronous = asynchronous;
this.isAsynchronous = isAsynchronous;
}
public String getErrorScript()
@@ -261,8 +174,8 @@ public class Rule
@Override
public String toString()
{
return "Rule{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", description='" + description + '\'' + ", enabled=" + enabled + ", cascade=" + cascade
+ ", asynchronous=" + asynchronous + ", isShared=" + isShared + ", errorScript='" + errorScript + '\'' + ", triggers=" + triggers + ", conditions=" + conditions
return "Rule{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", description='" + description + '\'' + ", isEnabled=" + isEnabled + ", isInheritable=" + isInheritable
+ ", isAsynchronous=" + isAsynchronous + ", isShared=" + isShared + ", errorScript='" + errorScript + '\'' + ", triggers=" + triggers + ", conditions=" + conditions
+ ", actions=" + actions + '}';
}
@@ -274,9 +187,9 @@ public class Rule
if (o == null || getClass() != o.getClass())
return false;
Rule rule = (Rule) o;
return enabled == rule.enabled
&& cascade == rule.cascade
&& asynchronous == rule.asynchronous
return isEnabled == rule.isEnabled
&& isInheritable == rule.isInheritable
&& isAsynchronous == rule.isAsynchronous
&& Objects.equals(isShared, rule.isShared)
&& Objects.equals(id, rule.id)
&& Objects.equals(name, rule.name)
@@ -290,7 +203,7 @@ public class Rule
@Override
public int hashCode()
{
return Objects.hash(id, name, description, enabled, cascade, asynchronous, isShared, errorScript, triggers, conditions, actions);
return Objects.hash(id, name, description, isEnabled, isInheritable, isAsynchronous, isShared, errorScript, triggers, conditions, actions);
}
public static Builder builder()
@@ -304,9 +217,9 @@ public class Rule
private String id;
private String name;
private String description;
private boolean enabled;
private boolean cascade;
private boolean asynchronous;
private boolean isEnabled;
private boolean isInheritable;
private boolean isAsynchronous;
private Boolean isShared;
private String errorScript;
private List<RuleTrigger> triggers = List.of(RuleTrigger.INBOUND);
@@ -331,21 +244,21 @@ public class Rule
return this;
}
public Builder enabled(boolean enabled)
public Builder isEnabled(boolean isEnabled)
{
this.enabled = enabled;
this.isEnabled = isEnabled;
return this;
}
public Builder cascade(boolean cascade)
public Builder isInheritable(boolean isInheritable)
{
this.cascade = cascade;
this.isInheritable = isInheritable;
return this;
}
public Builder asynchronous(boolean asynchronous)
public Builder isAsynchronous(boolean isAsynchronous)
{
this.asynchronous = asynchronous;
this.isAsynchronous = isAsynchronous;
return this;
}
@@ -385,9 +298,9 @@ public class Rule
rule.setId(id);
rule.setName(name);
rule.setDescription(description);
rule.setEnabled(enabled);
rule.setCascade(cascade);
rule.setAsynchronous(asynchronous);
rule.setIsEnabled(isEnabled);
rule.setIsInheritable(isInheritable);
rule.setIsAsynchronous(isAsynchronous);
rule.setIsShared(isShared);
rule.setErrorScript(errorScript);
rule.setRuleTriggers(triggers);

View File

@@ -0,0 +1,92 @@
/*
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.rest.api.model.rules;
import java.util.Objects;
import org.alfresco.service.Experimental;
@Experimental
public class RuleExecution
{
private boolean eachSubFolderIncluded;
public boolean getIsEachSubFolderIncluded()
{
return eachSubFolderIncluded;
}
public void setIsEachSubFolderIncluded(boolean eachSubFolderIncluded)
{
this.eachSubFolderIncluded = eachSubFolderIncluded;
}
@Override
public String toString()
{
return "RuleExecution{" + "eachSubFolderIncluded=" + eachSubFolderIncluded + '}';
}
@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
RuleExecution that = (RuleExecution) o;
return eachSubFolderIncluded == that.eachSubFolderIncluded;
}
@Override
public int hashCode()
{
return Objects.hash(eachSubFolderIncluded);
}
public static Builder builder()
{
return new Builder();
}
public static class Builder
{
private boolean eachSubFolderIncluded;
public Builder eachSubFolderIncluded(boolean eachSubFolderIncluded)
{
this.eachSubFolderIncluded = eachSubFolderIncluded;
return this;
}
public RuleExecution create()
{
final RuleExecution ruleExecution = new RuleExecution();
ruleExecution.setIsEachSubFolderIncluded(eachSubFolderIncluded);
return ruleExecution;
}
}
}

View File

@@ -44,6 +44,8 @@ public class RuleSet
private List<NodeRef> inheritedBy;
private List<NodeRef> linkedToBy;
private Boolean isInherited;
private Boolean isLinkedTo;
private List<String> ruleIds;
public static RuleSet of(String id)
{
@@ -130,6 +132,36 @@ public class RuleSet
return isInherited;
}
/**
* Set a flag indicating that the rule set is linked to by a folder.
*
* @param isLinkedTo The flag.
*/
public void setIsLinkedTo(Boolean isLinkedTo)
{
this.isLinkedTo = isLinkedTo;
}
/**
* Find if the rule set is linked to by a folder.
*
* @return The value of the flag.
*/
public Boolean getIsLinkedTo()
{
return isLinkedTo;
}
public List<String> getRuleIds()
{
return ruleIds;
}
public void setRuleIds(List<String> ruleIds)
{
this.ruleIds = ruleIds;
}
@Override
public String toString()
{
@@ -141,6 +173,8 @@ public class RuleSet
.add("inheritedBy='" + inheritedBy + "'")
.add("linkedToBy='" + linkedToBy + "'")
.add("isInherited='" + isInherited + "'")
.add("isLinkedTo='" + isLinkedTo + "'")
.add("ruleIds='" + ruleIds + "'")
.toString()
+ '}';
}
@@ -158,13 +192,15 @@ public class RuleSet
&& inclusionType == ruleSet.inclusionType
&& Objects.equals(inheritedBy, ruleSet.inheritedBy)
&& Objects.equals(linkedToBy, ruleSet.linkedToBy)
&& Objects.equals(isInherited, ruleSet.isInherited);
&& Objects.equals(isInherited, ruleSet.isInherited)
&& Objects.equals(isLinkedTo, ruleSet.isLinkedTo)
&& Objects.equals(ruleIds, ruleSet.ruleIds);
}
@Override
public int hashCode()
{
return Objects.hash(id, owningFolder, inclusionType, inheritedBy, linkedToBy, isInherited);
return Objects.hash(id, owningFolder, inclusionType, inheritedBy, linkedToBy, isInherited, isLinkedTo, ruleIds);
}
public static Builder builder()
@@ -180,6 +216,8 @@ public class RuleSet
private List<NodeRef> inheritedBy;
private List<NodeRef> linkedToBy;
private Boolean isInherited;
private Boolean isLinkedTo;
private List<String> ruleIds;
public Builder id(String id)
{
@@ -217,6 +255,18 @@ public class RuleSet
return this;
}
public Builder isLinkedTo(Boolean isLinkedTo)
{
this.isLinkedTo = isLinkedTo;
return this;
}
public Builder ruleIds(List<String> ruleIds)
{
this.ruleIds = ruleIds;
return this;
}
public RuleSet create()
{
final RuleSet ruleSet = new RuleSet();
@@ -226,6 +276,8 @@ public class RuleSet
ruleSet.setInheritedBy(inheritedBy);
ruleSet.setLinkedToBy(linkedToBy);
ruleSet.setIsInherited(isInherited);
ruleSet.setIsLinkedTo(isLinkedTo);
ruleSet.setRuleIds(ruleIds);
return ruleSet;
}
}

View File

@@ -59,49 +59,10 @@ import org.apache.commons.collections.CollectionUtils;
@Experimental
public class SimpleCondition
{
public static final String CATEGORY_INVALID_MSG = "Category in condition is invalid";
public static final String PARAM_CATEGORY = "category";
public static final String PARAM_MIMETYPE = "mimetype";
private String field;
private String comparator;
private String parameter;
/**
* Converts list of service POJO action conditions to list of REST model simple conditions.
*
* @param actionConditions - list of {@link ActionCondition} service POJOs
* @return list of {@link SimpleCondition} REST models
*/
public static List<SimpleCondition> listOf(final List<ActionCondition> actionConditions,
final RestModelMapper<SimpleCondition, ActionCondition> simpleConditionMapper)
{
if (CollectionUtils.isEmpty(actionConditions))
{
return null;
}
return simpleConditionMapper.toRestModels(actionConditions);
}
/**
* Creates simple condition REST model instance from service POJO action condition.
*
* @param actionCondition - {@link ActionCondition} service POJO
* @return {@link SimpleCondition} REST model
*/
public static SimpleCondition from(final ActionCondition actionCondition,
final RestModelMapper<SimpleCondition, ActionCondition> simpleConditionMapper)
{
return simpleConditionMapper.toRestModel(actionCondition);
}
public ActionCondition toServiceModel(final boolean inverted, final RestModelMapper<SimpleCondition, ActionCondition> mapper)
{
final ActionCondition actionCondition = mapper.toServiceModel(this);
actionCondition.setInvertCondition(inverted);
return actionCondition;
}
public String getField()
{
return field;

View File

@@ -0,0 +1,70 @@
/*
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.rest.api.nodes;
import java.util.List;
import org.alfresco.rest.api.Rules;
import org.alfresco.rest.api.model.rules.RuleExecution;
import org.alfresco.rest.framework.resource.RelationshipResource;
import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction;
import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.service.Experimental;
import org.alfresco.util.PropertyCheck;
import org.springframework.beans.factory.InitializingBean;
@Experimental
@RelationshipResource(name = "rule-executions", entityResource = NodesEntityResource.class, title = "Executing rules")
public class NodeRuleExecutionsRelation implements RelationshipResourceAction.Create<RuleExecution>, InitializingBean
{
private final Rules rules;
public NodeRuleExecutionsRelation(Rules rules)
{
this.rules = rules;
}
@Override
public void afterPropertiesSet() throws Exception
{
PropertyCheck.mandatory(this, "rules", this.rules);
}
/**
* Execute rules for given folder node.
*
* @param folderNodeId - the ID of a folder
* @param ruleExecutionParameters - rule execution parameters
* @param parameters - additional request parameters
* @return execution details
*/
@Override
public List<RuleExecution> create(String folderNodeId, List<RuleExecution> ruleExecutionParameters, Parameters parameters)
{
final RuleExecution ruleExecution = ruleExecutionParameters.stream().findFirst().orElse(new RuleExecution());
return List.of(rules.executeRules(folderNodeId, ruleExecution.getIsEachSubFolderIncluded()));
}
}

View File

@@ -35,6 +35,7 @@ import org.alfresco.rest.api.model.rules.RuleSetLink;
import org.alfresco.rest.framework.WebApiDescription;
import org.alfresco.rest.framework.WebApiParam;
import org.alfresco.rest.framework.core.ResourceParameter;
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.Parameters;
@@ -42,8 +43,9 @@ import org.alfresco.util.PropertyCheck;
import org.springframework.beans.factory.InitializingBean;
@RelationshipResource(name = "rule-set-links", entityResource = NodesEntityResource.class, title = "Linking to a rule set")
public class NodeRuleSetLinksRelation implements InitializingBean, RelationshipResourceAction.Create<RuleSetLink>
@RelationshipResource(name = "rule-set-links", entityResource = NodesEntityResource.class, title = "Rule set links")
public class NodeRuleSetLinksRelation implements InitializingBean, RelationshipResourceAction.Create<RuleSetLink>,
RelationshipResourceAction.Delete
{
private final RuleSets ruleSets;
@@ -67,6 +69,24 @@ public class NodeRuleSetLinksRelation implements InitializingBean, RelationshipR
.collect(Collectors.toList());
}
/**
* Remove link between a rule set and a folder for given rule set's and folder's node IDs.
* <p>
* - DELETE /nodes/{folderNodeId}/rule-set-links/{ruleSetId}
*
* @param folderNodeId - folder node ID
* @param ruleSetNodeId - rule set node ID (associated with folder node)
* @throws RelationshipResourceNotFoundException in case resource was not found
*/
@WebApiDescription(title = "Remove link between a rule set and a folder node",
description = "Submits a request to unlink a rule set from a folder",
successStatus = HttpServletResponse.SC_NO_CONTENT)
@Override
public void delete(String folderNodeId, String ruleSetNodeId, Parameters parameters)
{
ruleSets.unlinkRuleSet(folderNodeId, ruleSetNodeId);
}
public NodeRuleSetLinksRelation(RuleSets ruleSets)
{
this.ruleSets = ruleSets;

View File

@@ -28,8 +28,6 @@ package org.alfresco.rest.api.nodes;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import org.alfresco.rest.api.RuleSets;
import org.alfresco.rest.api.model.rules.RuleSet;
import org.alfresco.rest.framework.WebApiDescription;
@@ -37,7 +35,6 @@ import org.alfresco.rest.framework.core.exceptions.RelationshipResourceNotFoundE
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.Paging;
import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.service.Experimental;
import org.alfresco.util.PropertyCheck;
@@ -50,6 +47,7 @@ import org.springframework.beans.factory.InitializingBean;
@RelationshipResource(name = "rule-sets", entityResource = NodesEntityResource.class, title = "Folder node rule sets")
public class NodeRuleSetsRelation implements RelationshipResourceAction.Read<RuleSet>,
RelationshipResourceAction.ReadById<RuleSet>,
RelationshipResourceAction.Update<RuleSet>,
InitializingBean
{
private RuleSets ruleSets;
@@ -106,4 +104,20 @@ public class NodeRuleSetsRelation implements RelationshipResourceAction.Read<Rul
{
this.ruleSets = ruleSets;
}
/**
* Update a rule set, in particular this is useful for reordering rules in a rule set.
* <p>
* - PUT /nodes/{folderNodeId}/rule-sets/{ruleSetId}
*
* @param folderNodeId The id for the folder.
* @param ruleSet The updated rule set.
* @param parameters Contains information about which fields to include in the response.
* @return The updated rule set.
*/
@Override
public RuleSet update(String folderNodeId, RuleSet ruleSet, Parameters parameters)
{
return ruleSets.updateRuleSet(folderNodeId, ruleSet, parameters.getInclude());
}
}

View File

@@ -155,6 +155,7 @@
<entry key="org.alfresco.rest.framework.core.exceptions.PermissionDeniedException" value="#{T(org.springframework.extensions.webscripts.Status).STATUS_FORBIDDEN}" />
<entry key="org.alfresco.repo.security.authority.UnknownAuthorityException" value="#{T(org.springframework.extensions.webscripts.Status).STATUS_NOT_FOUND}" />
<entry key="org.alfresco.repo.security.permissions.AccessDeniedException" value="#{T(org.springframework.extensions.webscripts.Status).STATUS_FORBIDDEN}" />
<entry key="org.alfresco.repo.action.access.ActionAccessException" value="#{T(org.springframework.extensions.webscripts.Status).STATUS_FORBIDDEN}" />
<entry key="org.alfresco.rest.framework.core.exceptions.UnsupportedResourceOperationException" value="#{T(org.springframework.extensions.webscripts.Status).STATUS_METHOD_NOT_ALLOWED}" />
<entry key="org.alfresco.rest.framework.core.exceptions.ConstraintViolatedException" value="#{T(org.springframework.extensions.webscripts.Status).STATUS_CONFLICT}" />
<entry key="org.alfresco.service.cmr.lock.NodeLockedException" value="#{T(org.springframework.extensions.webscripts.Status).STATUS_CONFLICT}" />
@@ -566,6 +567,7 @@
<property name="namespaceService" ref="NamespaceService"/>
<property name="nodeService" ref="NodeService"/>
<property name="prefixResolver" ref="namespaceService"/>
<property name="actionParameterConverter" ref="actionParameterConverter"/>
</bean>
<bean id="Actions" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="org.alfresco.rest.api.Actions"/>
@@ -583,6 +585,9 @@
<bean class="org.alfresco.rest.api.actions.ActionExecutionsEntityResource">
<property name="actions" ref="Actions"/>
</bean>
<bean class="org.alfresco.rest.api.actions.ActionParameterConstraintsEntityResource">
<constructor-arg name="actions" ref="Actions"/>
</bean>
<bean id="Downloads" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
@@ -887,7 +892,7 @@
<bean id="ruleLoader" class="org.alfresco.rest.api.impl.rules.RuleLoader">
<property name="ruleService" ref="RuleService" />
<property name="nodeValidator" ref="nodeValidator" />
<property name="simpleConditionMapper" ref="simpleConditionMapper"/>
<property name="ruleMapper" ref="ruleMapper"/>
</bean>
<bean class="org.alfresco.rest.api.nodes.NodeRuleSetsRelation">
@@ -905,13 +910,12 @@
</bean>
<bean id="rules" class="org.alfresco.rest.api.impl.rules.RulesImpl">
<property name="nodes" ref="Nodes" />
<property name="actionService" ref="ActionService"/>
<property name="validator" ref="nodeValidator"/>
<property name="ruleService" ref="RuleService" />
<property name="ruleLoader" ref="ruleLoader"/>
<property name="actionParameterConverter" ref="actionParameterConverter"/>
<property name="actionPermissionValidator" ref="actionPermissionValidator"/>
<property name="simpleConditionMapper" ref="simpleConditionMapper"/>
<property name="ruleMapper" ref="ruleMapper"/>
</bean>
<bean id="Rules" class="org.springframework.aop.framework.ProxyFactoryBean">
@@ -928,6 +932,10 @@
<constructor-arg name="ruleSets" ref="RuleSets" />
</bean>
<bean class="org.alfresco.rest.api.nodes.NodeRuleExecutionsRelation">
<constructor-arg name="rules" ref="Rules" />
</bean>
<bean id="ruleSettings" class="org.alfresco.rest.api.impl.rules.RuleSettingsImpl">
<property name="validator" ref="nodeValidator" />
<property name="nodeService" ref="NodeService" />
@@ -951,6 +959,20 @@
<constructor-arg name="namespaceService" ref="NamespaceService"/>
<constructor-arg name="nodes" ref="Nodes"/>
</bean>
<bean id="compositeConditionMapper" class="org.alfresco.rest.api.impl.mapper.rules.RestRuleCompositeConditionModelMapper">
<constructor-arg name="simpleConditionMapper" ref="simpleConditionMapper"/>
</bean>
<bean id="actionMapper" class="org.alfresco.rest.api.impl.mapper.rules.RestRuleActionModelMapper">
<constructor-arg name="parameterConverter" ref="actionParameterConverter"/>
</bean>
<bean id="ruleMapper" class="org.alfresco.rest.api.impl.mapper.rules.RestRuleModelMapper">
<constructor-arg name="compositeConditionMapper" ref="compositeConditionMapper"/>
<constructor-arg name="actionMapper" ref="actionMapper"/>
<constructor-arg name="nodes" ref="Nodes"/>
<constructor-arg name="actionParameterConverter" ref="actionParameterConverter"/>
</bean>
<bean id="publicapi.mimeTypePropertyLookup" class="org.alfresco.rest.api.lookups.MimeTypePropertyLookup">
<property name="serviceRegistry" ref="ServiceRegistry"/>

View File

@@ -26,17 +26,16 @@
package org.alfresco.rest.api;
import org.alfresco.rest.api.impl.mapper.rules.RestRuleActionModelMapperTest;
import org.alfresco.rest.api.impl.mapper.rules.RestRuleCompositeConditionModelMapperTest;
import org.alfresco.rest.api.impl.mapper.rules.RestRuleModelMapperTest;
import org.alfresco.rest.api.impl.mapper.rules.RestRuleSimpleConditionModelMapperTest;
import org.alfresco.rest.api.impl.rules.ActionParameterConverterTest;
import org.alfresco.rest.api.impl.rules.ActionPermissionValidatorTest;
import org.alfresco.rest.api.impl.rules.NodeValidatorTest;
import org.alfresco.rest.api.impl.rules.RuleLoaderTest;
import org.alfresco.rest.api.impl.rules.RuleSetsImplTest;
import org.alfresco.rest.api.model.rules.ActionTest;
import org.alfresco.rest.api.model.rules.CompositeConditionTest;
import org.alfresco.rest.api.impl.rules.RulesImplTest;
import org.alfresco.rest.api.model.rules.RuleTest;
import org.alfresco.rest.api.model.rules.SimpleConditionTest;
import org.alfresco.rest.api.nodes.NodeRulesRelationTest;
import org.alfresco.service.Experimental;
import org.junit.runner.RunWith;
@@ -49,14 +48,13 @@ import org.junit.runners.Suite;
RulesImplTest.class,
RuleSetsImplTest.class,
NodeValidatorTest.class,
RuleTest.class,
ActionTest.class,
SimpleConditionTest.class,
CompositeConditionTest.class,
RuleLoaderTest.class,
ActionParameterConverterTest.class,
ActionPermissionValidatorTest.class,
RestRuleSimpleConditionModelMapperTest.class
RestRuleSimpleConditionModelMapperTest.class,
RestRuleCompositeConditionModelMapperTest.class,
RestRuleActionModelMapperTest.class,
RestRuleModelMapperTest.class
})
public class RulesUnitTests
{

View File

@@ -0,0 +1,66 @@
/*
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.rest.api.actions;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import org.alfresco.rest.api.Actions;
import org.alfresco.rest.api.model.ActionParameterConstraint;
import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class ActionParameterConstraintsEntityResourceTest
{
@Mock
private Actions actionsMock;
@Mock
private Parameters parametersMock;
@InjectMocks
private ActionParameterConstraintsEntityResource objectUnderTest;
@Test
public void testReadById() {
final String name = "name";
final ActionParameterConstraint dummyConstraint = new ActionParameterConstraint();
given(actionsMock.getActionConstraint(name)).willReturn(dummyConstraint);
//when
ActionParameterConstraint result = objectUnderTest.readById(name, parametersMock);
then(actionsMock).should().getActionConstraint(name);
then(actionsMock).shouldHaveNoMoreInteractions();
assertThat(result).isNotNull().usingRecursiveComparison().isEqualTo(dummyConstraint);
}
}

View File

@@ -0,0 +1,165 @@
/*
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.rest.api.impl;
import static org.alfresco.rest.api.impl.ActionsImpl.CONSTRAINT_NOT_EXISTS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
import java.util.Map;
import org.alfresco.repo.action.constraint.FolderContentsParameterConstraint;
import org.alfresco.rest.api.impl.rules.ActionParameterConverter;
import org.alfresco.rest.api.model.ActionParameterConstraint;
import org.alfresco.rest.framework.core.exceptions.NotFoundException;
import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.action.ParameterConstraint;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class ActionsImplTest
{
private static final String NAME = "name";
private static final String CONSTRAINT = "constraint";
private static final String LABEL = "label";
private static final String DISPLAY = "display ";
@Mock
private ActionService actionServiceMock;
@Mock
private Parameters parametersMock;
@Mock
private ActionParameterConverter parameterConverterMock;
@InjectMocks
private ActionsImpl objectUnderTest;
@Test
public void testGetSingleActionConstraint()
{
final String name = "name";
final String value = CONSTRAINT;
final String label = LABEL;
final Map<String, String> values = Map.of(value, label);
final ParameterConstraint testConstraint = createTestConstraint(name, values);
given(actionServiceMock.getParameterConstraint(name)).willReturn(testConstraint);
//when
final ActionParameterConstraint actualConstraint = objectUnderTest.getActionConstraint(name);
then(parametersMock).shouldHaveNoInteractions();
then(actionServiceMock).should().getParameterConstraint(name);
then(actionServiceMock).shouldHaveNoMoreInteractions();
assertThat(actualConstraint).isNotNull();
assertThat(actualConstraint.getConstraintName()).isEqualTo(testConstraint.getName());
ActionParameterConstraint.ConstraintData expectedConstraintData = new ActionParameterConstraint.ConstraintData(value, label);
assertThat(actualConstraint.getConstraintValues()).isNotNull().hasSize(1);
ActionParameterConstraint.ConstraintData actualConstraintData = actualConstraint.getConstraintValues().get(0);
assertThat(actualConstraintData).usingRecursiveComparison().isEqualTo(expectedConstraintData);
}
@Test
public void testGetSingleActionNodeConstraint()
{
final String name = "name1";
final String dummyNodeId = "dummy-node-id";
final String value = "workspace://DummyStore/" + dummyNodeId;
final Map<String, String> values = Map.of(value, LABEL);
final FolderContentsParameterConstraint testConstraint = mock(FolderContentsParameterConstraint.class);
given(testConstraint.getName()).willReturn(name);
given(testConstraint.getValues()).willReturn(values);
given(actionServiceMock.getParameterConstraint(name)).willReturn(testConstraint);
given(parameterConverterMock.convertParamFromServiceModel(any())).willReturn(dummyNodeId);
//when
final ActionParameterConstraint actualConstraint = objectUnderTest.getActionConstraint(name);
then(parametersMock).shouldHaveNoInteractions();
then(actionServiceMock).should().getParameterConstraint(name);
then(actionServiceMock).shouldHaveNoMoreInteractions();
assertThat(actualConstraint).isNotNull();
assertThat(actualConstraint.getConstraintName()).isEqualTo(testConstraint.getName());
ActionParameterConstraint.ConstraintData expectedConstraintData = new ActionParameterConstraint.ConstraintData(dummyNodeId, LABEL);
assertThat(actualConstraint.getConstraintValues()).isNotNull().hasSize(1);
ActionParameterConstraint.ConstraintData actualConstraintData = actualConstraint.getConstraintValues().get(0);
assertThat(actualConstraintData).usingRecursiveComparison().isEqualTo(expectedConstraintData);
}
@Test
public void testGetActionConstraintsWithNameFilterNonExistingConstraint()
{
final String name = "name";
given(actionServiceMock.getParameterConstraint(name)).willReturn(null);
//when
assertThatExceptionOfType(NotFoundException.class).isThrownBy(() -> objectUnderTest.getActionConstraint(name))
.withMessageContaining(String.format(CONSTRAINT_NOT_EXISTS, name));
then(parametersMock).shouldHaveNoInteractions();
then(actionServiceMock).should().getParameterConstraint(name);
then(actionServiceMock).shouldHaveNoMoreInteractions();
}
private ParameterConstraint createTestConstraint(final String name, final Map<String, String> values)
{
return new ParameterConstraint()
{
@Override
public String getName()
{
return name;
}
@Override
public boolean isValidValue(String value)
{
return true;
}
@Override
public String getValueDisplayLabel(String value)
{
return DISPLAY + name;
}
@Override
public Map<String, String> getAllowableValues()
{
return values;
}
};
}
}

View File

@@ -0,0 +1,139 @@
/*
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.rest.api.impl.mapper.rules;
import static java.util.Collections.emptyMap;
import static org.alfresco.repo.action.access.ActionAccessRestriction.ACTION_CONTEXT_PARAM_NAME;
import static org.alfresco.repo.action.executer.SetPropertyValueActionExecuter.PARAM_PROPERTY;
import static org.alfresco.repo.action.executer.SetPropertyValueActionExecuter.PARAM_VALUE;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.times;
import java.io.Serializable;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.alfresco.repo.action.ActionImpl;
import org.alfresco.rest.api.impl.rules.ActionParameterConverter;
import org.alfresco.rest.api.model.rules.Action;
import org.alfresco.service.Experimental;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.StoreRef;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@Experimental
@RunWith(MockitoJUnitRunner.class)
public class RestRuleActionModelMapperTest
{
private static final String ACTION_DEFINITION_NAME = "actionDefName";
private static final Map<String, Serializable> parameters =
Map.of(PARAM_PROPERTY, "propertyName", PARAM_VALUE, "propertyValue", ACTION_CONTEXT_PARAM_NAME, "rule");
@Mock
private ActionParameterConverter parameterConverter;
@InjectMocks
private RestRuleActionModelMapper objectUnderTest;
@Test
public void testToRestModel()
{
final NodeRef nodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "ruleId");
final org.alfresco.service.cmr.action.Action actionServiceModel =
new ActionImpl(nodeRef, "actionId", ACTION_DEFINITION_NAME, parameters);
given(parameterConverter.convertParamFromServiceModel(any())).willAnswer(a -> a.getArgument(0));
//when
final Action actualAction = objectUnderTest.toRestModel(actionServiceModel);
then(parameterConverter).should(times(3)).convertParamFromServiceModel(any());
then(parameterConverter).shouldHaveNoMoreInteractions();
final Map<String, Serializable> expectedParameters = Map.of(PARAM_PROPERTY, "propertyName", PARAM_VALUE, "propertyValue");
final Action expectedAction = Action.builder().actionDefinitionId(ACTION_DEFINITION_NAME).params(expectedParameters).create();
assertThat(actualAction).isNotNull().usingRecursiveComparison().isEqualTo(expectedAction);
}
@Test
public void testToRestModelWithNullValues()
{
final org.alfresco.service.cmr.action.Action actionServiceModel = new ActionImpl(null, null, null);
final Action expectedAction = Action.builder().params(emptyMap()).create();
//when
final Action actualAction = objectUnderTest.toRestModel(actionServiceModel);
then(parameterConverter).shouldHaveNoInteractions();
assertThat(actualAction).isNotNull().usingRecursiveComparison().isEqualTo(expectedAction);
}
@Test
public void testToServiceModel() {
final Action action = Action.builder().actionDefinitionId(ACTION_DEFINITION_NAME).params(parameters).create();
final List<Action> actions = List.of(action);
given(parameterConverter.getConvertedParams(parameters, ACTION_DEFINITION_NAME)).willAnswer(a -> a.getArgument(0));
//when
final org.alfresco.service.cmr.action.Action serviceModelAction = objectUnderTest.toServiceModel(actions);
then(parameterConverter).should().getConvertedParams(parameters, ACTION_DEFINITION_NAME);
then(parameterConverter).shouldHaveNoMoreInteractions();
assertThat(serviceModelAction).isNotNull();
}
@Test
public void testToServiceModelFromEmptyActions() {
final List<Action> actions = Collections.emptyList();
//when
final org.alfresco.service.cmr.action.Action serviceModelAction = objectUnderTest.toServiceModel(actions);
then(parameterConverter).shouldHaveNoInteractions();
assertThat(serviceModelAction).isNull();
}
@Test
public void testToServiceModelWithNullParams()
{
final Action action = Action.builder().actionDefinitionId(ACTION_DEFINITION_NAME).params(null).create();
final List<Action> actions = List.of(action);
//when
final org.alfresco.service.cmr.action.Action serviceModelAction = objectUnderTest.toServiceModel(actions);
then(parameterConverter).should().getConvertedParams(emptyMap(), ACTION_DEFINITION_NAME);
then(parameterConverter).shouldHaveNoMoreInteractions();
assertThat(serviceModelAction).isNotNull();
}
}

View File

@@ -0,0 +1,251 @@
/*
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.rest.api.impl.mapper.rules;
import static org.alfresco.repo.action.evaluator.NoConditionEvaluator.NAME;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.IntStream;
import org.alfresco.repo.action.ActionConditionImpl;
import org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator;
import org.alfresco.rest.api.model.mapper.RestModelMapper;
import org.alfresco.rest.api.model.rules.CompositeCondition;
import org.alfresco.rest.api.model.rules.ConditionOperator;
import org.alfresco.rest.api.model.rules.SimpleCondition;
import org.alfresco.service.Experimental;
import org.alfresco.service.cmr.action.ActionCondition;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@Experimental
@RunWith(MockitoJUnitRunner.class)
public class RestRuleCompositeConditionModelMapperTest
{
@Mock
private RestModelMapper<SimpleCondition, ActionCondition> simpleConditionMapperMock;
@InjectMocks
private RestRuleCompositeConditionModelMapper objectUnderTest;
@Test
public void testToRestModel()
{
final List<ActionCondition> actionConditions = List.of(
createActionCondition("value1"),
createActionCondition("value3"),
createActionCondition("value2", true)
);
final List<SimpleCondition> simpleConditions = List.of(
createSimpleCondition("value1"),
createSimpleCondition("value3"),
createSimpleCondition("value2")
);
final CompositeCondition expectedCompositeCondition = createCompositeCondition(List.of(
createCompositeCondition(false, simpleConditions.subList(0,2)),
createCompositeCondition(true, simpleConditions.subList(2,3))
));
given(simpleConditionMapperMock.toRestModels(actionConditions.subList(2,3))).willReturn(simpleConditions.subList(2,3));
given(simpleConditionMapperMock.toRestModels(actionConditions.subList(0,2))).willReturn(simpleConditions.subList(0,2));
// when
final CompositeCondition actualCompositeCondition = objectUnderTest.toRestModel(actionConditions);
assertThat(actualCompositeCondition).isNotNull().usingRecursiveComparison().isEqualTo(expectedCompositeCondition);
}
@Test
public void testToRestModel_fromEmptyList()
{
final List<ActionCondition> actionConditions = Collections.emptyList();
// when
final CompositeCondition actualCompositeCondition = objectUnderTest.toRestModel(actionConditions);
assertThat(actualCompositeCondition).isNull();
}
@Test
public void testToRestModel_fromNullValue()
{
// when
final CompositeCondition actualCompositeCondition = objectUnderTest.toRestModel((Collection<ActionCondition>) null);
assertThat(actualCompositeCondition).isNull();
}
@Test
public void testToRestModel_fromListContainingNullsOnly()
{
final List<ActionCondition> actionConditions = new ArrayList<>();
actionConditions.add(null);
actionConditions.add(null);
// when
final CompositeCondition actualCompositeCondition = objectUnderTest.toRestModel(actionConditions);
assertThat(actualCompositeCondition).isNull();
}
@Test
public void testToRestModel_fromNoCondition()
{
final List<ActionCondition> actionConditions = new ArrayList<>();
final ActionCondition noCondition = new ActionConditionImpl("fake-id", NAME);
actionConditions.add(noCondition);
// when
final CompositeCondition actualCompositeCondition = objectUnderTest.toRestModel(actionConditions);
assertThat(actualCompositeCondition).isNull();
}
@Test
public void testToServiceModels() {
final List<SimpleCondition> simpleConditions = List.of(
createSimpleCondition("value1"),
createSimpleCondition("value3"),
createSimpleCondition("value2")
);
final CompositeCondition compositeCondition = createCompositeCondition(List.of(
createCompositeCondition(false, simpleConditions.subList(0,2)),
createCompositeCondition(true, simpleConditions.subList(2,3))
));
final List<ActionCondition> actionConditions = List.of(
createActionCondition("value1"),
createActionCondition("value3"),
createActionCondition("value2", true)
);
IntStream.rangeClosed(0, 2)
.forEach(i -> given(simpleConditionMapperMock.toServiceModel(simpleConditions.get(i))).willReturn(actionConditions.get(i)));
final List<ActionCondition> actualActionConditions = objectUnderTest.toServiceModels(compositeCondition);
assertThat(actualActionConditions).isEqualTo(actionConditions);
}
@Test
public void testToServiceModels_simpleNonInvertedConditionsOnly() {
final List<SimpleCondition> simpleConditions = List.of(
createSimpleCondition("value1"),
createSimpleCondition("value2"),
createSimpleCondition("value3")
);
final CompositeCondition compositeCondition = createCompositeCondition(false, simpleConditions);
final List<ActionCondition> actionConditions = List.of(
createActionCondition("value1"),
createActionCondition("value2"),
createActionCondition("value3")
);
IntStream.rangeClosed(0, 2)
.forEach(i -> given(simpleConditionMapperMock.toServiceModel(simpleConditions.get(i))).willReturn(actionConditions.get(i)));
final List<ActionCondition> actualActionConditions = objectUnderTest.toServiceModels(compositeCondition);
assertThat(actualActionConditions).isEqualTo(actionConditions);
}
@Test
public void testToServiceModels_nullSimpleConditions() {
final CompositeCondition compositeCondition = createCompositeCondition(false, null);
final List<ActionCondition> actualActionConditions = objectUnderTest.toServiceModels(compositeCondition);
assertThat(actualActionConditions).isNotNull().isEmpty();
}
@Test
public void testToServiceModels_emptyCompositeCondition() {
final CompositeCondition compositeCondition = CompositeCondition.builder().create();
final List<ActionCondition> actualActionConditions = objectUnderTest.toServiceModels(compositeCondition);
assertThat(actualActionConditions).isNotNull().isEmpty();
}
@Test
public void testToServiceModels_nullCompositeCondition() {
final CompositeCondition compositeCondition = null;
final List<ActionCondition> actualActionConditions = objectUnderTest.toServiceModels(compositeCondition);
assertThat(actualActionConditions).isNotNull().isEmpty();
}
private static ActionCondition createActionCondition(final String value)
{
return createActionCondition(value, false);
}
private static ActionCondition createActionCondition(final String value, final boolean inverted)
{
final ActionCondition actionCondition = new ActionConditionImpl("fake-id", ComparePropertyValueEvaluator.NAME);
actionCondition.setInvertCondition(inverted);
final Map<String, Serializable> parameterValues = new HashMap<>();
parameterValues.put(ComparePropertyValueEvaluator.PARAM_CONTENT_PROPERTY, "content-property");
parameterValues.put(ComparePropertyValueEvaluator.PARAM_OPERATION, "operation");
parameterValues.put(ComparePropertyValueEvaluator.PARAM_VALUE, value);
actionCondition.setParameterValues(parameterValues);
return actionCondition;
}
private static SimpleCondition createSimpleCondition(final String value) {
return SimpleCondition.builder()
.field("content-property")
.comparator("operation")
.parameter(value)
.create();
}
private static CompositeCondition createCompositeCondition(final List<CompositeCondition> compositeConditions) {
return createCompositeCondition(false, ConditionOperator.AND, compositeConditions, null);
}
private static CompositeCondition createCompositeCondition(final boolean inverted, final List<SimpleCondition> simpleConditions) {
return createCompositeCondition(inverted, ConditionOperator.AND, null, simpleConditions);
}
private static CompositeCondition createCompositeCondition(final boolean inverted, final ConditionOperator conditionOperator,
final List<CompositeCondition> compositeConditions, final List<SimpleCondition> simpleConditions) {
return CompositeCondition.builder()
.inverted(inverted)
.booleanMode(conditionOperator)
.compositeConditions(compositeConditions)
.simpleConditions(simpleConditions)
.create();
}
}

Some files were not shown because too many files have changed in this diff Show More