Compare commits

...

172 Commits

Author SHA1 Message Date
Tiago Salvado
e63ad92ed9 [MNT-23241] Prevent duplicated default headers based on override flag 2022-09-28 18:29:37 +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
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
108 changed files with 7400 additions and 732 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.130</version>
<version>17.141-SNAPSHOT</version>
</parent>
<modules>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-community-parent</artifactId>
<version>17.130</version>
<version>17.141-SNAPSHOT</version>
</parent>
<modules>

View File

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

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-community-parent</artifactId>
<version>17.130</version>
<version>17.141-SNAPSHOT</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.130</version>
<version>17.141-SNAPSHOT</version>
</parent>
<properties>

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.130</version>
<version>17.141-SNAPSHOT</version>
</parent>
<build>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>17.130</version>
<version>17.141-SNAPSHOT</version>
</parent>
<modules>

View File

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

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>17.130</version>
<version>17.141-SNAPSHOT</version>
</parent>
<dependencies>

View File

@@ -203,7 +203,18 @@ public abstract class AbstractHttpClient implements AlfrescoHttpClient
}
}
/*
* @see AlfrescoHttpClient#setOverrideDefaultHeaders(boolean)
*/
public void setOverrideDefaultHeaders(boolean override)
{
if (httpClient != null)
{
if (httpClient instanceof RequestHeadersHttpClient)
{
((RequestHeadersHttpClient) httpClient).setOverrideDefaultHeaders(override);
}
}
}
}

View File

@@ -27,4 +27,13 @@ public interface AlfrescoHttpClient
*
*/
public void close();
/**
* Allows to override (or not) the default headers that will be included in HTTP requests
*
* @param override
* if true, it will prevent the default headers to be duplicated, otherwise the request may contain
* multiple instances of the same header
*/
public void setOverrideDefaultHeaders(boolean override);
}

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;
@@ -36,6 +37,8 @@ public class RequestHeadersHttpClient extends HttpClient
{
private Map<String, String> defaultHeaders;
private boolean overrideDefaultHeaders = false;
public RequestHeadersHttpClient(MultiThreadedHttpConnectionManager connectionManager)
{
@@ -57,7 +60,14 @@ public class RequestHeadersHttpClient extends HttpClient
if (defaultHeaders != null)
{
defaultHeaders.forEach((k,v) -> {
method.addRequestHeader(k, v);
if (overrideDefaultHeaders)
{
method.setRequestHeader(k, v);
}
else
{
method.addRequestHeader(k, v);
}
});
}
}
@@ -84,4 +94,8 @@ public class RequestHeadersHttpClient extends HttpClient
return super.executeMethod(hostconfig, method, state);
}
public void setOverrideDefaultHeaders(boolean overrideDefaultHeaders)
{
this.overrideDefaultHeaders = overrideDefaultHeaders;
}
}

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>17.130</version>
<version>17.141-SNAPSHOT</version>
</parent>
<properties>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>17.130</version>
<version>17.141-SNAPSHOT</version>
</parent>
<dependencies>

View File

@@ -9,6 +9,6 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-packaging</artifactId>
<version>17.130</version>
<version>17.141-SNAPSHOT</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.130</version>
<version>17.141-SNAPSHOT</version>
</parent>
<properties>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>17.130</version>
<version>17.141-SNAPSHOT</version>
</parent>
<modules>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-packaging</artifactId>
<version>17.130</version>
<version>17.141-SNAPSHOT</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.130</version>
<version>17.141-SNAPSHOT</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.130</version>
<version>17.141-SNAPSHOT</version>
</parent>
<developers>

View File

@@ -9,7 +9,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-tests</artifactId>
<version>17.130</version>
<version>17.141-SNAPSHOT</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.130</version>
<version>17.141-SNAPSHOT</version>
</parent>
<developers>

View File

@@ -363,25 +363,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));
RestRuleModel expectedRuleModel = createRuleModelWithDefaultValues();
expectedRuleModel.setActions(createVariousActions().getActions());
expectedRuleModel.setTriggers(List.of("inbound"));
restClient.assertStatusCodeIs(CREATED);

View File

@@ -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(false)
.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]"));
}
@@ -305,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

@@ -26,6 +26,7 @@
package org.alfresco.rest.rules;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -61,9 +62,9 @@ public class RulesTestsUtils
{
RestRuleModel ruleModel = createRuleModelWithDefaultValues();
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);
@@ -144,6 +145,23 @@ public class RulesTestsUtils
));
}
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);
final RestRuleModel ruleModel = createRuleModelWithDefaultValues();
ruleModel.setActions(Arrays.asList(copyAction, checkOutAction, scriptAction));
return ruleModel;
}
public static RestSimpleConditionDefinitionModel createSimpleCondition(String field, String comparator, String parameter)
{
RestSimpleConditionDefinitionModel simpleCondition = new RestSimpleConditionDefinitionModel();

View File

@@ -33,8 +33,10 @@ 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.createCompositeCondition;
import static org.alfresco.rest.rules.RulesTestsUtils.createCustomActionModel;
import static org.alfresco.rest.rules.RulesTestsUtils.createDefaultActionModel;
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.createSimpleCondition;
import static org.alfresco.rest.rules.RulesTestsUtils.createVariousConditions;
@@ -46,10 +48,13 @@ 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;
@@ -235,7 +240,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");
@@ -287,9 +292,9 @@ 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()
@@ -388,7 +393,6 @@ public class UpdateRulesTests extends RestTest
rule.setConditions(conditions);
restClient.authenticateUser(user).withCoreAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.include(IS_SHARED)
.updateRule(rule.getId(), rule);
restClient.assertStatusCodeIs(BAD_REQUEST);
@@ -409,7 +413,6 @@ public class UpdateRulesTests extends RestTest
rule.setConditions(conditions);
restClient.authenticateUser(user).withCoreAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.include(IS_SHARED)
.updateRule(rule.getId(), rule);
restClient.assertStatusCodeIs(BAD_REQUEST);
@@ -430,7 +433,6 @@ public class UpdateRulesTests extends RestTest
rule.setConditions(conditions);
restClient.authenticateUser(user).withCoreAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.include(IS_SHARED)
.updateRule(rule.getId(), rule);
restClient.assertStatusCodeIs(BAD_REQUEST);
@@ -451,13 +453,81 @@ public class UpdateRulesTests extends RestTest
rule.setConditions(conditions);
restClient.authenticateUser(user).withCoreAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.include(IS_SHARED)
.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");
}
private RestRuleModel createAndSaveRule(String name)
{
return createAndSaveRule(name, List.of(createDefaultActionModel()));

View File

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

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-packaging</artifactId>
<version>17.130</version>
<version>17.141-SNAPSHOT</version>
</parent>
<properties>

16
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.130</version>
<version>17.141-SNAPSHOT</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>
@@ -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.124</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.130</tag>
<tag>HEAD</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.130</version>
<version>17.141-SNAPSHOT</version>
</parent>
<dependencies>

View File

@@ -63,4 +63,9 @@ public interface RuleSets
* 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

@@ -0,0 +1,107 @@
/*
* #%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.access.ActionAccessRestriction.ACTION_CONTEXT_PARAM_NAME;
import java.io.Serializable;
import java.util.Collection;
import java.util.Map;
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> convertedParams =
parameterConverter.getConvertedParams(action.getParams(), action.getActionDefinitionId());
return new ActionImpl(null, GUID.generate(), action.getActionDefinitionId(), convertedParams);
}
}

View File

@@ -26,6 +26,8 @@
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;
@@ -65,10 +67,19 @@ public class RestRuleCompositeConditionModelMapper implements RestModelMapper<Co
{
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
actionConditions.stream().filter(Objects::nonNull).collect(Collectors.groupingBy(ActionCondition::getInvertCondition))
filteredActions.stream()
.collect(Collectors.groupingBy(ActionCondition::getInvertCondition))
// map action condition sub lists
.forEach((inverted, actionConditionsPart) -> Optional
.ofNullable(ofActionConditions(actionConditionsPart, inverted, ConditionOperator.AND))
@@ -113,7 +124,7 @@ public class RestRuleCompositeConditionModelMapper implements RestModelMapper<Co
private CompositeCondition ofActionConditions(final List<ActionCondition> actionConditions, final boolean inverted,
final ConditionOperator conditionOperator)
{
if (actionConditions == null)
if (CollectionUtils.isEmpty(actionConditions))
{
return null;
}

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

@@ -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

@@ -43,11 +43,11 @@ public class RuleLoader
public static final String IS_SHARED = "isShared";
private RuleService ruleService;
private NodeValidator nodeValidator;
private RestModelMapper<CompositeCondition, ActionCondition> compositeConditionMapper;
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, compositeConditionMapper);
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 setCompositeConditionMapper(
RestModelMapper<CompositeCondition, ActionCondition> compositeConditionMapper)
public void setRuleMapper(
RestModelMapper<Rule, org.alfresco.service.cmr.rule.Rule> ruleMapper)
{
this.compositeConditionMapper = compositeConditionMapper;
this.ruleMapper = ruleMapper;
}
}

View File

@@ -41,6 +41,7 @@ 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.repository.AspectMissingException;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.rule.RuleService;
@@ -110,6 +111,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

@@ -58,13 +58,11 @@ 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 RuleService ruleService;
private NodeValidator validator;
private RuleLoader ruleLoader;
private ActionParameterConverter actionParameterConverter;
private ActionPermissionValidator actionPermissionValidator;
private RestModelMapper<CompositeCondition, ActionCondition> compositeConditionMapper;
private RestModelMapper<Rule, org.alfresco.service.cmr.rule.Rule> ruleMapper;
@Override
public CollectionWithPagingInfo<Rule> getRules(final String folderNodeId,
@@ -77,7 +75,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);
@@ -90,7 +88,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
@@ -106,7 +104,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());
}
@@ -138,32 +136,11 @@ public class RulesImpl implements Rules
{
throw new InvalidArgumentException(MUST_HAVE_AT_LEAST_ONE_ACTION);
}
final org.alfresco.service.cmr.rule.Rule serviceModelRule = rule.toServiceModel(nodes, compositeConditionMapper);
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)
{
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;
}
public void setRuleService(RuleService ruleService)
{
this.ruleService = ruleService;
@@ -179,19 +156,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 setCompositeConditionMapper(
RestModelMapper<CompositeCondition, ActionCondition> compositeConditionMapper)
public void setRuleMapper(
RestModelMapper<Rule, org.alfresco.service.cmr.rule.Rule> ruleMapper)
{
this.compositeConditionMapper = compositeConditionMapper;
this.ruleMapper = ruleMapper;
}
}

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;
@@ -49,10 +50,16 @@ 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();

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

@@ -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<CompositeCondition, ActionCondition> compositeConditionMapper)
{
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(compositeConditionMapper.toRestModel(ruleModel.getAction().getActionConditions()));
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<CompositeCondition, ActionCondition> compositeConditionMapper)
{
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)
{
compositeConditionMapper.toServiceModels(conditions).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

@@ -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

@@ -887,7 +887,7 @@
<bean id="ruleLoader" class="org.alfresco.rest.api.impl.rules.RuleLoader">
<property name="ruleService" ref="RuleService" />
<property name="nodeValidator" ref="nodeValidator" />
<property name="compositeConditionMapper" ref="compositeConditionMapper"/>
<property name="ruleMapper" ref="ruleMapper"/>
</bean>
<bean class="org.alfresco.rest.api.nodes.NodeRuleSetsRelation">
@@ -905,13 +905,11 @@
</bean>
<bean id="rules" class="org.alfresco.rest.api.impl.rules.RulesImpl">
<property name="nodes" ref="Nodes" />
<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="compositeConditionMapper" ref="compositeConditionMapper"/>
<property name="ruleMapper" ref="ruleMapper"/>
</bean>
<bean id="Rules" class="org.springframework.aop.framework.ProxyFactoryBean">
@@ -955,6 +953,17 @@
<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"/>
<property name="supported">

View File

@@ -26,16 +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.impl.rules.RulesImplTest;
import org.alfresco.rest.api.model.rules.RuleTest;
import org.alfresco.rest.api.nodes.NodeRulesRelationTest;
import org.alfresco.service.Experimental;
import org.junit.runner.RunWith;
@@ -48,13 +48,13 @@ import org.junit.runners.Suite;
RulesImplTest.class,
RuleSetsImplTest.class,
NodeValidatorTest.class,
RuleTest.class,
ActionTest.class,
RuleLoaderTest.class,
ActionParameterConverterTest.class,
ActionPermissionValidatorTest.class,
RestRuleSimpleConditionModelMapperTest.class,
RestRuleCompositeConditionModelMapperTest.class
RestRuleCompositeConditionModelMapperTest.class,
RestRuleActionModelMapperTest.class,
RestRuleModelMapperTest.class
})
public class RulesUnitTests
{

View File

@@ -0,0 +1,125 @@
/*
* #%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.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(Collections.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();
}
}

View File

@@ -26,8 +26,9 @@
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.Mockito.when;
import static org.mockito.BDDMockito.given;
import java.io.Serializable;
import java.util.ArrayList;
@@ -61,7 +62,7 @@ public class RestRuleCompositeConditionModelMapperTest
private RestModelMapper<SimpleCondition, ActionCondition> simpleConditionMapperMock;
@InjectMocks
RestRuleCompositeConditionModelMapper objectUnderTest;
private RestRuleCompositeConditionModelMapper objectUnderTest;
@Test
public void testToRestModel()
@@ -81,9 +82,8 @@ public class RestRuleCompositeConditionModelMapperTest
createCompositeCondition(false, simpleConditions.subList(0,2)),
createCompositeCondition(true, simpleConditions.subList(2,3))
));
when(simpleConditionMapperMock.toRestModels(actionConditions.subList(0,2))).thenReturn(simpleConditions.subList(0,2));
when(simpleConditionMapperMock.toRestModels(actionConditions.subList(2,3))).thenReturn(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);
@@ -112,16 +112,29 @@ public class RestRuleCompositeConditionModelMapperTest
}
@Test
public void testToRestModel_fromListContainingNull()
public void testToRestModel_fromListContainingNullsOnly()
{
final List<ActionCondition> actionConditions = new ArrayList<>();
actionConditions.add(null);
final CompositeCondition expectedCompositeCondition = CompositeCondition.builder().create();
actionConditions.add(null);
// when
final CompositeCondition actualCompositeCondition = objectUnderTest.toRestModel(actionConditions);
assertThat(actualCompositeCondition).isNotNull().usingRecursiveComparison().isEqualTo(expectedCompositeCondition);
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
@@ -142,7 +155,7 @@ public class RestRuleCompositeConditionModelMapperTest
);
IntStream.rangeClosed(0, 2)
.forEach(i -> when(simpleConditionMapperMock.toServiceModel(simpleConditions.get(i))).thenReturn(actionConditions.get(i)));
.forEach(i -> given(simpleConditionMapperMock.toServiceModel(simpleConditions.get(i))).willReturn(actionConditions.get(i)));
final List<ActionCondition> actualActionConditions = objectUnderTest.toServiceModels(compositeCondition);
assertThat(actualActionConditions).isEqualTo(actionConditions);
@@ -163,7 +176,7 @@ public class RestRuleCompositeConditionModelMapperTest
);
IntStream.rangeClosed(0, 2)
.forEach(i -> when(simpleConditionMapperMock.toServiceModel(simpleConditions.get(i))).thenReturn(actionConditions.get(i)));
.forEach(i -> given(simpleConditionMapperMock.toServiceModel(simpleConditions.get(i))).willReturn(actionConditions.get(i)));
final List<ActionCondition> actualActionConditions = objectUnderTest.toServiceModels(compositeCondition);
assertThat(actualActionConditions).isEqualTo(actionConditions);

View File

@@ -0,0 +1,244 @@
/*
* #%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.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import java.util.List;
import org.alfresco.repo.action.ActionConditionImpl;
import org.alfresco.repo.action.ActionImpl;
import org.alfresco.repo.action.CompositeActionImpl;
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.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.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.rule.RuleType;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@Experimental
@RunWith(MockitoJUnitRunner.class)
public class RestRuleModelMapperTest
{
private static final String RULE_ID = "fake-rule-id";
private static final String RULE_NAME = "rule name";
private static final String RULE_DESCRIPTION = "rule description";
private static final boolean RULE_ENABLED = true;
private static final boolean RULE_INHERITABLE = true;
private static final boolean RULE_ASYNC = true;
private static final String ACTION_DEFINITION_NAME = "action-def-name";
private static final String ERROR_SCRIPT = "error-script-ref";
@Mock
private RestRuleActionModelMapper actionMapperMock;
@Mock
private RestRuleCompositeConditionModelMapper compositeConditionMapperMock;
@Mock
private Nodes nodesMock;
@Mock
private ActionParameterConverter actionParameterConverterMock;
private RestRuleModelMapper objectUnderTest;
@Before
public void setUp()
{
objectUnderTest = new RestRuleModelMapper(compositeConditionMapperMock, actionMapperMock, nodesMock, actionParameterConverterMock);
}
@Test
public void testToRestModel()
{
final org.alfresco.service.cmr.rule.Rule ruleModel = createRuleModel();
given(actionParameterConverterMock.convertParamFromServiceModel(any())).willAnswer(a -> a.getArgument(0));
given(actionMapperMock.toRestModel(createActionModel())).willReturn(createAction());
given(compositeConditionMapperMock.toRestModel(List.of(createConditionModel()))).willReturn(createCondition());
// when
final Rule actualRule = objectUnderTest.toRestModel(ruleModel);
then(compositeConditionMapperMock).should().toRestModel(ruleModel.getAction().getActionConditions());
then(compositeConditionMapperMock).shouldHaveNoMoreInteractions();
then(actionParameterConverterMock).should().convertParamFromServiceModel(ERROR_SCRIPT);
then(actionParameterConverterMock).shouldHaveNoMoreInteractions();
((CompositeAction) ruleModel.getAction()).getActions().forEach(a -> then(actionMapperMock).should().toRestModel(a));
final Rule expectedRule = createRuleWithDefaultValues();
assertThat(actualRule).isNotNull().usingRecursiveComparison().isEqualTo(expectedRule);
}
@Test
public void testToRestModelWithNullValues()
{
final org.alfresco.service.cmr.rule.Rule ruleModel = new org.alfresco.service.cmr.rule.Rule();
final Rule expectedRule = Rule.builder().isEnabled(true).create();
// when
final Rule actualRule = objectUnderTest.toRestModel(ruleModel);
assertThat(actualRule).isNotNull().usingRecursiveComparison().isEqualTo(expectedRule);
}
@Test
public void testToServiceModel()
{
final Rule rule = createRuleWithDefaultValues();
final Action action = Action.builder().actionDefinitionId(ACTION_DEFINITION_NAME).create();
rule.setActions(List.of(action));
final CompositeCondition compositeCondition = CompositeCondition.builder().create();
final org.alfresco.service.cmr.rule.Rule expectedRuleModel = createRuleModel();
rule.setConditions(compositeCondition);
final org.alfresco.service.cmr.action.Action actionModel = createCompositeActionModel();
given(actionMapperMock.toServiceModel(List.of(action))).willReturn(actionModel);
given(compositeConditionMapperMock.toServiceModels(compositeCondition)).willCallRealMethod();
given(actionParameterConverterMock.getConvertedParams(any(), any())).willAnswer(a -> a.getArgument(0));
// when
final org.alfresco.service.cmr.rule.Rule actualRuleModel = objectUnderTest.toServiceModel(rule);
then(nodesMock).should().validateOrLookupNode(RULE_ID, null);
then(nodesMock).shouldHaveNoMoreInteractions();
then(actionMapperMock).should().toServiceModel(List.of(action));
then(actionMapperMock).shouldHaveNoMoreInteractions();
then(compositeConditionMapperMock).should().toServiceModels(compositeCondition);
then(compositeConditionMapperMock).shouldHaveNoMoreInteractions();
assertThat(actualRuleModel)
.isNotNull()
.usingRecursiveComparison().ignoringFields("nodeRef", "action")
.isEqualTo(expectedRuleModel);
assertThat(actualRuleModel.getAction())
.isNotNull();
final org.alfresco.service.cmr.action.Action expectedCompensatingActionModel = createCompensatingActionModel();
assertThat(actualRuleModel.getAction().getCompensatingAction())
.isNotNull()
.usingRecursiveComparison().ignoringFields("id")
.isEqualTo(expectedCompensatingActionModel);
}
@Test
public void testToServiceModel_withNullValues()
{
final Rule rule = new Rule();
final org.alfresco.service.cmr.rule.Rule expectedRuleModel = new org.alfresco.service.cmr.rule.Rule();
expectedRuleModel.setRuleDisabled(true);
// when
final org.alfresco.service.cmr.rule.Rule actualRuleModel = objectUnderTest.toServiceModel(rule);
then(nodesMock).shouldHaveNoInteractions();
assertThat(actualRuleModel)
.isNotNull()
.usingRecursiveComparison()
.ignoringFields("ruleTypes")
.isEqualTo(expectedRuleModel);
}
private Rule createRuleWithDefaultValues()
{
return Rule.builder()
.id(RULE_ID)
.name(RULE_NAME)
.description(RULE_DESCRIPTION)
.isEnabled(RULE_ENABLED)
.isInheritable(RULE_INHERITABLE)
.isAsynchronous(RULE_ASYNC)
.triggers(List.of(RuleTrigger.INBOUND, RuleTrigger.UPDATE))
.errorScript(ERROR_SCRIPT)
.actions(List.of(createAction()))
.conditions(createCondition())
.create();
}
private CompositeCondition createCondition()
{
return CompositeCondition.builder().create();
}
private Action createAction() {
return Action.builder().actionDefinitionId(ACTION_DEFINITION_NAME).create();
}
private static org.alfresco.service.cmr.rule.Rule createRuleModel()
{
final NodeRef nodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, RULE_ID);
final org.alfresco.service.cmr.rule.Rule ruleModel = new org.alfresco.service.cmr.rule.Rule(nodeRef);
ruleModel.setTitle(RULE_NAME);
ruleModel.setDescription(RULE_DESCRIPTION);
ruleModel.setRuleDisabled(!RULE_ENABLED);
ruleModel.applyToChildren(RULE_INHERITABLE);
ruleModel.setExecuteAsynchronously(RULE_ASYNC);
ruleModel.setRuleTypes(List.of(RuleType.INBOUND, RuleType.UPDATE));
ruleModel.setAction(createCompositeActionModel());
return ruleModel;
}
private static org.alfresco.service.cmr.action.Action createCompositeActionModel()
{
final ActionCondition actionCondition = createConditionModel();
final org.alfresco.service.cmr.action.CompositeAction compositeActionModel = new CompositeActionImpl(null, "composite-action");
compositeActionModel.addAction(createActionModel());
compositeActionModel.setCompensatingAction(createCompensatingActionModel());
compositeActionModel.addActionCondition(actionCondition);
return compositeActionModel;
}
private static ActionConditionImpl createConditionModel()
{
return new ActionConditionImpl("action-condition-id", "action-condition-def-name");
}
private static ActionImpl createActionModel()
{
return new ActionImpl(null, "action-id", ACTION_DEFINITION_NAME);
}
private static org.alfresco.service.cmr.action.Action createCompensatingActionModel()
{
final org.alfresco.service.cmr.action.Action compensatingActionModel =
new ActionImpl(null, "compensating-action-id", ScriptActionExecuter.NAME);
compensatingActionModel.setParameterValue(ScriptActionExecuter.PARAM_SCRIPTREF, ERROR_SCRIPT);
return compensatingActionModel;
}
}

View File

@@ -176,7 +176,7 @@ public class RestRuleSimpleConditionModelMapperTest
// when
final List<SimpleCondition> actualSimpleConditions = objectUnderTest.toRestModels(actionConditions);
assertThat(actualSimpleConditions).hasSize(1).containsOnlyNulls();
assertThat(actualSimpleConditions).isEmpty();
}
@Test

View File

@@ -33,19 +33,22 @@ import static org.alfresco.rest.api.model.rules.RuleTrigger.UPDATE;
import static org.alfresco.service.cmr.repository.StoreRef.STORE_REF_WORKSPACE_SPACESSTORE;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.MockitoAnnotations.openMocks;
import static org.mockito.BDDMockito.then;
import java.util.List;
import org.alfresco.rest.api.model.mapper.RestModelMapper;
import org.alfresco.rest.api.model.rules.Rule;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.rule.RuleService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
/** Unit tests for {@link RuleLoader}. */
@RunWith(MockitoJUnitRunner.class)
public class RuleLoaderTest
{
private static final String NODE_ID = "node-id";
@@ -53,36 +56,38 @@ public class RuleLoaderTest
private static final String TITLE = "title";
private static final String DESCRIPTION = "description";
private static final boolean ENABLED = true;
private static final boolean CASCADE = true;
private static final boolean INHERITABLE = true;
private static final boolean EXECUTE_ASYNCHRONOUSLY = false;
private static final List<String> TRIGGERS = List.of("update", "outbound");
private static final NodeRef RULE_SET_NODE = new NodeRef("rule://set/");
@InjectMocks
private RuleLoader ruleLoader;
@Mock
private RuleService ruleServiceMock;
@Mock
private NodeValidator nodeValidatorMock;
@Mock
private RestModelMapper<Rule, org.alfresco.service.cmr.rule.Rule> ruleMapperMock;
private org.alfresco.service.cmr.rule.Rule serviceRule = createServiceRule();
@Before
public void setUp()
{
openMocks(this);
}
@InjectMocks
private RuleLoader ruleLoader;
@Test
public void testLoadRule()
{
final Rule restModelRule = getRestModelRule();
given(ruleMapperMock.toRestModel(serviceRule)).willReturn(restModelRule);
//when
Rule rule = ruleLoader.loadRule(serviceRule, emptyList());
Rule expected = Rule.builder().id(NODE_ID)
.name(TITLE)
.description(DESCRIPTION)
.enabled(ENABLED)
.cascade(CASCADE)
.asynchronous(EXECUTE_ASYNCHRONOUSLY)
.isEnabled(ENABLED)
.isInheritable(INHERITABLE)
.isAsynchronous(EXECUTE_ASYNCHRONOUSLY)
.triggers(List.of(UPDATE, OUTBOUND)).create();
assertThat(rule).isEqualTo(expected);
}
@@ -90,18 +95,30 @@ public class RuleLoaderTest
@Test
public void testLoadRule_noExceptionWithNullInclude()
{
//when
ruleLoader.loadRule(serviceRule, null);
then(ruleMapperMock).should().toRestModel(serviceRule);
then(ruleMapperMock).shouldHaveNoMoreInteractions();
}
@Test
public void testLoadRule_includeIsShared()
{
// Simulate the rule set being shared.
final Rule restModelRule = getRestModelRule();
given(ruleServiceMock.getRuleSetNode(NODE_REF)).willReturn(RULE_SET_NODE);
given(nodeValidatorMock.isRuleSetNotNullAndShared(RULE_SET_NODE)).willReturn(true);
given(ruleMapperMock.toRestModel(serviceRule)).willReturn(restModelRule);
Rule rule = ruleLoader.loadRule(serviceRule, List.of(IS_SHARED));
then(ruleMapperMock).should().toRestModel(serviceRule);
then(ruleMapperMock).shouldHaveNoMoreInteractions();
then(ruleServiceMock).should().getRuleSetNode(NODE_REF);
then(ruleServiceMock).shouldHaveNoMoreInteractions();
then(nodeValidatorMock).should().isRuleSetNotNullAndShared(RULE_SET_NODE);
then(nodeValidatorMock).shouldHaveNoMoreInteractions();
assertThat(rule).extracting("isShared").isEqualTo(true);
}
@@ -112,10 +129,21 @@ public class RuleLoaderTest
rule.setTitle(TITLE);
rule.setDescription(DESCRIPTION);
rule.setRuleDisabled(!ENABLED);
rule.applyToChildren(CASCADE);
rule.applyToChildren(INHERITABLE);
rule.setExecuteAsynchronously(EXECUTE_ASYNCHRONOUSLY);
rule.setRuleTypes(TRIGGERS);
return rule;
}
private Rule getRestModelRule()
{
return Rule.builder().id(NODE_ID)
.name(TITLE)
.description(DESCRIPTION)
.isEnabled(ENABLED)
.isInheritable(INHERITABLE)
.isAsynchronous(EXECUTE_ASYNCHRONOUSLY)
.triggers(List.of(UPDATE, OUTBOUND)).create();
}
}

View File

@@ -46,6 +46,7 @@ import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Paging;
import org.alfresco.service.Experimental;
import org.alfresco.service.cmr.repository.AspectMissingException;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
@@ -294,4 +295,38 @@ public class RuleSetsImplTest extends TestCase
then(ruleServiceMock).shouldHaveNoMoreInteractions();
then(runtimeRuleServiceMock).shouldHaveNoInteractions();
}
@Test
public void testUnlinkRuleSet()
{
given(ruleServiceMock.isLinkedToRuleNode(FOLDER_NODE)).willReturn(true);
//when
ruleSets.unlinkRuleSet(FOLDER_ID,RULE_SET_ID);
then(nodeValidatorMock).should().validateFolderNode(FOLDER_ID,true);
then(nodeValidatorMock).should().validateRuleSetNode(RULE_SET_ID,FOLDER_NODE);
then(nodeValidatorMock).shouldHaveNoMoreInteractions();
then(ruleServiceMock).should().isLinkedToRuleNode(FOLDER_NODE);
then(ruleServiceMock).shouldHaveNoMoreInteractions();
then(nodeServiceMock).should().removeAspect(FOLDER_NODE,RuleModel.ASPECT_RULES);
then(nodeServiceMock).shouldHaveNoMoreInteractions();
}
@Test
public void testUnlinkRuleSet_folderIsNotLinkedToRuleSet()
{
given(ruleServiceMock.isLinkedToRuleNode(FOLDER_NODE)).willReturn(false);
//when
assertThatExceptionOfType(InvalidArgumentException.class).isThrownBy(
() -> ruleSets.unlinkRuleSet(FOLDER_ID,RULE_SET_ID));
then(nodeValidatorMock).should().validateFolderNode(FOLDER_ID,true);
then(nodeValidatorMock).should().validateRuleSetNode(RULE_SET_ID,FOLDER_NODE);
then(nodeValidatorMock).shouldHaveNoMoreInteractions();
then(ruleServiceMock).should().isLinkedToRuleNode(FOLDER_NODE);
then(ruleServiceMock).shouldHaveNoMoreInteractions();
then(nodeServiceMock).shouldHaveNoInteractions();
}
}

View File

@@ -36,24 +36,17 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.IntStream;
import junit.framework.TestCase;
import org.alfresco.repo.action.ActionImpl;
import org.alfresco.repo.action.CompositeActionImpl;
import org.alfresco.rest.api.Nodes;
import org.alfresco.rest.api.model.rules.Action;
import org.alfresco.rest.api.model.mapper.RestModelMapper;
import org.alfresco.rest.api.model.rules.CompositeCondition;
import org.alfresco.rest.api.model.rules.Action;
import org.alfresco.rest.api.model.rules.Rule;
import org.alfresco.rest.api.model.rules.SimpleCondition;
import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException;
@@ -61,8 +54,6 @@ import org.alfresco.rest.framework.core.exceptions.RelationshipResourceNotFoundE
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
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.repository.NodeRef;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.rule.RuleService;
@@ -86,13 +77,11 @@ public class RulesImplTest extends TestCase
private static final NodeRef RULE_NODE_REF = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, RULE_ID);
private static final Paging PAGING = Paging.DEFAULT;
private static final List<String> INCLUDE = emptyList();
private static final String ACTION_DEFINITION_NAME = "actionDefinitionName";
private static final Map<String, Serializable> DUMMY_PARAMS = Map.of("dummy-key", "dummy-value");
@Mock
private Nodes nodesMock;
@Mock
private RestModelMapper<CompositeCondition, ActionCondition> compositeConditionMapperMock;
private RestModelMapper<Rule, org.alfresco.service.cmr.rule.Rule> ruleMapper;
@Mock
private NodeValidator nodeValidatorMock;
@Mock
@@ -100,8 +89,6 @@ public class RulesImplTest extends TestCase
@Mock
private RuleLoader ruleLoaderMock;
@Mock
private ActionParameterConverter actionParameterConverterMock;
@Mock
private ActionPermissionValidator actionPermissionValidatorMock;
@Mock
private org.alfresco.service.cmr.rule.Rule serviceRuleMock;
@@ -111,7 +98,6 @@ public class RulesImplTest extends TestCase
private Action actionMock;
private org.alfresco.service.cmr.rule.Rule ruleModel = createRule(RULE_ID);
private CompositeAction compositeAction = new CompositeActionImpl(RULE_NODE_REF, "compositeActionId");
@InjectMocks
private RulesImpl rules;
@@ -129,8 +115,6 @@ public class RulesImplTest extends TestCase
given(ruleServiceMock.getOwningNodeRef(RULE_SET_NODE_REF)).willReturn(FOLDER_NODE_REF);
given(ruleLoaderMock.loadRule(ruleModel, INCLUDE)).willReturn(ruleMock);
compositeAction.addAction(new ActionImpl(FOLDER_NODE_REF, "actionId", ACTION_DEFINITION_NAME, DUMMY_PARAMS));
}
@Test
@@ -298,9 +282,8 @@ public class RulesImplTest extends TestCase
public void testCreateRules()
{
List<Rule> ruleList = List.of(ruleMock);
given(ruleMock.toServiceModel(nodesMock, compositeConditionMapperMock)).willReturn(serviceRuleMock);
given(ruleMapper.toServiceModel(ruleMock)).willReturn(serviceRuleMock);
given(ruleMock.getActions()).willReturn(List.of(actionMock));
given(serviceRuleMock.getAction()).willReturn(compositeAction);
given(ruleServiceMock.saveRule(FOLDER_NODE_REF, serviceRuleMock)).willAnswer(arg -> arg.getArguments()[1]);
given(ruleLoaderMock.loadRule(serviceRuleMock, INCLUDE)).willReturn(ruleMock);
given(actionPermissionValidatorMock.validateRulePermissions(any())).willAnswer(arg -> arg.getArguments()[0]);
@@ -311,11 +294,9 @@ public class RulesImplTest extends TestCase
then(nodeValidatorMock).should().validateFolderNode(FOLDER_NODE_ID, true);
then(nodeValidatorMock).should().validateRuleSetNode(RULE_SET_ID, FOLDER_NODE_REF);
then(nodeValidatorMock).shouldHaveNoMoreInteractions();
then(actionParameterConverterMock).should().getConvertedParams(DUMMY_PARAMS, ACTION_DEFINITION_NAME);
then(actionParameterConverterMock).shouldHaveNoMoreInteractions();
then(actionPermissionValidatorMock).should().validateRulePermissions(serviceRuleMock);
then(actionPermissionValidatorMock).shouldHaveNoMoreInteractions();
then(ruleServiceMock).should().saveRule(FOLDER_NODE_REF, ruleMock.toServiceModel(nodesMock, compositeConditionMapperMock));
then(ruleServiceMock).should().saveRule(FOLDER_NODE_REF, serviceRuleMock);
then(ruleServiceMock).shouldHaveNoMoreInteractions();
List<Rule> expected = List.of(ruleMock);
assertThat(actual).isEqualTo(expected);
@@ -328,11 +309,10 @@ public class RulesImplTest extends TestCase
public void testCreateRules_defaultRuleSet()
{
List<Rule> ruleList = List.of(ruleMock);
given(ruleMock.toServiceModel(nodesMock, compositeConditionMapperMock)).willReturn(serviceRuleMock);
given(ruleMapper.toServiceModel(ruleMock)).willReturn(serviceRuleMock);
given(ruleMock.getActions()).willReturn(List.of(actionMock));
given(ruleServiceMock.saveRule(FOLDER_NODE_REF, serviceRuleMock)).willAnswer(arg -> arg.getArguments()[1]);
given(ruleLoaderMock.loadRule(serviceRuleMock, INCLUDE)).willReturn(ruleMock);
given(serviceRuleMock.getAction()).willReturn(compositeAction);
given(actionPermissionValidatorMock.validateRulePermissions(any())).willAnswer(arg -> arg.getArguments()[0]);
// when
@@ -340,11 +320,9 @@ public class RulesImplTest extends TestCase
then(nodeValidatorMock).should().validateFolderNode(FOLDER_NODE_ID, true);
then(nodeValidatorMock).shouldHaveNoMoreInteractions();
then(actionParameterConverterMock).should().getConvertedParams(DUMMY_PARAMS, ACTION_DEFINITION_NAME);
then(actionParameterConverterMock).shouldHaveNoMoreInteractions();
then(actionPermissionValidatorMock).should().validateRulePermissions(serviceRuleMock);
then(actionPermissionValidatorMock).shouldHaveNoMoreInteractions();
then(ruleServiceMock).should().saveRule(FOLDER_NODE_REF, ruleMock.toServiceModel(nodesMock, compositeConditionMapperMock));
then(ruleServiceMock).should().saveRule(FOLDER_NODE_REF, serviceRuleMock);
then(ruleServiceMock).shouldHaveNoMoreInteractions();
List<Rule> expected = List.of(ruleMock);
assertThat(actual).isEqualTo(expected);
@@ -375,10 +353,7 @@ public class RulesImplTest extends TestCase
given(ruleBodyMock.getActions()).willReturn(List.of(actionMock));
ruleBodyList.add(ruleBodyMock);
org.alfresco.service.cmr.rule.Rule serviceRuleMockInner = mock(org.alfresco.service.cmr.rule.Rule.class);
given(ruleBodyMock.toServiceModel(nodesMock, compositeConditionMapperMock)).willReturn(serviceRuleMockInner);
final CompositeAction compositeActionInner = new CompositeActionImpl(RULE_NODE_REF, "compositeActionInnerId");
compositeActionInner.addAction(new ActionImpl(FOLDER_NODE_REF, "actionInnerId", ACTION_DEFINITION_NAME, DUMMY_PARAMS));
given(serviceRuleMockInner.getAction()).willReturn(compositeActionInner);
given(ruleMapper.toServiceModel(ruleBodyMock)).willReturn(serviceRuleMockInner);
given(ruleServiceMock.saveRule(FOLDER_NODE_REF, serviceRuleMockInner)).willAnswer(arg -> arg.getArguments()[1]);
Rule ruleMockInner = mock(Rule.class);
given(ruleLoaderMock.loadRule(serviceRuleMockInner, INCLUDE)).willReturn(ruleMockInner);
@@ -394,12 +369,9 @@ public class RulesImplTest extends TestCase
then(nodeValidatorMock).shouldHaveNoMoreInteractions();
for (Rule ruleBody : ruleBodyList)
{
then(actionPermissionValidatorMock).should().validateRulePermissions(ruleBody.toServiceModel(nodesMock,
compositeConditionMapperMock));
then(ruleServiceMock).should().saveRule(FOLDER_NODE_REF, ruleBody.toServiceModel(nodesMock, compositeConditionMapperMock));
then(actionPermissionValidatorMock).should().validateRulePermissions(ruleMapper.toServiceModel(ruleBody));
then(ruleServiceMock).should().saveRule(FOLDER_NODE_REF, ruleMapper.toServiceModel(ruleBody));
}
then(actionParameterConverterMock).should(times(3)).getConvertedParams(DUMMY_PARAMS, ACTION_DEFINITION_NAME);
then(actionParameterConverterMock).shouldHaveNoMoreInteractions();
then(actionPermissionValidatorMock).shouldHaveNoMoreInteractions();
then(ruleServiceMock).shouldHaveNoMoreInteractions();
assertThat(actual).isEqualTo(expected);
@@ -459,7 +431,6 @@ public class RulesImplTest extends TestCase
then(nodeValidatorMock).should().validateFolderNode(FOLDER_NODE_ID, true);
then(nodeValidatorMock).should().validateRuleSetNode(RULE_SET_ID, FOLDER_NODE_REF);
then(nodeValidatorMock).shouldHaveNoMoreInteractions();
then(actionParameterConverterMock).shouldHaveNoInteractions();
then(actionPermissionValidatorMock).shouldHaveNoInteractions();
then(ruleServiceMock).shouldHaveNoInteractions();
}
@@ -470,10 +441,9 @@ public class RulesImplTest extends TestCase
@Test
public void testUpdateRuleById()
{
given(ruleMock.toServiceModel(nodesMock, compositeConditionMapperMock)).willReturn(serviceRuleMock);
given(ruleMapper.toServiceModel(ruleMock)).willReturn(serviceRuleMock);
given(ruleMock.getActions()).willReturn(List.of(actionMock));
given(ruleServiceMock.saveRule(FOLDER_NODE_REF, serviceRuleMock)).willAnswer(a -> a.getArguments()[1]);
given(serviceRuleMock.getAction()).willReturn(compositeAction);
given(ruleLoaderMock.loadRule(serviceRuleMock, INCLUDE)).willReturn(ruleMock);
given(actionPermissionValidatorMock.validateRulePermissions(any())).willAnswer(arg -> arg.getArguments()[0]);
@@ -486,8 +456,6 @@ public class RulesImplTest extends TestCase
then(nodeValidatorMock).shouldHaveNoMoreInteractions();
then(ruleServiceMock).should().saveRule(FOLDER_NODE_REF, serviceRuleMock);
then(ruleServiceMock).shouldHaveNoMoreInteractions();
then(actionParameterConverterMock).should().getConvertedParams(DUMMY_PARAMS, ACTION_DEFINITION_NAME);
then(actionParameterConverterMock).shouldHaveNoMoreInteractions();
then(actionPermissionValidatorMock).should().validateRulePermissions(serviceRuleMock);
then(actionPermissionValidatorMock).shouldHaveNoMoreInteractions();
assertThat(updatedRule).isEqualTo(ruleMock);
@@ -571,7 +539,6 @@ public class RulesImplTest extends TestCase
then(nodeValidatorMock).shouldHaveNoMoreInteractions();
then(ruleServiceMock).shouldHaveNoInteractions();
then(actionParameterConverterMock).shouldHaveNoInteractions();
then(actionPermissionValidatorMock).shouldHaveNoInteractions();
}

View File

@@ -1,79 +0,0 @@
/*
* #%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 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 java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.alfresco.repo.action.ActionImpl;
import org.alfresco.service.Experimental;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.StoreRef;
import org.junit.Test;
@Experimental
public class ActionTest
{
private static final String ACTION_DEFINITION_NAME = "actionDefName";
private static final Map<String, Serializable> parameters = new HashMap<>();
static
{
parameters.put(PARAM_PROPERTY, "propertyName");
parameters.put(PARAM_VALUE, "propertyValue");
}
@Test
public void testFrom()
{
final NodeRef nodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "ruleId");
final org.alfresco.service.cmr.action.Action actionModel = new ActionImpl(nodeRef, "actionId", ACTION_DEFINITION_NAME, parameters);
final Action expectedAction = Action.builder().actionDefinitionId(ACTION_DEFINITION_NAME).params(parameters).create();
final Action actualAction = Action.from(actionModel);
assertThat(actualAction).isNotNull().usingRecursiveComparison().isEqualTo(expectedAction);
}
@Test
public void testFromActionModelWithNullValues()
{
final org.alfresco.service.cmr.action.Action actionModel = new ActionImpl(null, null, null);
final Action expectedAction = Action.builder().params(Collections.emptyMap()).create();
final Action actualAction = Action.from(actionModel);
assertThat(actualAction).isNotNull().usingRecursiveComparison().isEqualTo(expectedAction);
}
}

View File

@@ -1,182 +0,0 @@
/*
* #%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 static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
import java.util.Collections;
import java.util.List;
import org.alfresco.repo.action.ActionConditionImpl;
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.mapper.rules.RestRuleCompositeConditionModelMapper;
import org.alfresco.rest.api.impl.mapper.rules.RestRuleSimpleConditionModelMapper;
import org.alfresco.rest.api.model.mapper.RestModelMapper;
import org.alfresco.service.Experimental;
import org.alfresco.service.cmr.action.ActionCondition;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.rule.RuleType;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
@Experimental
@RunWith(MockitoJUnitRunner.class)
public class RuleTest
{
private static final String RULE_ID = "fake-rule-id";
private static final String RULE_NAME = "rule name";
private static final String RULE_DESCRIPTION = "rule description";
private static final boolean RULE_ENABLED = true;
private static final boolean RULE_CASCADE = true;
private static final boolean RULE_ASYNC = true;
private static final boolean RULE_SHARED = true;
private static final String ACTION_DEFINITION_NAME = "action-def-name";
private static final String ERROR_SCRIPT = "error-script-ref";
private final RestModelMapper<CompositeCondition, ActionCondition> compositeConditionMapper = mock(RestRuleCompositeConditionModelMapper.class);
@Test
public void testFrom()
{
final org.alfresco.service.cmr.rule.Rule ruleModel = createRuleModel();
final Rule expectedRule = createRuleWithDefaultValues();
// when
final Rule actualRule = Rule.from(ruleModel, compositeConditionMapper);
assertThat(actualRule).isNotNull().usingRecursiveComparison().isEqualTo(expectedRule);
}
@Test
public void testFromRuleModelWithNullValues()
{
final org.alfresco.service.cmr.rule.Rule ruleModel = new org.alfresco.service.cmr.rule.Rule();
final Rule expectedRule = Rule.builder().enabled(true).create();
// when
final Rule actualRule = Rule.from(ruleModel, compositeConditionMapper);
assertThat(actualRule).isNotNull().usingRecursiveComparison().isEqualTo(expectedRule);
}
@Test
public void testToServiceModel()
{
final Nodes nodesMock = mock(Nodes.class);
final Rule rule = createRuleWithDefaultValues();
rule.setActions(List.of(Action.builder().actionDefinitionId(ACTION_DEFINITION_NAME).create()));
final org.alfresco.service.cmr.rule.Rule expectedRuleModel = createRuleModel();
final org.alfresco.service.cmr.action.Action expectedCompensatingActionModel = createCompensatingActionModel();
// when
final org.alfresco.service.cmr.rule.Rule actualRuleModel = rule.toServiceModel(nodesMock, compositeConditionMapper);
then(nodesMock).should().validateOrLookupNode(RULE_ID, null);
then(nodesMock).shouldHaveNoMoreInteractions();
assertThat(actualRuleModel)
.isNotNull()
.usingRecursiveComparison().ignoringFields("nodeRef", "action")
.isEqualTo(expectedRuleModel);
assertThat(actualRuleModel.getAction())
.isNotNull();
assertThat(actualRuleModel.getAction().getCompensatingAction())
.isNotNull()
.usingRecursiveComparison().ignoringFields("id")
.isEqualTo(expectedCompensatingActionModel);
}
@Test
public void testToServiceModel_withNullValues()
{
final Nodes nodesMock = mock(Nodes.class);
final Rule rule = new Rule();
final org.alfresco.service.cmr.rule.Rule expectedRuleModel = new org.alfresco.service.cmr.rule.Rule();
expectedRuleModel.setRuleDisabled(true);
// when
final org.alfresco.service.cmr.rule.Rule actualRuleModel = rule.toServiceModel(nodesMock, compositeConditionMapper);
then(nodesMock).shouldHaveNoInteractions();
assertThat(actualRuleModel)
.isNotNull()
.usingRecursiveComparison()
.ignoringFields("ruleTypes")
.isEqualTo(expectedRuleModel);
}
private Rule createRuleWithDefaultValues() {
return Rule.builder()
.id(RULE_ID)
.name(RULE_NAME)
.description(RULE_DESCRIPTION)
.enabled(RULE_ENABLED)
.cascade(RULE_CASCADE)
.asynchronous(RULE_ASYNC)
.triggers(List.of(RuleTrigger.INBOUND, RuleTrigger.UPDATE))
.errorScript(ERROR_SCRIPT)
.conditions(compositeConditionMapper.toRestModel(Collections.emptyList()))
.create();
}
private static org.alfresco.service.cmr.rule.Rule createRuleModel() {
final NodeRef nodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, RULE_ID);
final org.alfresco.service.cmr.rule.Rule ruleModel = new org.alfresco.service.cmr.rule.Rule(nodeRef);
ruleModel.setTitle(RULE_NAME);
ruleModel.setDescription(RULE_DESCRIPTION);
ruleModel.setRuleDisabled(!RULE_ENABLED);
ruleModel.applyToChildren(RULE_CASCADE);
ruleModel.setExecuteAsynchronously(RULE_ASYNC);
ruleModel.setRuleTypes(List.of(RuleType.INBOUND, RuleType.UPDATE));
ruleModel.setAction(createActionModel());
return ruleModel;
}
private static org.alfresco.service.cmr.action.Action createActionModel() {
final ActionCondition actionCondition = new ActionConditionImpl("action-condition-id", "action-condition-def-name");
final org.alfresco.service.cmr.action.Action actionModel = new ActionImpl(null, "action-id", ACTION_DEFINITION_NAME);
actionModel.setCompensatingAction(createCompensatingActionModel());
actionModel.addActionCondition(actionCondition);
return actionModel;
}
private static org.alfresco.service.cmr.action.Action createCompensatingActionModel() {
final org.alfresco.service.cmr.action.Action compensatingActionModel = new ActionImpl(null, "compensating-action-id", ScriptActionExecuter.NAME);
compensatingActionModel.setParameterValue(ScriptActionExecuter.PARAM_SCRIPTREF, ERROR_SCRIPT);
return compensatingActionModel;
}
}

View File

@@ -26,6 +26,8 @@
package org.alfresco.rest.api.nodes;
import static org.mockito.BDDMockito.then;
import junit.framework.TestCase;
import org.alfresco.rest.api.RuleSets;
import org.alfresco.rest.api.model.rules.RuleSetLink;
@@ -46,6 +48,7 @@ public class NodeRuleSetsRelationTest extends TestCase
{
private static final String FOLDER_NODE_ID = "dummy-folder-node-id";
private static final String LINK_TO_NODE_ID = "dummy-link-to-node-id";
private static final String RULE_SET_NODE_ID = "dummy-rule-set-node-id";
@Mock
private RuleSets ruleSets;
@@ -71,5 +74,15 @@ public class NodeRuleSetsRelationTest extends TestCase
Assert.assertEquals(ruleResult, actual);
}
@Test
public void testUnlinkRuleSet()
{
//when
ruleSets.unlinkRuleSet(FOLDER_NODE_ID,RULE_SET_NODE_ID);
then(ruleSets).should().unlinkRuleSet(FOLDER_NODE_ID,RULE_SET_NODE_ID);
then(ruleSets).shouldHaveNoMoreInteractions();
}
}

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2021 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
@@ -177,6 +177,7 @@ public class DiscoveryApiTest extends AbstractSingleNetworkSiteTest
assertEquals(1000L, entitlements.getMaxDocs().longValue());
assertTrue(entitlements.getIsClusterEnabled());
assertFalse(entitlements.getIsCryptodocEnabled());
assertFalse(entitlements.getIsCustomEmbeddedWorkflowEnabled());
// Check status
StatusInfo statusInfo = repositoryInfo.getStatus();
@@ -297,11 +298,13 @@ public class DiscoveryApiTest extends AbstractSingleNetworkSiteTest
assertEquals(1000L, entitlements.getMaxDocs().longValue());
assertTrue(entitlements.getIsClusterEnabled());
assertFalse(entitlements.getIsCryptodocEnabled());
assertFalse(entitlements.getIsCustomEmbeddedWorkflowEnabled());
// Override entitlements
when(licenseDescriptorMock.getMaxDocs()).thenReturn(null);
when(licenseDescriptorMock.isClusterEnabled()).thenReturn(false);
when(licenseDescriptorMock.isCryptodocEnabled()).thenReturn(true);
when(licenseDescriptorMock.isCustomEmbeddedWorkflowEnabled()).thenReturn(true);
response = get("discovery", null, 200);
@@ -319,6 +322,7 @@ public class DiscoveryApiTest extends AbstractSingleNetworkSiteTest
assertNull(entitlements.getMaxDocs());
assertFalse(entitlements.getIsClusterEnabled());
assertTrue(entitlements.getIsCryptodocEnabled());
assertTrue(entitlements.getIsCustomEmbeddedWorkflowEnabled());
}
@Test

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>17.130</version>
<version>17.141-SNAPSHOT</version>
</parent>
<dependencies>

View File

@@ -46,6 +46,7 @@ public class PropTablesCleaner
{
private static final String PROPERTY_PROP_TABLE_CLEANER_ALG = "system.prop_table_cleaner.algorithm";
private static final String PROP_TABLE_CLEANER_ALG_V2 = "V2";
private static final String PROP_TABLE_CLEANER_ALG_V3 = "V3";
private PropertyValueDAO propertyValueDAO;
private JobLockService jobLockService;
@@ -100,6 +101,10 @@ public class PropTablesCleaner
{
propertyValueDAO.cleanupUnusedValuesV2();
}
else if (PROP_TABLE_CLEANER_ALG_V3.equalsIgnoreCase(getAlgorithm()))
{
propertyValueDAO.cleanupUnusedValuesV3();
}
else
{
propertyValueDAO.cleanupUnusedValues();

View File

@@ -377,4 +377,6 @@ public interface PropertyValueDAO
void cleanupUnusedValues();
void cleanupUnusedValuesV2();
void cleanupUnusedValuesV3();
}

View File

@@ -758,4 +758,23 @@ public class PropertyValueDAOImpl extends AbstractPropertyValueDAOImpl
clearCaches();
}
}
@Override
public void cleanupUnusedValuesV3()
{
// Run the main script
try
{
scriptExecutor.exec(false, "alfresco/dbscripts/utility/${db.script.dialect}", "CleanAlfPropTablesV3.sql");
}
catch (RuntimeException e)
{
logger.error("The cleanup script failed: ", e);
throw e;
}
finally
{
clearCaches();
}
}
}

View File

@@ -73,7 +73,7 @@ public class DeleteNotExistsExecutor implements StatementExecutor
private String sql;
private int line;
private File scriptFile;
private Properties globalProperties;
protected Properties globalProperties;
protected boolean readOnly;
protected int deleteBatchSize;

View File

@@ -0,0 +1,501 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.repo.domain.schema.script;
import java.io.File;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
import javax.sql.DataSource;
import org.alfresco.repo.domain.dialect.Dialect;
import org.alfresco.repo.domain.dialect.MySQLInnoDBDialect;
import org.alfresco.util.Pair;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Same logic as DeleteNotExistsExecutor with the following changes:
* <p/>
* - filters the queries by unique values
* <p/>
* - eager close of result sets
* <p/>
* - we store all the ids in memory and process them from there - the secondary ids are stored in a unique list without
* duplicate values.
* <p/>
* - we only cross 2 sets (the potential ids to delete from the primary table with the set of all secondary ids in that
* range) removing all elements from the second set from the first set
* <p/>
* - every {pauseAndRecoverBatchSize} rows deleted we close all prepared statements and close the connection and sleep
* for {pauseAndRecoverTime} milliseconds. This is necessary to allow the DBMS to perform the background tasks without
* load from ACS. When we do not do this and if we are performing millions of deletes, the connection eventually gets
* aborted.
*
* @author Eva Vasques
*/
public class DeleteNotExistsV3Executor extends DeleteNotExistsExecutor
{
private static Log logger = LogFactory.getLog(DeleteNotExistsV3Executor.class);
public static final String PROPERTY_PAUSE_AND_RECOVER_BATCHSIZE = "system.delete_not_exists.pauseAndRecoverBatchSize";
public static final String PROPERTY_PAUSE_AND_RECOVER_TIME = "system.delete_not_exists.pauseAndRecoverTime";
public static final long DEFAULT_PAUSE_AND_RECOVER_BATCHSIZE = 500000;
public static final long DEFAULT_PAUSE_AND_RECOVER_TIME = 300000;
private Dialect dialect;
private final DataSource dataSource;
private long pauseAndRecoverTime;
private long pauseAndRecoverBatchSize;
private boolean pauseAndRecover = false;
private int processedCounter = 0;
public DeleteNotExistsV3Executor(Dialect dialect, Connection connection, String sql, int line, File scriptFile,
Properties globalProperties, DataSource dataSource)
{
super(connection, sql, line, scriptFile, globalProperties);
this.dialect = dialect;
this.dataSource = dataSource;
}
@Override
public void execute() throws Exception
{
checkProperties();
String pauseAndRecoverBatchSizeString = globalProperties.getProperty(PROPERTY_PAUSE_AND_RECOVER_BATCHSIZE);
pauseAndRecoverBatchSize = pauseAndRecoverBatchSizeString == null ? DEFAULT_PAUSE_AND_RECOVER_BATCHSIZE
: Long.parseLong(pauseAndRecoverBatchSizeString);
String pauseAndRecoverTimeString = globalProperties.getProperty(PROPERTY_PAUSE_AND_RECOVER_TIME);
pauseAndRecoverTime = pauseAndRecoverTimeString == null ? DEFAULT_PAUSE_AND_RECOVER_TIME
: Long.parseLong(pauseAndRecoverTimeString);
super.execute();
}
@Override
protected void process(Pair<String, String>[] tableColumn, Long[] tableUpperLimits, String[] optionalWhereClauses)
throws SQLException
{
String primaryTableName = tableColumn[0].getFirst();
String primaryColumnName = tableColumn[0].getSecond();
String primaryWhereClause = optionalWhereClauses[0];
Long primaryId = 0L;
deletedCount = 0L;
startTime = new Date();
processBatch(primaryTableName, primaryColumnName, primaryWhereClause, primaryId, tableColumn, tableUpperLimits,
optionalWhereClauses);
if (logger.isDebugEnabled())
{
String msg = ((readOnly) ? "Script would have" : "Script") + " deleted a total of " + deletedCount
+ " items from table " + primaryTableName + ".";
logger.debug(msg);
}
}
private void processBatch(String primaryTableName, String primaryColumnName, String primaryWhereClause, Long primaryId,
Pair<String, String>[] tableColumn, Long[] tableUpperLimits, String[] optionalWhereClauses) throws SQLException
{
PreparedStatement primaryPrepStmt = null;
PreparedStatement[] secondaryPrepStmts = null;
PreparedStatement deletePrepStmt = null;
Set<Long> deleteIds = new HashSet<>();
pauseAndRecover = false;
try
{
connection.setAutoCommit(false);
primaryPrepStmt = connection
.prepareStatement(createPreparedSelectStatement(primaryTableName, primaryColumnName, primaryWhereClause));
primaryPrepStmt.setFetchSize(batchSize);
primaryPrepStmt.setLong(1, primaryId);
primaryPrepStmt.setLong(2, tableUpperLimits[0]);
boolean hasResults = primaryPrepStmt.execute();
if (hasResults)
{
// Prepared statements for secondary tables for the next batch
secondaryPrepStmts = new PreparedStatement[tableColumn.length];
for (int i = 1; i < tableColumn.length; i++)
{
PreparedStatement secStmt = connection.prepareStatement(createPreparedSelectStatement(
tableColumn[i].getFirst(), tableColumn[i].getSecond(), optionalWhereClauses[i]));
secStmt.setFetchSize(batchSize);
secondaryPrepStmts[i] = secStmt;
}
deletePrepStmt = connection.prepareStatement(
createPreparedDeleteStatement(primaryTableName, primaryColumnName, deleteBatchSize, primaryWhereClause));
// Timeout is only checked at each batch start.
// It can be further refined by being verified at each primary row processing.
while (hasResults && !isTimeoutExceeded())
{
// Process batch
primaryId = processPrimaryTableResultSet(primaryPrepStmt, secondaryPrepStmts, deletePrepStmt, deleteIds,
primaryTableName, primaryColumnName, tableColumn);
connection.commit();
// If we have no more results (next primaryId is null) or job is marked for pause and recover, do
// not start the next batch
if (primaryId == null || pauseAndRecover)
{
break;
}
// Prepare for next batch
primaryPrepStmt.setLong(1, primaryId + 1);
primaryPrepStmt.setLong(2, tableUpperLimits[0]);
// Query the primary table for the next batch
hasResults = primaryPrepStmt.execute();
}
// Check if we have any more ids to delete
if (!deleteIds.isEmpty())
{
deleteFromPrimaryTable(deletePrepStmt, deleteIds, primaryTableName);
connection.commit();
}
}
}
finally
{
closeQuietly(deletePrepStmt);
closeQuietly(secondaryPrepStmts);
closeQuietly(primaryPrepStmt);
closeQuietly(connection);
}
if (pauseAndRecover)
{
pauseAndRecoverJob(dataSource);
logger.info("Resuming the job on primary table " + primaryTableName + " picking up after id " + primaryId);
processBatch(primaryTableName, primaryColumnName, primaryWhereClause, primaryId, tableColumn, tableUpperLimits,
optionalWhereClauses);
}
}
@Override
protected String createPreparedSelectStatement(String tableName, String columnName, String whereClause)
{
StringBuilder sqlBuilder = new StringBuilder("SELECT " + columnName + " FROM " + tableName + " WHERE ");
if (whereClause != null && !whereClause.isEmpty())
{
sqlBuilder.append(whereClause + " AND ");
}
sqlBuilder.append(
columnName + " >= ? AND " + columnName + " <= ? GROUP BY " + columnName + " ORDER BY " + columnName + " ASC ");
if (dialect instanceof MySQLInnoDBDialect)
{
sqlBuilder.append(" LIMIT " + batchSize);
}
else
{
sqlBuilder.append(" OFFSET 0 ROWS FETCH FIRST " + batchSize + " ROWS ONLY");
}
return sqlBuilder.toString();
}
@Override
protected Long processPrimaryTableResultSet(PreparedStatement primaryPrepStmt, PreparedStatement[] secondaryPrepStmts,
PreparedStatement deletePrepStmt, Set<Long> deleteIds, String primaryTableName, String primaryColumnName,
Pair<String, String>[] tableColumn) throws SQLException
{
Long primaryId = null;
Long minSecId = 0L;
Long maxSecId = 0L;
// Set all rows retrieved from the primary table as our potential ids to delete
Set<Long> potentialIdsToDelete = new HashSet<Long>();
Long minPotentialId = 0L;
Long maxPotentialId = 0L;
try (ResultSet resultSet = primaryPrepStmt.getResultSet())
{
while (resultSet.next())
{
primaryId = resultSet.getLong(primaryColumnName);
potentialIdsToDelete.add(primaryId);
minPotentialId = (minPotentialId == 0L || primaryId < minPotentialId) ? primaryId : minPotentialId;
maxPotentialId = primaryId > maxPotentialId ? primaryId : maxPotentialId;
}
}
if (potentialIdsToDelete.size() == 0)
{
// Nothing more to do
return primaryId;
}
int rowsInBatch = potentialIdsToDelete.size();
processedCounter = processedCounter + rowsInBatch;
// Get a combined list of the ids present in the secondary tables
SecondaryResultsInfo secondaryResultsInfo = getSecondaryResults(secondaryPrepStmts, tableColumn, minPotentialId,
maxPotentialId);
Set<Long> secondaryResults = secondaryResultsInfo.getValues();
if (secondaryResultsInfo.getSize() > 0)
{
minSecId = secondaryResultsInfo.getMinValue();
maxSecId = secondaryResultsInfo.getMaxValue();
// From our potentialIdsToDelete list, remove all non-eligible ids: any id that is in a secondary table or
// any ID past the last ID we were able to access in the secondary tables (maxSecId)
Iterator<Long> it = potentialIdsToDelete.iterator();
while (it.hasNext())
{
Long id = it.next();
if (id > maxSecId || secondaryResults.contains(id))
{
it.remove();
}
}
// The next starting primary ID for the next batch will either be the next last id evaluated from the
// primary table or, in case the secondary queries did not get that far, the last secondary table id
// evaluated (maxSecId)
primaryId = primaryId < maxSecId ? primaryId : maxSecId;
}
// Delete the ids that are eligble from the primary table
if (potentialIdsToDelete.size() > 0)
{
deleteInBatches(potentialIdsToDelete, deleteIds, primaryTableName, deletePrepStmt);
}
if (logger.isTraceEnabled())
{
logger.trace("Rows processed " + rowsInBatch + " from primary table " + primaryTableName + ". Primary: ["
+ minPotentialId + "," + maxPotentialId + "] Secondary rows processed: " + secondaryResultsInfo.getSize()
+ " [" + minSecId + "," + maxSecId + "] Total Deleted: " + deletedCount);
}
// If the total rows processed from all batches so far is greater that the defined pauseAndRecoverBatchSize,
// mark the job to pause and recover after completing this batch
if (processedCounter >= pauseAndRecoverBatchSize)
{
pauseAndRecover = true;
}
// Return the last primary id processed for the next batch
return primaryId;
}
private void deleteInBatches(Set<Long> potentialIdsToDelete, Set<Long> deleteIds, String primaryTableName,
PreparedStatement deletePrepStmt) throws SQLException
{
Iterator<Long> potentialIdsIt = potentialIdsToDelete.iterator();
while (potentialIdsIt.hasNext())
{
Long idToDelete = (Long) potentialIdsIt.next();
deleteIds.add(idToDelete);
if (deleteIds.size() == deleteBatchSize)
{
deleteFromPrimaryTable(deletePrepStmt, deleteIds, primaryTableName);
}
}
}
/*
* Get a combined list of the ids present in all the secondary tables
*/
private SecondaryResultsInfo getSecondaryResults(PreparedStatement[] preparedStatements, Pair<String, String>[] tableColumn,
Long minPotentialId, Long maxPotentialId) throws SQLException
{
Set<Long> secondaryResultValues = new HashSet<Long>();
Long lowestUpperValue = 0L;
for (int i = 1; i < preparedStatements.length; i++)
{
String columnId = tableColumn[i].getSecond();
PreparedStatement secStmt = preparedStatements[i];
secStmt.setLong(1, minPotentialId);
secStmt.setLong(2, maxPotentialId);
// Execute the query on each secondary table
boolean secHasResults = secStmt.execute();
if (secHasResults)
{
try (ResultSet secResultSet = secStmt.getResultSet())
{
Long thisId = 0L;
Long resultSize = 0L;
Long upperValue = 0L;
while (secResultSet.next())
{
resultSize++;
thisId = secResultSet.getLong(columnId);
// Add to the list if it's not there yet
if (!secondaryResultValues.contains(thisId))
{
secondaryResultValues.add(thisId);
}
upperValue = thisId > upperValue ? thisId : upperValue;
}
// Set the upper min value. We need to gather the last ID processed, so on the next batch on the
// primary table we can resume from there. We only need to do this if the number of results of the
// secondary table matches the batch size (if not, it means that there aren't more results up to
// maxPotentialId). Example on why this is needed: Primary table batch has 100k results from id's 1
// to 250000. Secondary table on that interval returns 100k results from id 3 to 210000. Next batch
// needs to start on id 210001
if (upperValue > 0 && resultSize == batchSize)
{
lowestUpperValue = (lowestUpperValue == 0L || upperValue < lowestUpperValue) ? upperValue
: lowestUpperValue;
}
}
}
}
// If lowestUpperValue is still 0 (because a secondary table never had more or the same number of results as the
// primary table), the next id should be the last max id evaluated from the primary table (maxPotentialId)
lowestUpperValue = lowestUpperValue == 0 ? maxPotentialId : lowestUpperValue;
// Remove all values after the lower upper value of a secondary table
long minSecId = 0L;
Iterator<Long> it = secondaryResultValues.iterator();
while (it.hasNext())
{
long secondaryId = it.next();
if (secondaryId > lowestUpperValue)
{
it.remove();
}
else
{
minSecId = (minSecId == 0L || secondaryId < minSecId) ? secondaryId : minSecId;
}
}
// Return a combined list of the ids present in all the secondary tables
return new SecondaryResultsInfo(secondaryResultValues, minSecId, lowestUpperValue);
}
private class SecondaryResultsInfo
{
Set<Long> values;
long minValue;
long maxValue;
long size;
public SecondaryResultsInfo(Set<Long> values, long minValue, long maxValue)
{
super();
this.values = values;
this.minValue = minValue;
this.maxValue = maxValue;
this.size = values.size();
}
public Set<Long> getValues()
{
return values;
}
public long getMinValue()
{
return minValue;
}
public long getMaxValue()
{
return maxValue;
}
public long getSize()
{
return size;
}
}
/*
* Sleep for {pauseAndRecoverTime} before opening a new connection and continue to process new batches
*/
private void pauseAndRecoverJob(DataSource dataSource) throws SQLException
{
if (logger.isDebugEnabled())
{
logger.debug("Reached batch size for pause and recovery. Job will resume in " + pauseAndRecoverTime + " ms");
}
// Wait
try
{
Thread.sleep(pauseAndRecoverTime);
}
catch (InterruptedException e)
{
// Do nothing
}
// Start another connection and continue where we left off
connection = dataSource.getConnection();
pauseAndRecover = false;
processedCounter = 0;
}
protected void closeQuietly(Connection connection)
{
try
{
connection.close();
}
catch (SQLException e)
{
// Do nothing
}
finally
{
connection = null;
}
}
}

View File

@@ -196,7 +196,6 @@ public class MySQLDeleteNotExistsExecutor extends DeleteNotExistsExecutor
if (deleteIds.size() == deleteBatchSize)
{
deleteFromPrimaryTable(deletePrepStmt, deleteIds, primaryTableName);
connection.commit();
}
if (!resultSet.next())
@@ -208,13 +207,13 @@ public class MySQLDeleteNotExistsExecutor extends DeleteNotExistsExecutor
primaryId = resultSet.getLong(primaryColumnName);
}
if (logger.isTraceEnabled())
{
logger.trace("RowsProcessed " + rowsProcessed + " from primary table " + primaryTableName);
}
updateSecondaryIds(primaryId, secondaryIds, secondaryPrepStmts, secondaryOffsets, secondaryResultSets, tableColumn);
}
if (logger.isTraceEnabled())
{
logger.trace("RowsProcessed " + rowsProcessed + " from primary table " + primaryTableName);
}
}
finally
{

View File

@@ -274,6 +274,8 @@ public class ScriptExecutorImpl implements ScriptExecutor
while(true)
{
connection = refreshConnection(connection);
String sqlOriginal = reader.readLine();
line++;
@@ -348,6 +350,24 @@ public class ScriptExecutorImpl implements ScriptExecutor
}
continue;
}
else if (sql.startsWith("--DELETE_NOT_EXISTS_V3"))
{
DeleteNotExistsV3Executor deleteNotExistsFiltered = createDeleteNotExistV3Executor(dialect, connection, sql,
line, scriptFile);
deleteNotExistsFiltered.execute();
// Reset
sb.setLength(0);
fetchVarName = null;
fetchColumnName = null;
defaultFetchValue = null;
batchTableName = null;
doBatch = false;
batchUpperLimit = 0;
batchSize = 1;
continue;
}
else if (sql.startsWith("--DELETE_NOT_EXISTS"))
{
DeleteNotExistsExecutor deleteNotExists = createDeleteNotExistsExecutor(dialect, connection, sql, line, scriptFile);
@@ -538,6 +558,17 @@ public class ScriptExecutorImpl implements ScriptExecutor
}
}
private Connection refreshConnection(Connection connection) throws SQLException
{
if (connection == null || connection.isClosed())
{
connection = dataSource.getConnection();
connection.setAutoCommit(true);
}
return connection;
}
private DeleteNotExistsExecutor createDeleteNotExistsExecutor(Dialect dialect, Connection connection, String sql, int line, File scriptFile)
{
if (dialect instanceof MySQLInnoDBDialect)
@@ -548,6 +579,12 @@ public class ScriptExecutorImpl implements ScriptExecutor
return new DeleteNotExistsExecutor(connection, sql, line, scriptFile, globalProperties);
}
private DeleteNotExistsV3Executor createDeleteNotExistV3Executor(Dialect dialect, Connection connection, String sql, int line,
File scriptFile)
{
return new DeleteNotExistsV3Executor(dialect, connection, sql, line, scriptFile, globalProperties, dataSource);
}
/**
* Execute the given SQL statement, absorbing exceptions that we expect during
* schema creation or upgrade.

View File

@@ -390,8 +390,12 @@ public class ImporterComponent implements ImporterService
{
String[] qnameComponents = QName.splitPrefixedQName(segments[i]);
String localName = QName.createValidLocalName(qnameComponents[1]);
String localName = qnameComponents[1];
if (localName == null || localName.length() == 0)
{
throw new IllegalArgumentException("Local name cannot be null or empty.");
}
// MT: bootstrap of "alfrescoUserStore.xml" requires 'sys:people/cm:admin@tenant' to be encoded as 'sys:people/cm:admin_x0040_tenant' (for XPath)
localName = localName.replace("@", "_x0040_");

View File

@@ -0,0 +1,76 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.repo.module;
import static java.util.Optional.ofNullable;
import static java.util.function.Predicate.not;
import static java.util.stream.Collectors.joining;
import java.util.List;
import org.alfresco.service.cmr.module.ModuleDetails;
import org.alfresco.service.cmr.module.ModuleService;
import org.springframework.extensions.surf.util.I18NUtil;
/**
* Halts the bootstrap process if a deprecated module is present.
*
* @author Domenico Sibilio
* @since 7.3.0
*/
public class DeprecatedModulesValidator
{
private static final String ERROR_MSG = "module.err.deprecated_modules";
private final ModuleService moduleService;
private final List<String> deprecatedModules;
public DeprecatedModulesValidator(final ModuleService moduleService, final List<String> deprecatedModules)
{
this.moduleService = moduleService;
this.deprecatedModules = deprecatedModules;
}
public void onInit()
{
ofNullable(moduleService.getAllModules())
.map(this::getDeprecatedModules)
.filter(not(String::isBlank))
.ifPresent(DeprecatedModulesValidator::throwException);
}
private String getDeprecatedModules(List<ModuleDetails> modules)
{
return modules.stream()
.filter(module -> deprecatedModules.contains(module.getId()))
.map(module -> module.getTitle() + " " + module.getModuleVersionNumber())
.collect(joining(", "));
}
private static void throwException(String foundDeprecatedModules)
{
throw new IllegalStateException(I18NUtil.getMessage(ERROR_MSG, foundDeprecatedModules));
}
}

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
@@ -46,6 +46,7 @@ import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.Path;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.workflow.FailedWorkflowDeployment;
import org.alfresco.service.cmr.workflow.WorkflowAdminService;
import org.alfresco.service.cmr.workflow.WorkflowDefinition;
import org.alfresco.service.cmr.workflow.WorkflowDeployment;
@@ -62,11 +63,14 @@ import org.springframework.core.io.ClassPathResource;
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
import javax.transaction.UserTransaction;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.function.Supplier;
/**
* Alfresco bootstrap Process deployment.
@@ -314,8 +318,10 @@ public class WorkflowDeployer extends AbstractLifecycleBean
}
else
{
WorkflowDeployment deployment = workflowService.deployDefinition(engineId, workflowResource.getInputStream(), mimetype, workflowResource.getFilename(), true);
logDeployment(location, deployment);
final InputStream workflowInputStream = workflowResource.getInputStream();
final Optional<WorkflowDeployment> possibleDeployment = tryToDeploy(() ->
workflowService.deployDefinition(engineId, workflowInputStream, mimetype, workflowResource.getFilename(), true));
possibleDeployment.ifPresent(deployment -> logDeployment(location, deployment));
}
}
else
@@ -402,10 +408,9 @@ public class WorkflowDeployer extends AbstractLifecycleBean
else
{
// deploy / re-deploy
WorkflowDeployment deployment = workflowService.deployDefinition(nodeRef);
logDeployment(nodeRef, deployment);
if (deployment != null)
tryToDeploy(() -> workflowService.deployDefinition(nodeRef)).ifPresent(deployment ->
{
logDeployment(nodeRef, deployment);
WorkflowDefinition def = deployment.getDefinition();
// Update the meta data for the model
@@ -424,7 +429,7 @@ public class WorkflowDeployer extends AbstractLifecycleBean
}
nodeService.setProperties(nodeRef, props);
}
});
}
}
else
@@ -441,6 +446,20 @@ public class WorkflowDeployer extends AbstractLifecycleBean
}
}
private Optional<WorkflowDeployment> tryToDeploy(Supplier<WorkflowDeployment> workflowDeployment)
{
final WorkflowDeployment deployment = workflowDeployment.get();
final Optional<String> possibleFailure = FailedWorkflowDeployment.getFailure(deployment);
if (possibleFailure.isEmpty())
{
return Optional.ofNullable(deployment);
}
logger.warn("Failed to deploy a workflow. " + possibleFailure.get());
return Optional.empty();
}
/**
* Validate that the workflow definition node is a child of the correct
* workflow location node, e.g. "/Company Home/Data Dictionary/Workflows"

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
@@ -105,7 +105,7 @@ public class ActivitiWorkflowManagerFactory implements FactoryBean<ActivitiWorkf
ActivitiPropertyConverter propertyConverter = new ActivitiPropertyConverter(activitiUtil, factory, handlerRegistry, authorityManager, messageService, nodeConverter);
ActivitiTypeConverter typeConverter = new ActivitiTypeConverter(processEngine, factory, propertyConverter, deployWorkflowsInTenant);
ActivitiWorkflowEngine workflowEngine = new ActivitiWorkflowEngine();
ActivitiWorkflowEngine workflowEngine = instantiateWorkflowEngine();
workflowEngine.setActivitiUtil(activitiUtil);
workflowEngine.setAuthorityManager(authorityManager);
workflowEngine.setBPMEngineRegistry(bpmEngineRegistry);
@@ -124,6 +124,11 @@ public class ActivitiWorkflowManagerFactory implements FactoryBean<ActivitiWorkf
return new ActivitiWorkflowManager(workflowEngine, propertyConverter, handlerRegistry, nodeConverter, authorityManager);
}
protected ActivitiWorkflowEngine instantiateWorkflowEngine()
{
return new ActivitiWorkflowEngine();
}
/**
* @param tenantService the tenantService to set
*/

View File

@@ -0,0 +1,71 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 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.service.cmr.workflow;
import java.util.Optional;
/**
* The goal of this class is to provide a support for workflow deployment failure. Since {@link WorkflowDeployment} is
* part of the public API we don't want to change it.
*/
public final class FailedWorkflowDeployment
{
private FailedWorkflowDeployment()
{
//no instantiation
}
public static WorkflowDeployment deploymentForbidden(String workflowName, String reason)
{
return new DeploymentFailure(workflowName, reason);
}
public static Optional<String> getFailure(WorkflowDeployment workflowDeployment)
{
if (!(workflowDeployment instanceof DeploymentFailure))
{
return Optional.empty();
}
return Optional.of(workflowDeployment.getProblems()[0]);
}
private static class DeploymentFailure extends WorkflowDeployment
{
private static final String UNDEFINED = "undefined";
private DeploymentFailure(String workflowName, String problemDescription)
{
super(failedDefinition(workflowName), problemDescription);
}
private static WorkflowDefinition failedDefinition(String workflowName)
{
final String definitionName = workflowName == null ? UNDEFINED : workflowName;
return new WorkflowDefinition(UNDEFINED, definitionName, UNDEFINED, UNDEFINED, UNDEFINED, null);
}
}
}

View File

@@ -139,7 +139,17 @@ public interface LicenseDescriptor
* @return <code>true</code> if the license allows cryptodoc
*/
boolean isCryptodocEnabled();
/**
* Does this license allow custom embedded workflows?
*
* @return <code>true</code> if the license allows custom embedded workflows
*/
default boolean isCustomEmbeddedWorkflowEnabled()
{
return false;
}
/**
* ATS Transformation Server Expiry Date
* @return the ATS Transformation Server Expiry Date or <code>null</code>

View File

@@ -135,6 +135,26 @@
<bean id="siteLoadBootstrap-Spaces" parent="spacesStoreImporter"/>
<bean id="siteLoadBootstrap-Users" parent="userStoreImporter"/>
<!-- Descriptor Service -->
<bean id="descriptorComponent" class="org.alfresco.repo.descriptor.DescriptorServiceImpl">
<property name="serverDescriptorDAO">
<ref bean="serverDescriptorDAO"/>
</property>
<property name="currentRepoDescriptorDAO">
<ref bean="currentRepoDescriptorDAO"/>
</property>
<property name="installedRepoDescriptorDAO">
<ref bean="installedRepoDescriptorDAO"/>
</property>
<property name="transactionService">
<ref bean="transactionService"/>
</property>
<property name="hbDataCollectorService">
<ref bean="hbDataCollectorService"/>
</property>
<property name="repoUsageComponent" ref="repoUsageComponent"/>
</bean>
<bean id="workflowBootstrap" parent="workflowDeployer">
<property name="workflowDefinitions">
<list>
@@ -255,26 +275,6 @@
</bean>
<!-- Descriptor Service -->
<bean id="descriptorComponent" class="org.alfresco.repo.descriptor.DescriptorServiceImpl">
<property name="serverDescriptorDAO">
<ref bean="serverDescriptorDAO"/>
</property>
<property name="currentRepoDescriptorDAO">
<ref bean="currentRepoDescriptorDAO"/>
</property>
<property name="installedRepoDescriptorDAO">
<ref bean="installedRepoDescriptorDAO"/>
</property>
<property name="transactionService">
<ref bean="transactionService"/>
</property>
<property name="hbDataCollectorService">
<ref bean="hbDataCollectorService"/>
</property>
<property name="repoUsageComponent" ref="repoUsageComponent"/>
</bean>
<!-- Bootstrap MT (multi-tenancy) if applicable -->
<bean id="multiTenantBootstrap" class="org.alfresco.repo.tenant.MultiTenantBootstrap" >
<property name="tenantAdminService" ref="tenantAdminService"/>

View File

@@ -0,0 +1,9 @@
--DELETE_NOT_EXISTS_V3 alf_prop_root.id,alf_audit_app.disabled_paths_id,alf_audit_entry.audit_values_id,alf_prop_unique_ctx.prop1_id system.delete_not_exists.batchsize
--DELETE_NOT_EXISTS_V3 alf_prop_value.id,alf_audit_app.app_name_id,alf_audit_entry.audit_user_id,alf_prop_link.key_prop_id,alf_prop_link.value_prop_id,alf_prop_unique_ctx.value1_prop_id,alf_prop_unique_ctx.value2_prop_id,alf_prop_unique_ctx.value3_prop_id system.delete_not_exists.batchsize
--DELETE_NOT_EXISTS_V3 alf_prop_string_value.id,alf_prop_value.long_value."persisted_type in (3, 5, 6)",alf_audit_app.app_name_id,alf_audit_entry.audit_user_id,alf_prop_link.key_prop_id,alf_prop_link.value_prop_id,alf_prop_unique_ctx.value1_prop_id,alf_prop_unique_ctx.value2_prop_id,alf_prop_unique_ctx.value3_prop_id system.delete_not_exists.batchsize
--DELETE_NOT_EXISTS_V3 alf_prop_serializable_value.id,alf_prop_value.long_value.persisted_type=4,alf_audit_app.app_name_id,alf_audit_entry.audit_user_id,alf_prop_link.key_prop_id,alf_prop_link.value_prop_id,alf_prop_unique_ctx.value1_prop_id,alf_prop_unique_ctx.value2_prop_id,alf_prop_unique_ctx.value3_prop_id system.delete_not_exists.batchsize
--DELETE_NOT_EXISTS_V3 alf_prop_double_value.id,alf_prop_value.long_value.persisted_type=2,alf_audit_app.app_name_id,alf_audit_entry.audit_user_id,alf_prop_link.key_prop_id,alf_prop_link.value_prop_id,alf_prop_unique_ctx.value1_prop_id,alf_prop_unique_ctx.value2_prop_id,alf_prop_unique_ctx.value3_prop_id system.delete_not_exists.batchsize

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