mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-09-10 14:11:58 +00:00
Compare commits
114 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
abf9bf8d71 | ||
|
162e164a0c | ||
|
55b0044965 | ||
|
cfe212d52f | ||
|
a3cafb7c4c | ||
|
e42e2b2c8e | ||
|
9090dea75b | ||
|
a26ac1f778 | ||
|
8e1d4782b4 | ||
|
4c409c925b | ||
|
7cbfab81ce | ||
|
addc2c202b | ||
|
7b523e5ad9 | ||
|
7567e83955 | ||
|
d6082f84ac | ||
|
4f58031178 | ||
|
7aff9366ce | ||
|
58235fd891 | ||
|
bb204c4cea | ||
|
ae29ae8581 | ||
|
4b351fbfdc | ||
|
3e9964d53f | ||
|
23761f6c56 | ||
|
2f8c283ada | ||
|
5232b8c3fe | ||
|
b90938bb99 | ||
|
d243ac04c6 | ||
|
e651f6e104 | ||
|
e4776e2594 | ||
|
acc5425d68 | ||
|
a96e805d52 | ||
|
45a9a1ae49 | ||
|
bf18c6b419 | ||
|
e728489b69 | ||
|
f2fdf958f2 | ||
|
0cb03c2a38 | ||
|
b13ca1f68b | ||
|
484699b266 | ||
|
a15161c872 | ||
|
b3a6150655 | ||
|
42e06da4f8 | ||
|
4ac30c2173 | ||
|
d1d84d849e | ||
|
40af1799fe | ||
|
f59e4a044a | ||
|
5924263c18 | ||
|
082e38692f | ||
|
f5c4112e65 | ||
|
ad8251c054 | ||
|
32d0182096 | ||
|
2ac9779a3b | ||
|
af999fb0fd | ||
|
ddbaabb713 | ||
|
b93136f2c0 | ||
|
923aadb12a | ||
|
212fa9b362 | ||
|
c3e37b96b4 | ||
|
517f40e150 | ||
|
f86d0f1fd2 | ||
|
09da8640a0 | ||
|
6bd9bf768e | ||
|
f75c0c8f9e | ||
|
ff9364a3b1 | ||
|
db832663b4 | ||
|
890e1173c5 | ||
|
9399c38741 | ||
|
bf16e843fd | ||
|
69a5a95dae | ||
|
be37440e24 | ||
|
6504406861 | ||
|
38d7dbfa00 | ||
|
1ed635275f | ||
|
7b87ee628d | ||
|
9cff80362d | ||
|
8311c889bb | ||
|
bd5b5a240e | ||
|
c2d13e3177 | ||
|
e8f50da5a2 | ||
|
2053e3b83a | ||
|
bcc2eadba6 | ||
|
eb40dd3a45 | ||
|
4c511423d5 | ||
|
b12b21e773 | ||
|
569078f408 | ||
|
90de678d7d | ||
|
0a75cbbc67 | ||
|
c5f174a55d | ||
|
119596647a | ||
|
e5d5969fc4 | ||
|
b79dc538c9 | ||
|
72965efb57 | ||
|
d467c3cdb1 | ||
|
73d919435c | ||
|
5fb86e1bbc | ||
|
23de0fefd7 | ||
|
7c3e8a2549 | ||
|
6a3dbff290 | ||
|
0ca29a76a9 | ||
|
561e7330e3 | ||
|
628b267a2b | ||
|
ea9687ac33 | ||
|
4292a3ce1c | ||
|
09721dca4e | ||
|
0e4020452c | ||
|
96c1464b6c | ||
|
9457e019ef | ||
|
41d82cc5ce | ||
|
439e8587cf | ||
|
a59234f47f | ||
|
c9522f299e | ||
|
30dd78a6ab | ||
|
008134f5b0 | ||
|
285ebc29a4 | ||
|
9c6e8aad9c |
10
.github/workflows/master_release.yml
vendored
10
.github/workflows/master_release.yml
vendored
@@ -27,11 +27,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [run_ci]
|
||||
if: >
|
||||
!failure() &&
|
||||
!(failure() || cancelled()) &&
|
||||
!contains(github.event.head_commit.message, '[no release]') &&
|
||||
github.event_name != 'pull_request'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.33.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v1.33.0
|
||||
- name: "Init"
|
||||
@@ -53,11 +55,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [push_to_nexus]
|
||||
if: >
|
||||
!failure() &&
|
||||
!(failure() || cancelled()) &&
|
||||
!contains(github.event.head_commit.message, '[no downstream]') &&
|
||||
github.event_name != 'pull_request'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.33.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v1.33.0
|
||||
- name: "Init"
|
||||
@@ -72,4 +76,4 @@ jobs:
|
||||
env:
|
||||
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
|
||||
- name: "Clean Maven cache"
|
||||
run: bash ./scripts/ci/cleanup_cache.sh
|
||||
run: bash ./scripts/ci/cleanup_cache.sh
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo-amps</artifactId>
|
||||
<version>20.78</version>
|
||||
<version>20.112</version>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-governance-services-community-parent</artifactId>
|
||||
<version>20.78</version>
|
||||
<version>20.112</version>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-governance-services-automation-community-repo</artifactId>
|
||||
<version>20.78</version>
|
||||
<version>20.112</version>
|
||||
</parent>
|
||||
|
||||
<build>
|
||||
|
@@ -33,7 +33,9 @@ import org.alfresco.rest.rm.community.model.record.Record;
|
||||
import org.alfresco.rest.v0.RecordsAPI;
|
||||
import org.alfresco.utility.Utility;
|
||||
import org.alfresco.utility.constants.UserRole;
|
||||
import org.alfresco.utility.data.RandomData;
|
||||
import org.alfresco.utility.model.FileModel;
|
||||
import org.alfresco.utility.model.FileType;
|
||||
import org.alfresco.utility.model.SiteModel;
|
||||
import org.alfresco.utility.model.UserModel;
|
||||
import org.json.JSONObject;
|
||||
@@ -97,9 +99,10 @@ public class InplaceRecordSearchTests extends BaseRMRestTest {
|
||||
@Test
|
||||
public void searchForInplaceRecord() {
|
||||
// And a document that isn't a record
|
||||
final String fileName = "File" + RandomData.getRandomAlphanumeric();
|
||||
uploadedDocbyCollabUser = dataContent.usingSite(privateSite)
|
||||
.usingUser(siteCollaborator)
|
||||
.createContent(CMISUtil.DocumentType.TEXT_PLAIN);
|
||||
.createContent(new FileModel(fileName, FileType.fromName(fileName + "." + CMISUtil.DocumentType.TEXT_PLAIN.extention)));
|
||||
|
||||
assertNotNull(uploadedDocbyCollabUser.getNodeRef());
|
||||
|
||||
|
@@ -72,7 +72,7 @@ public class SearchDocumentsV1Test extends BaseRMRestTest
|
||||
cmisQueryModel.setLanguage("cmis");
|
||||
|
||||
RestRequestQueryModel aftsQueryModel = new RestRequestQueryModel();
|
||||
aftsQueryModel.setQuery("cm:name:*" + SEARCH_TERM);
|
||||
aftsQueryModel.setQuery("cm:name:*" + SEARCH_TERM + ".txt");
|
||||
aftsQueryModel.setLanguage("afts");
|
||||
|
||||
return new RestRequestQueryModel[][] {
|
||||
@@ -107,7 +107,7 @@ public class SearchDocumentsV1Test extends BaseRMRestTest
|
||||
private void waitIndexing() throws Exception
|
||||
{
|
||||
RestRequestQueryModel queryType = new RestRequestQueryModel();
|
||||
queryType.setQuery("cm:name:*" + SEARCH_TERM);
|
||||
queryType.setQuery("cm:name:*" + SEARCH_TERM + ".txt");
|
||||
queryType.setLanguage("afts");
|
||||
Utility.sleep(1000, 80000, () ->
|
||||
{
|
||||
|
@@ -66,7 +66,6 @@ import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanCo
|
||||
import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAspects.CUT_OFF_ASPECT;
|
||||
import static org.alfresco.rest.rm.community.model.recordcategory.RetentionPeriodProperty.*;
|
||||
import static org.alfresco.rest.rm.community.util.CommonTestUtils.generateTestPrefix;
|
||||
import static org.alfresco.utility.data.RandomData.getRandomName;
|
||||
import static org.alfresco.utility.report.log.Step.STEP;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
@@ -84,26 +83,29 @@ public class DispositionScheduleLinkedRecordsTest extends BaseRMRestTest {
|
||||
@Autowired
|
||||
private RecordFoldersAPI recordFoldersAPI;
|
||||
private final static String TEST_PREFIX = generateTestPrefix(DispositionScheduleLinkedRecordsTest.class);
|
||||
private RecordCategory Category1,catsameLevel1,catsameLevel2;
|
||||
private RecordCategory Category1;
|
||||
private RecordCategoryChild CopyCatFolder,folder1,CatFolder,folder2;
|
||||
private static final String categoryRM3077 = TEST_PREFIX + "RM-3077_manager_sees_me";
|
||||
private static final String copyCategoryRM3077 = "Copy_of_" + categoryRM3077;
|
||||
private static final String folderRM3077 = "RM-3077_folder_"+ categoryRM3077;
|
||||
private static final String copyFolderRM3077 = "Copy_of_" + folderRM3077;
|
||||
private final String electronicRecord = "RM-2937 electronic 2 record";
|
||||
private final String folder = TEST_PREFIX + "RM-2937 folder ghosting";
|
||||
private static final String categoryRecordsRM2526 = TEST_PREFIX + "RM-2526_category_records_immediately";
|
||||
private static final String category2RecordsRM2526 = TEST_PREFIX + "RM-2526_category_2_records_1_day";
|
||||
private static final String firstCategoryRM3060 = TEST_PREFIX + "RM-3060_category_record";
|
||||
private static final String secondCategoryRM3060 = "Copy_of_" + firstCategoryRM3060;
|
||||
private static final String firstFolderRM3060 = TEST_PREFIX + "RM-3060_folder";
|
||||
private static final String secondFolderRM3060 = TEST_PREFIX + "RM-3060_disposition_on_Record_Level";
|
||||
private static final String electronicRecordRM3060 = TEST_PREFIX + "RM-3060_electronic_1_record";
|
||||
private static final String nonElectronicRecordRM3060 = TEST_PREFIX + "RM-3060_non-electronic_record";
|
||||
private static final String firstCategoryRM1622 = TEST_PREFIX + "RM-1622_category_record";
|
||||
private static final String secondCategoryRM1622 = "Copy_of_" + firstCategoryRM1622;;
|
||||
private static final String firstFolderRM1622 = TEST_PREFIX + "RM-1622_folder";
|
||||
private static final String electronicRecordRM1622 = TEST_PREFIX + "RM-1622_electronic_1_record";
|
||||
private static final String secondFolderRM1622 = TEST_PREFIX + "RM-1622_disposition_on_Record_Level";
|
||||
private static final String TRANSFER_LOCATION = TEST_PREFIX + "RM-3060_transferred_records";
|
||||
public static final String TRANSFER_TYPE = "rma:transferred";
|
||||
private FilePlan filePlanModel;
|
||||
private UserModel rmAdmin, rmManager;
|
||||
|
||||
@BeforeClass(alwaysRun = true)
|
||||
public void setupDispositionScheduleLinkedRecordsTest() {
|
||||
createRMSiteIfNotExists();
|
||||
@@ -368,35 +370,35 @@ public class DispositionScheduleLinkedRecordsTest extends BaseRMRestTest {
|
||||
|
||||
// create a category with retention applied on records level
|
||||
RecordCategory catsameLevel1 = getRestAPIFactory().getFilePlansAPI(rmAdmin)
|
||||
.createRootRecordCategory(RecordCategory.builder().name(firstCategoryRM3060).build(),
|
||||
RecordCategory.DEFAULT_FILE_PLAN_ALIAS);
|
||||
.createRootRecordCategory(RecordCategory.builder().name(firstCategoryRM1622).build(),
|
||||
RecordCategory.DEFAULT_FILE_PLAN_ALIAS);
|
||||
RecordCategory catsameLevel2 = getRestAPIFactory().getFilePlansAPI(rmAdmin)
|
||||
.createRootRecordCategory(RecordCategory.builder().name(secondCategoryRM3060).build(),
|
||||
RecordCategory.DEFAULT_FILE_PLAN_ALIAS);
|
||||
.createRootRecordCategory(RecordCategory.builder().name(secondCategoryRM1622).build(),
|
||||
RecordCategory.DEFAULT_FILE_PLAN_ALIAS);
|
||||
|
||||
// create retention schedule applied on records for category 1
|
||||
dispositionScheduleService.createCategoryRetentionSchedule(firstCategoryRM3060, true);
|
||||
dispositionScheduleService.createCategoryRetentionSchedule(firstCategoryRM1622, true);
|
||||
|
||||
// with retain immediately after record creation date and cut 1 day after record creation date
|
||||
dispositionScheduleService.addCutOffAfterPeriodStep(firstCategoryRM3060, "day|1", DATE_FILED);
|
||||
dispositionScheduleService.addCutOffAfterPeriodStep(firstCategoryRM1622, "day|1", DATE_FILED);
|
||||
|
||||
|
||||
// create a folder on the category firstCategoryRM3060 with a complete electronic record
|
||||
RecordCategoryChild firstFolderRecordCategoryChild = createRecordFolder(catsameLevel1.getId(),firstFolderRM3060);
|
||||
Record firstElectronicRecord = createElectronicRecord(firstFolderRecordCategoryChild.getId(),electronicRecordRM3060);
|
||||
// create a folder on the category firstCategoryRM1622 with a complete electronic record
|
||||
RecordCategoryChild firstFolderRecordCategoryChild = createRecordFolder(catsameLevel1.getId(),firstFolderRM1622);
|
||||
Record firstElectronicRecord = createElectronicRecord(firstFolderRecordCategoryChild.getId(),electronicRecordRM1622);
|
||||
|
||||
String elRecordFullName = recordsAPI.getRecordFullName(getDataUser().getAdminUser().getUsername(),
|
||||
getDataUser().getAdminUser().getPassword(),firstFolderRM3060, electronicRecordRM3060);
|
||||
getDataUser().getAdminUser().getPassword(),firstFolderRM1622, electronicRecordRM1622);
|
||||
String elRecordNameNodeRef = recordsAPI.getRecordNodeRef(getDataUser().usingAdmin().getAdminUser().getUsername(),
|
||||
getDataUser().usingAdmin().getAdminUser().getPassword(), elRecordFullName, "/" + firstCategoryRM3060 + "/" + firstFolderRM3060);
|
||||
getDataUser().usingAdmin().getAdminUser().getPassword(), elRecordFullName, "/" + firstCategoryRM1622 + "/" + firstFolderRM1622);
|
||||
|
||||
recordsAPI.completeRecord(getDataUser().getAdminUser().getUsername(),
|
||||
getDataUser().getAdminUser().getPassword(), elRecordFullName);
|
||||
|
||||
// create a folder on the category secondCategoryRM3060 with a non electronic record
|
||||
RecordCategoryChild secondFolderRecordCategoryChild = createRecordFolder(catsameLevel2.getId(),secondFolderRM3060);
|
||||
// create a folder on the category secondCategoryRM1622 with a non electronic record
|
||||
RecordCategoryChild secondFolderRecordCategoryChild = createRecordFolder(catsameLevel2.getId(),secondFolderRM1622);
|
||||
String elRecordNameNodeRefs = recordsAPI.getRecordNodeRef(getDataUser().usingAdmin().getAdminUser().getUsername(),
|
||||
getDataUser().usingAdmin().getAdminUser().getPassword(), elRecordFullName, "/" + firstCategoryRM3060 + "/" + firstFolderRM3060);
|
||||
getDataUser().usingAdmin().getAdminUser().getPassword(), elRecordFullName, "/" + firstCategoryRM1622 + "/" + firstFolderRM1622);
|
||||
|
||||
|
||||
// link it to the folder in second category through the details page
|
||||
@@ -404,8 +406,8 @@ public class DispositionScheduleLinkedRecordsTest extends BaseRMRestTest {
|
||||
recordLists.add(NODE_REF_WORKSPACE_SPACES_STORE + firstElectronicRecord.getId());
|
||||
|
||||
linksAPI.linkRecord(getDataUser().getAdminUser().getUsername(),
|
||||
getDataUser().getAdminUser().getPassword(), HttpStatus.SC_OK,secondCategoryRM3060 + "/" +
|
||||
secondFolderRM3060, recordLists);
|
||||
getDataUser().getAdminUser().getPassword(), HttpStatus.SC_OK,secondCategoryRM1622 + "/" +
|
||||
secondFolderRM1622, recordLists);
|
||||
|
||||
// edit disposition date
|
||||
recordFoldersAPI.postRecordAction(getAdminUser().getUsername(),
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-governance-services-community-parent</artifactId>
|
||||
<version>20.78</version>
|
||||
<version>20.112</version>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
|
@@ -1,3 +1,4 @@
|
||||
ARG BASE_IMAGE
|
||||
# BUILD STAGE AGS
|
||||
FROM debian:11-slim AS AGSBUILDER
|
||||
|
||||
@@ -12,7 +13,7 @@ RUN unzip -q /build/gs-api-explorer-*.war -d /build/gs-api-explorer && \
|
||||
chmod -R g-w,o= /build
|
||||
|
||||
# ACTUAL IMAGE
|
||||
FROM alfresco/alfresco-community-repo-base:${image.tag}
|
||||
FROM ${BASE_IMAGE}
|
||||
|
||||
# Alfresco user does not have permissions to modify webapps or configuration. Switch to root.
|
||||
# The access will be fixed after all operations are done.
|
||||
|
@@ -8,13 +8,15 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-governance-services-community-repo-parent</artifactId>
|
||||
<version>20.78</version>
|
||||
<version>20.112</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<app.amp.client.war.folder>${project.build.directory}/${project.build.finalName}-war</app.amp.client.war.folder>
|
||||
|
||||
<image.name>alfresco/alfresco-governance-repository-community-base</image.name>
|
||||
<base.image>alfresco/alfresco-community-repo-base</base.image>
|
||||
<scripts.directory>${project.parent.parent.parent.parent.basedir}/scripts</scripts.directory>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -537,9 +539,43 @@
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>build-docker-images</id>
|
||||
<!-- builds "image:latest" locally -->
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>io.fabric8</groupId>
|
||||
<artifactId>docker-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<images>
|
||||
<image>
|
||||
<name>${image.name}:${image.tag}</name>
|
||||
<build>
|
||||
<args>
|
||||
<BASE_IMAGE>${base.image}:${image.tag}</BASE_IMAGE>
|
||||
</args>
|
||||
<contextDir>${project.basedir}</contextDir>
|
||||
</build>
|
||||
</image>
|
||||
</images>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>build-image</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>build</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>build-docker-images</id>
|
||||
<!-- builds "image:latest" locally -->
|
||||
<id>build-multiarch-docker-images</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
@@ -548,20 +584,56 @@
|
||||
<configuration>
|
||||
<images>
|
||||
<image>
|
||||
<name>${image.name}:${image.tag}</name>
|
||||
<name>${local.registry}/${image.name}:${image.tag}</name>
|
||||
<build>
|
||||
<buildx>
|
||||
<platforms>
|
||||
<platform>linux/amd64</platform>
|
||||
<platform>linux/arm64</platform>
|
||||
</platforms>
|
||||
<builderName>${builder.name}</builderName>
|
||||
</buildx>
|
||||
<contextDir>${project.basedir}</contextDir>
|
||||
<args>
|
||||
<BASE_IMAGE>${local.registry}/${base.image}:${image.tag}</BASE_IMAGE>
|
||||
</args>
|
||||
</build>
|
||||
</image>
|
||||
</images>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>build-image</id>
|
||||
<id>build-push-image</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>build</goal>
|
||||
<goal>push</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>prepare-buildx</id>
|
||||
<phase>generate-sources</phase>
|
||||
<goals>
|
||||
<goal>exec</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<executable>${scripts.directory}/prepare_buildx.sh</executable>
|
||||
<arguments>
|
||||
<argument>${builder.name}</argument>
|
||||
<argument>${image.registry}</argument>
|
||||
<argument>${image.name}</argument>
|
||||
<argument>${image.tag}</argument>
|
||||
</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
@@ -578,12 +650,37 @@
|
||||
<images>
|
||||
<image>
|
||||
<!-- Quay image -->
|
||||
<name>${image.name}:${image.tag}</name>
|
||||
<registry>${image.registry}</registry>
|
||||
<name>${image.registry}/${image.name}:${image.tag}</name>
|
||||
<build>
|
||||
<buildx>
|
||||
<platforms>
|
||||
<platform>linux/amd64</platform>
|
||||
<platform>linux/arm64</platform>
|
||||
</platforms>
|
||||
<builderName>${builder.name}</builderName>
|
||||
</buildx>
|
||||
<args>
|
||||
<BASE_IMAGE>${local.registry}/${base.image}:${image.tag}</BASE_IMAGE>
|
||||
</args>
|
||||
<contextDir>${project.basedir}</contextDir>
|
||||
</build>
|
||||
</image>
|
||||
<image>
|
||||
<!-- DockerHub image -->
|
||||
<name>${image.name}:${image.tag}</name>
|
||||
<build>
|
||||
<buildx>
|
||||
<platforms>
|
||||
<platform>linux/amd64</platform>
|
||||
<platform>linux/arm64</platform>
|
||||
</platforms>
|
||||
<builderName>${builder.name}</builderName>
|
||||
</buildx>
|
||||
<args>
|
||||
<BASE_IMAGE>${local.registry}/${base.image}:${image.tag}</BASE_IMAGE>
|
||||
</args>
|
||||
<contextDir>${project.basedir}</contextDir>
|
||||
</build>
|
||||
</image>
|
||||
</images>
|
||||
</configuration>
|
||||
@@ -598,6 +695,28 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>prepare-buildx</id>
|
||||
<phase>generate-sources</phase>
|
||||
<goals>
|
||||
<goal>exec</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<executable>${scripts.directory}/prepare_buildx.sh</executable>
|
||||
<arguments>
|
||||
<argument>${builder.name}</argument>
|
||||
<argument>${image.registry}</argument>
|
||||
<argument>${image.name}</argument>
|
||||
<argument>${image.tag}</argument>
|
||||
</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-governance-services-community-repo-parent</artifactId>
|
||||
<version>20.78</version>
|
||||
<version>20.112</version>
|
||||
</parent>
|
||||
|
||||
<build>
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo</artifactId>
|
||||
<version>20.78</version>
|
||||
<version>20.112</version>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
|
@@ -8,7 +8,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo-amps</artifactId>
|
||||
<version>20.78</version>
|
||||
<version>20.112</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
@@ -121,12 +121,6 @@
|
||||
<version>${dependency.webscripts.version}</version>
|
||||
<classifier>tests</classifier>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>commons-collections</groupId>
|
||||
<artifactId>commons-collections</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo</artifactId>
|
||||
<version>20.78</version>
|
||||
<version>20.112</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
@@ -45,6 +45,13 @@ public class ListBackedPagingResults<R> implements PagingResults<R>
|
||||
size = list.size();
|
||||
hasMore = false;
|
||||
}
|
||||
|
||||
public ListBackedPagingResults(List<R> list, boolean hasMore)
|
||||
{
|
||||
this(list);
|
||||
this.hasMore = hasMore;
|
||||
}
|
||||
|
||||
public ListBackedPagingResults(List<R> list, PagingRequest paging)
|
||||
{
|
||||
// Excerpt
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo</artifactId>
|
||||
<version>20.78</version>
|
||||
<version>20.112</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo</artifactId>
|
||||
<version>20.78</version>
|
||||
<version>20.112</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
@@ -9,6 +9,6 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo-packaging</artifactId>
|
||||
<version>20.78</version>
|
||||
<version>20.112</version>
|
||||
</parent>
|
||||
</project>
|
||||
|
@@ -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-202209261711
|
||||
FROM alfresco/alfresco-base-tomcat:tomcat9-jre17-rockylinux8-202303081618
|
||||
|
||||
# Set default docker_context.
|
||||
ARG resource_path=target
|
||||
@@ -65,7 +65,7 @@ RUN sed -i -e "s_appender.rolling.fileName\=alfresco.log_appender.rolling.fileNa
|
||||
RUN yum install -y fontconfig-2.13.1-4.el8 \
|
||||
dejavu-fonts-common-2.35-7.el8 \
|
||||
fontpackages-filesystem-1.44-22.el8 \
|
||||
freetype-2.9.1-4.el8_3.1 \
|
||||
freetype-2.9.1-9.el8 \
|
||||
libpng-1.6.34-5.el8 \
|
||||
dejavu-sans-fonts-2.35-7.el8 && \
|
||||
yum clean all
|
||||
|
@@ -7,11 +7,12 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo-packaging</artifactId>
|
||||
<version>20.78</version>
|
||||
<version>20.112</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<image.name>alfresco/alfresco-community-repo-base</image.name>
|
||||
<scripts.directory>${project.parent.parent.basedir}/scripts</scripts.directory>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
@@ -156,6 +157,67 @@
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>build-multiarch-docker-images</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>io.fabric8</groupId>
|
||||
<artifactId>docker-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<images>
|
||||
<image>
|
||||
<name>${local.registry}/${image.name}:${image.tag}</name>
|
||||
<build>
|
||||
<buildx>
|
||||
<platforms>
|
||||
<platform>linux/amd64</platform>
|
||||
<platform>linux/arm64</platform>
|
||||
</platforms>
|
||||
<builderName>${builder.name}</builderName>
|
||||
</buildx>
|
||||
<contextDir>${project.basedir}</contextDir>
|
||||
</build>
|
||||
</image>
|
||||
</images>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>build-push-image</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>build</goal>
|
||||
<goal>push</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>prepare-buildx</id>
|
||||
<phase>generate-sources</phase>
|
||||
<goals>
|
||||
<goal>exec</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<executable>${scripts.directory}/prepare_buildx.sh</executable>
|
||||
<arguments>
|
||||
<argument>${builder.name}</argument>
|
||||
<argument>${image.registry}</argument>
|
||||
<argument>${image.name}</argument>
|
||||
<argument>${image.tag}</argument>
|
||||
</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>push-docker-images</id>
|
||||
<!-- publishes "image:latest" on Quay & DockerHub -->
|
||||
@@ -168,12 +230,29 @@
|
||||
<images>
|
||||
<!-- Quay image -->
|
||||
<image>
|
||||
<name>${image.name}:${image.tag}</name>
|
||||
<registry>${image.registry}</registry>
|
||||
<name>${image.registry}/${image.name}:${image.tag}</name>
|
||||
<build>
|
||||
<buildx>
|
||||
<platforms>
|
||||
<platform>linux/amd64</platform>
|
||||
<platform>linux/arm64</platform>
|
||||
</platforms>
|
||||
</buildx>
|
||||
<contextDir>${project.basedir}</contextDir>
|
||||
</build>
|
||||
</image>
|
||||
<!-- DockerHub image -->
|
||||
<image>
|
||||
<name>${image.name}:${image.tag}</name>
|
||||
<build>
|
||||
<buildx>
|
||||
<platforms>
|
||||
<platform>linux/amd64</platform>
|
||||
<platform>linux/arm64</platform>
|
||||
</platforms>
|
||||
</buildx>
|
||||
<contextDir>${project.basedir}</contextDir>
|
||||
</build>
|
||||
</image>
|
||||
</images>
|
||||
</configuration>
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo</artifactId>
|
||||
<version>20.78</version>
|
||||
<version>20.112</version>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo-packaging</artifactId>
|
||||
<version>20.78</version>
|
||||
<version>20.112</version>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo-tests</artifactId>
|
||||
<version>20.78</version>
|
||||
<version>20.112</version>
|
||||
</parent>
|
||||
|
||||
<organization>
|
||||
|
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo-tests</artifactId>
|
||||
<version>20.78</version>
|
||||
<version>20.112</version>
|
||||
</parent>
|
||||
|
||||
<developers>
|
||||
|
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo-tests</artifactId>
|
||||
<version>20.78</version>
|
||||
<version>20.112</version>
|
||||
</parent>
|
||||
|
||||
<developers>
|
||||
@@ -95,7 +95,6 @@
|
||||
<dependency>
|
||||
<groupId>com.jayway.jsonpath</groupId>
|
||||
<artifactId>json-path</artifactId>
|
||||
<version>${dependency.jakarta-json-path.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
@@ -8,7 +8,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo-tests</artifactId>
|
||||
<version>20.78</version>
|
||||
<version>20.112</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
@@ -165,14 +165,14 @@
|
||||
<dependency>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy</artifactId>
|
||||
<version>3.0.12</version>
|
||||
<version>3.0.16</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.codehaus.groovy/groovy-json-->
|
||||
<dependency>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy-json</artifactId>
|
||||
<version>3.0.12</version>
|
||||
<version>3.0.16</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
@@ -30,7 +30,10 @@ import static org.alfresco.utility.report.log.Step.STEP;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.alfresco.rest.core.IRestModelsCollection;
|
||||
import org.alfresco.utility.exception.TestConfigurationException;
|
||||
@@ -117,7 +120,7 @@ public class ModelsCollectionAssertion<C>
|
||||
return (C) modelCollection;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@SuppressWarnings("unchecked")
|
||||
public C entriesListDoesNotContain(String key, String value)
|
||||
{
|
||||
boolean exist = false;
|
||||
@@ -143,6 +146,53 @@ public class ModelsCollectionAssertion<C>
|
||||
return (C) modelCollection;
|
||||
}
|
||||
|
||||
public C entrySetContains(String key, String... expectedValues)
|
||||
{
|
||||
return entrySetContains(key, Arrays.stream(expectedValues).collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public C entrySetContains(String key, Collection<String> expectedValues)
|
||||
{
|
||||
Collection<String> actualValues = ((List<Model>) modelCollection.getEntries()).stream()
|
||||
.map(model -> extractValueAsString(model, key))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
Assert.assertTrue(actualValues.containsAll(expectedValues), String.format("Entry with key: \"%s\" is expected to contain values: %s, but actual values are: %s",
|
||||
key, expectedValues, actualValues));
|
||||
|
||||
return (C) modelCollection;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public C entrySetMatches(String key, Collection<String> expectedValues)
|
||||
{
|
||||
Collection<String> actualValues = ((List<Model>) modelCollection.getEntries()).stream()
|
||||
.map(model -> extractValueAsString(model, key))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
Assert.assertEqualsNoOrder(actualValues, expectedValues, String.format("Entry with key: \"%s\" is expected to match values: %s, but actual values are: %s",
|
||||
key, expectedValues, actualValues));
|
||||
|
||||
return (C) modelCollection;
|
||||
}
|
||||
|
||||
private String extractValueAsString(Model model, String key)
|
||||
{
|
||||
String fieldValue;
|
||||
Object modelObject = loadModel(model);
|
||||
try {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String jsonInString = mapper.writeValueAsString(modelObject);
|
||||
fieldValue = JsonPath.with(jsonInString).get(key);
|
||||
} catch (Exception e) {
|
||||
throw new TestConfigurationException(String.format(
|
||||
"You try to assert field [%s] that doesn't exist in class: [%s]. Exception: %s, Please check your code!",
|
||||
key, getClass().getCanonicalName(), e.getMessage()));
|
||||
}
|
||||
return fieldValue;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public C entriesListDoesNotContain(String key)
|
||||
{
|
||||
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* alfresco-tas-restapi
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2022 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -27,6 +27,8 @@ package org.alfresco.rest.model;
|
||||
|
||||
import static org.alfresco.utility.report.log.Step.STEP;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.alfresco.rest.core.IRestModel;
|
||||
@@ -73,4 +75,65 @@ public class RestTagModel extends TagModel implements IRestModel<RestTagModel>
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
RestTagModel tagModel = (RestTagModel) o;
|
||||
return Objects.equals(id, tagModel.id) && Objects.equals(tag, tagModel.tag) && Objects.equals(count, tagModel.count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hash(id, tag, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "RestTagModel{" + "id='" + id + ", tag='" + tag + '\'' + ", count=" + count + '\'' + '}';
|
||||
}
|
||||
|
||||
public static Builder builder()
|
||||
{
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder
|
||||
{
|
||||
private String id;
|
||||
private String tag;
|
||||
private Integer count;
|
||||
|
||||
public Builder id(String id)
|
||||
{
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder tag(String tag)
|
||||
{
|
||||
this.tag = tag;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder count(Integer count)
|
||||
{
|
||||
this.count = count;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RestTagModel create()
|
||||
{
|
||||
final RestTagModel tag = new RestTagModel();
|
||||
tag.setId(id);
|
||||
tag.setTag(this.tag);
|
||||
tag.setCount(count);
|
||||
return tag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -82,4 +82,15 @@ public class Tags extends ModelRequest<Tags>
|
||||
|
||||
return restWrapper.processModel(RestTagModel.class, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete tag.
|
||||
* - DELETE /tags/{tagId}
|
||||
*/
|
||||
public void deleteTag()
|
||||
{
|
||||
RestRequest request = RestRequest.
|
||||
simpleRequest(HttpMethod.DELETE, "/tags/{tagId}", tag.getId());
|
||||
restWrapper.processEmptyModel(request);
|
||||
}
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* alfresco-tas-restapi
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2022 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -25,6 +25,12 @@
|
||||
*/
|
||||
package org.alfresco.rest.requests.coreAPI;
|
||||
|
||||
import static org.alfresco.rest.core.JsonBodyGenerator.arrayToJson;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.restassured.RestAssured;
|
||||
import org.alfresco.rest.core.RestRequest;
|
||||
import org.alfresco.rest.core.RestWrapper;
|
||||
import org.alfresco.rest.model.RestCategoryModel;
|
||||
import org.alfresco.rest.model.RestDownloadsModel;
|
||||
@@ -49,8 +55,7 @@ import org.alfresco.rest.requests.Trashcan;
|
||||
import org.alfresco.utility.model.RepoTestModel;
|
||||
import org.alfresco.utility.model.SiteModel;
|
||||
import org.alfresco.utility.model.UserModel;
|
||||
|
||||
import io.restassured.RestAssured;
|
||||
import org.springframework.http.HttpMethod;
|
||||
|
||||
/**
|
||||
* Defines the entire Rest Core API
|
||||
@@ -172,6 +177,30 @@ public class RestCoreAPI extends ModelRequest<RestCoreAPI>
|
||||
return new Networks(restWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a single orphan tag.
|
||||
*
|
||||
* @param tag Tag model to create.
|
||||
* @return Created tag.
|
||||
*/
|
||||
public RestTagModel createSingleTag(RestTagModel tag)
|
||||
{
|
||||
RestRequest request = RestRequest.requestWithBody(HttpMethod.POST, tag.toJson(), "tags/");
|
||||
return restWrapper.processModel(RestTagModel.class, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create several orphan tags in one request.
|
||||
*
|
||||
* @param tags Tags models to create.
|
||||
* @return Created tags.
|
||||
*/
|
||||
public RestTagModelsCollection createTags(List<RestTagModel> tags)
|
||||
{
|
||||
RestRequest request = RestRequest.requestWithBody(HttpMethod.POST, arrayToJson(tags), "tags/");
|
||||
return restWrapper.processModels(RestTagModelsCollection.class, request);
|
||||
}
|
||||
|
||||
public Tags usingTag(RestTagModel tag)
|
||||
{
|
||||
return new Tags(tag, restWrapper);
|
||||
|
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2023 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.categories;
|
||||
|
||||
import static org.alfresco.utility.data.RandomData.getRandomName;
|
||||
import static org.alfresco.utility.report.log.Step.STEP;
|
||||
import static org.springframework.http.HttpStatus.CREATED;
|
||||
import static org.springframework.http.HttpStatus.OK;
|
||||
import static org.testng.Assert.assertTrue;
|
||||
|
||||
import org.alfresco.dataprep.CMISUtil;
|
||||
import org.alfresco.rest.model.RestCategoryModel;
|
||||
import org.alfresco.rest.model.RestCategoryModelsCollection;
|
||||
import org.alfresco.utility.Utility;
|
||||
import org.alfresco.utility.model.FileModel;
|
||||
import org.alfresco.utility.model.FolderModel;
|
||||
import org.alfresco.utility.model.SiteModel;
|
||||
import org.alfresco.utility.model.TestGroup;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class CategoriesCountTests extends CategoriesRestTest
|
||||
{
|
||||
|
||||
private RestCategoryModel categoryLinkedWithFolder;
|
||||
private RestCategoryModel categoryLinkedWithFile;
|
||||
private RestCategoryModel categoryLinkedWithBoth;
|
||||
private RestCategoryModel notLinkedCategory;
|
||||
|
||||
@BeforeClass(alwaysRun = true)
|
||||
public void dataPreparation() throws Exception
|
||||
{
|
||||
STEP("Create user and site");
|
||||
user = dataUser.createRandomTestUser();
|
||||
SiteModel site = dataSite.usingUser(user).createPublicRandomSite();
|
||||
|
||||
STEP("Create a folder, file in it and few categories");
|
||||
FolderModel folder = dataContent.usingUser(user).usingSite(site).createFolder();
|
||||
FileModel file = dataContent.usingUser(user).usingResource(folder).createContent(CMISUtil.DocumentType.TEXT_PLAIN);
|
||||
categoryLinkedWithFolder = prepareCategoryUnderRoot();
|
||||
categoryLinkedWithFile = prepareCategoryUnderRoot();
|
||||
categoryLinkedWithBoth = prepareCategoryUnder(prepareCategoryUnderRoot());
|
||||
notLinkedCategory = prepareCategoryUnderRoot();
|
||||
|
||||
STEP("Link folder and file to categories");
|
||||
linkContentToCategories(folder, categoryLinkedWithFolder, categoryLinkedWithBoth);
|
||||
linkContentToCategories(file, categoryLinkedWithFile, categoryLinkedWithBoth);
|
||||
|
||||
STEP("Wait for indexing to complete");
|
||||
Utility.sleep(1000, 60000, () -> restClient.authenticateUser(user)
|
||||
.withCoreAPI()
|
||||
.usingCategory(categoryLinkedWithFolder)
|
||||
.include(INCLUDE_COUNT_PARAM)
|
||||
.getCategory()
|
||||
.assertThat()
|
||||
.field(FIELD_COUNT)
|
||||
.isNot(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify count for a category linked with file and folder.
|
||||
*/
|
||||
@Test(groups = { TestGroup.REST_API })
|
||||
public void testGetCategoryById_includeCount()
|
||||
{
|
||||
STEP("Get linked category and verify if count is higher than 0");
|
||||
final RestCategoryModel actualCategory = restClient.authenticateUser(user)
|
||||
.withCoreAPI()
|
||||
.usingCategory(categoryLinkedWithBoth)
|
||||
.include(INCLUDE_COUNT_PARAM)
|
||||
.getCategory();
|
||||
|
||||
restClient.assertStatusCodeIs(OK);
|
||||
actualCategory.assertThat().field(FIELD_ID).is(categoryLinkedWithBoth.getId());
|
||||
actualCategory.assertThat().field(FIELD_COUNT).is(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify count for a category not linked with any content.
|
||||
*/
|
||||
@Test(groups = { TestGroup.REST_API })
|
||||
public void testGetCategoryById_includeCountForNonLinkedCategory()
|
||||
{
|
||||
STEP("Get non-linked category and verify if count is 0");
|
||||
final RestCategoryModel actualCategory = restClient.authenticateUser(user)
|
||||
.withCoreAPI()
|
||||
.usingCategory(notLinkedCategory)
|
||||
.include(INCLUDE_COUNT_PARAM)
|
||||
.getCategory();
|
||||
|
||||
restClient.assertStatusCodeIs(OK);
|
||||
actualCategory.assertThat().field(FIELD_ID).is(notLinkedCategory.getId());
|
||||
actualCategory.assertThat().field(FIELD_COUNT).is(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify count for three categories: linked with file, linked with folder and third not linked to any content.
|
||||
*/
|
||||
@Test(groups = { TestGroup.REST_API })
|
||||
public void testGetCategories_includeCount()
|
||||
{
|
||||
STEP("Get few categories and verify its counts");
|
||||
final RestCategoryModel parentCategory = createCategoryModelWithId(ROOT_CATEGORY_ID);
|
||||
final RestCategoryModelsCollection actualCategories = restClient.authenticateUser(user)
|
||||
.withCoreAPI()
|
||||
.usingCategory(parentCategory)
|
||||
.include(INCLUDE_COUNT_PARAM)
|
||||
.getCategoryChildren();
|
||||
|
||||
restClient.assertStatusCodeIs(OK);
|
||||
assertTrue(actualCategories.getEntries().stream()
|
||||
.map(RestCategoryModel::onModel)
|
||||
.anyMatch(category -> category.getId().equals(categoryLinkedWithFolder.getId()) && category.getCount() == 1));
|
||||
assertTrue(actualCategories.getEntries().stream()
|
||||
.map(RestCategoryModel::onModel)
|
||||
.anyMatch(category -> category.getId().equals(categoryLinkedWithFile.getId()) && category.getCount() == 1));
|
||||
assertTrue(actualCategories.getEntries().stream()
|
||||
.map(RestCategoryModel::onModel)
|
||||
.anyMatch(category -> category.getId().equals(notLinkedCategory.getId()) && category.getCount() == 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create category and verify that its count is 0.
|
||||
*/
|
||||
@Test(groups = { TestGroup.REST_API })
|
||||
public void testCreateCategory_includingCount()
|
||||
{
|
||||
STEP("Create a category under root and verify if count is 0");
|
||||
final String categoryName = getRandomName("Category");
|
||||
final RestCategoryModel rootCategory = createCategoryModelWithId(ROOT_CATEGORY_ID);
|
||||
final RestCategoryModel aCategory = createCategoryModelWithName(categoryName);
|
||||
final RestCategoryModel createdCategory = restClient.authenticateUser(dataUser.getAdminUser())
|
||||
.withCoreAPI()
|
||||
.include(INCLUDE_COUNT_PARAM)
|
||||
.usingCategory(rootCategory)
|
||||
.createSingleCategory(aCategory);
|
||||
|
||||
STEP("Create a category under root category (as admin)");
|
||||
restClient.assertStatusCodeIs(CREATED);
|
||||
createdCategory.assertThat().field(FIELD_NAME).is(categoryName);
|
||||
createdCategory.assertThat().field(FIELD_COUNT).is(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update category linked to file and folder and verify that its count is 2.
|
||||
*/
|
||||
@Test(groups = { TestGroup.REST_API })
|
||||
public void testUpdateCategory_includeCount()
|
||||
{
|
||||
STEP("Update linked category and verify if count is higher than 0");
|
||||
final String categoryNewName = getRandomName("NewCategoryName");
|
||||
final RestCategoryModel fixedCategoryModel = createCategoryModelWithName(categoryNewName);
|
||||
final RestCategoryModel updatedCategory = restClient.authenticateUser(dataUser.getAdminUser())
|
||||
.withCoreAPI()
|
||||
.usingCategory(categoryLinkedWithBoth)
|
||||
.include(INCLUDE_COUNT_PARAM)
|
||||
.updateCategory(fixedCategoryModel);
|
||||
|
||||
restClient.assertStatusCodeIs(OK);
|
||||
updatedCategory.assertThat().field(FIELD_ID).is(categoryLinkedWithBoth.getId());
|
||||
updatedCategory.assertThat().field(FIELD_COUNT).is(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update category not linked to any content and verify that its count is 0.
|
||||
*/
|
||||
@Test(groups = { TestGroup.REST_API })
|
||||
public void testUpdateCategory_includeCountForNonLinkedCategory()
|
||||
{
|
||||
STEP("Update non-linked category and verify if count is 0");
|
||||
final String categoryNewName = getRandomName("NewCategoryName");
|
||||
final RestCategoryModel fixedCategoryModel = createCategoryModelWithName(categoryNewName);
|
||||
final RestCategoryModel updatedCategory = restClient.authenticateUser(dataUser.getAdminUser())
|
||||
.withCoreAPI()
|
||||
.usingCategory(notLinkedCategory)
|
||||
.include(INCLUDE_COUNT_PARAM)
|
||||
.updateCategory(fixedCategoryModel);
|
||||
|
||||
restClient.assertStatusCodeIs(OK);
|
||||
updatedCategory.assertThat().field(FIELD_ID).is(notLinkedCategory.getId());
|
||||
updatedCategory.assertThat().field(FIELD_COUNT).is(0);
|
||||
}
|
||||
}
|
@@ -30,6 +30,7 @@ import static org.alfresco.utility.data.RandomData.getRandomName;
|
||||
import static org.alfresco.utility.report.log.Step.STEP;
|
||||
import static org.springframework.http.HttpStatus.CREATED;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
@@ -37,18 +38,21 @@ import java.util.stream.IntStream;
|
||||
import org.alfresco.rest.RestTest;
|
||||
import org.alfresco.rest.model.RestCategoryLinkBodyModel;
|
||||
import org.alfresco.rest.model.RestCategoryModel;
|
||||
import org.alfresco.rest.model.RestCategoryModelsCollection;
|
||||
import org.alfresco.utility.model.RepoTestModel;
|
||||
import org.alfresco.utility.model.UserModel;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
|
||||
abstract class CategoriesRestTest extends RestTest
|
||||
{
|
||||
protected static final String INCLUDE_COUNT_PARAM = "count";
|
||||
protected static final String ROOT_CATEGORY_ID = "-root-";
|
||||
protected static final String CATEGORY_NAME_PREFIX = "CategoryName";
|
||||
protected static final String FIELD_NAME = "name";
|
||||
protected static final String FIELD_ID = "id";
|
||||
protected static final String FIELD_PARENT_ID = "parentId";
|
||||
protected static final String FIELD_HAS_CHILDREN = "hasChildren";
|
||||
protected static final String FIELD_COUNT = "count";
|
||||
|
||||
protected UserModel user;
|
||||
|
||||
@@ -59,14 +63,26 @@ abstract class CategoriesRestTest extends RestTest
|
||||
user = dataUser.createRandomTestUser();
|
||||
}
|
||||
|
||||
protected RestCategoryModel prepareCategoryUnderRoot()
|
||||
protected RestCategoryModelsCollection linkContentToCategories(final RepoTestModel node, final RestCategoryModel... categories)
|
||||
{
|
||||
return prepareCategoryUnder(ROOT_CATEGORY_ID);
|
||||
final List<RestCategoryLinkBodyModel> categoryLinkModels = Arrays.stream(categories)
|
||||
.map(RestCategoryModel::getId)
|
||||
.map(this::createCategoryLinkModelWithId)
|
||||
.collect(Collectors.toList());
|
||||
final RestCategoryModelsCollection linkedCategories = restClient.authenticateUser(user).withCoreAPI().usingNode(node).linkToCategories(categoryLinkModels);
|
||||
|
||||
restClient.assertStatusCodeIs(CREATED);
|
||||
|
||||
return linkedCategories;
|
||||
}
|
||||
|
||||
protected RestCategoryModel prepareCategoryUnder(final String parentId)
|
||||
protected RestCategoryModel prepareCategoryUnderRoot()
|
||||
{
|
||||
return prepareCategoryUnder(createCategoryModelWithId(ROOT_CATEGORY_ID));
|
||||
}
|
||||
|
||||
protected RestCategoryModel prepareCategoryUnder(final RestCategoryModel parentCategory)
|
||||
{
|
||||
final RestCategoryModel parentCategory = createCategoryModelWithId(parentId);
|
||||
final RestCategoryModel categoryModel = createCategoryModelWithName(getRandomName(CATEGORY_NAME_PREFIX));
|
||||
final RestCategoryModel createdCategory = restClient.authenticateUser(dataUser.getAdminUser())
|
||||
.withCoreAPI()
|
||||
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2022 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
package org.alfresco.rest.categories;
|
||||
|
||||
import static org.alfresco.utility.data.RandomData.getRandomName;
|
||||
import static org.alfresco.utility.report.log.Step.STEP;
|
||||
import static org.springframework.http.HttpStatus.BAD_REQUEST;
|
||||
import static org.springframework.http.HttpStatus.CREATED;
|
||||
@@ -38,7 +39,6 @@ import java.util.stream.IntStream;
|
||||
|
||||
import org.alfresco.rest.model.RestCategoryModel;
|
||||
import org.alfresco.rest.model.RestCategoryModelsCollection;
|
||||
import org.alfresco.utility.data.RandomData;
|
||||
import org.alfresco.utility.model.FolderModel;
|
||||
import org.alfresco.utility.model.SiteModel;
|
||||
import org.alfresco.utility.model.TestGroup;
|
||||
@@ -56,7 +56,7 @@ public class CreateCategoriesTests extends CategoriesRestTest
|
||||
{
|
||||
STEP("Create a category under root category (as admin)");
|
||||
final RestCategoryModel rootCategory = createCategoryModelWithId(ROOT_CATEGORY_ID);
|
||||
final RestCategoryModel aCategory = createCategoryModelWithName(RandomData.getRandomName("Category"));
|
||||
final RestCategoryModel aCategory = createCategoryModelWithName(getRandomName("Category"));
|
||||
final RestCategoryModel createdCategory = restClient.authenticateUser(dataUser.getAdminUser())
|
||||
.withCoreAPI()
|
||||
.usingCategory(rootCategory)
|
||||
@@ -92,7 +92,7 @@ public class CreateCategoriesTests extends CategoriesRestTest
|
||||
{
|
||||
STEP("Create a category under root category (as admin)");
|
||||
final RestCategoryModel rootCategory = createCategoryModelWithId(ROOT_CATEGORY_ID);
|
||||
final RestCategoryModel aCategory = createCategoryModelWithName(RandomData.getRandomName("Category"));
|
||||
final RestCategoryModel aCategory = createCategoryModelWithName(getRandomName("Category"));
|
||||
final RestCategoryModel createdCategory = restClient.authenticateUser(dataUser.getAdminUser())
|
||||
.withCoreAPI()
|
||||
.usingCategory(rootCategory)
|
||||
@@ -140,7 +140,7 @@ public class CreateCategoriesTests extends CategoriesRestTest
|
||||
{
|
||||
STEP("Create a category under root category (as admin)");
|
||||
final RestCategoryModel rootCategory = createCategoryModelWithId(ROOT_CATEGORY_ID);
|
||||
final RestCategoryModel aCategory = createCategoryModelWithName(RandomData.getRandomName("Category"));
|
||||
final RestCategoryModel aCategory = createCategoryModelWithName(getRandomName("Category"));
|
||||
final RestCategoryModel createdCategory = restClient.authenticateUser(dataUser.getAdminUser())
|
||||
.withCoreAPI()
|
||||
.usingCategory(rootCategory)
|
||||
@@ -186,7 +186,7 @@ public class CreateCategoriesTests extends CategoriesRestTest
|
||||
{
|
||||
STEP("Create a category under root category (as user)");
|
||||
final RestCategoryModel rootCategory = createCategoryModelWithId(ROOT_CATEGORY_ID);
|
||||
final RestCategoryModel aCategory = createCategoryModelWithName(RandomData.getRandomName("Category"));
|
||||
final RestCategoryModel aCategory = createCategoryModelWithName(getRandomName("Category"));
|
||||
restClient.authenticateUser(user)
|
||||
.withCoreAPI()
|
||||
.usingCategory(rootCategory)
|
||||
@@ -203,7 +203,7 @@ public class CreateCategoriesTests extends CategoriesRestTest
|
||||
STEP("Create a category under non existing category node (as admin)");
|
||||
final String id = "non-existing-node-id";
|
||||
final RestCategoryModel rootCategory = createCategoryModelWithId(id);
|
||||
final RestCategoryModel aCategory = createCategoryModelWithName(RandomData.getRandomName("Category"));
|
||||
final RestCategoryModel aCategory = createCategoryModelWithName(getRandomName("Category"));
|
||||
restClient.authenticateUser(dataUser.getAdminUser())
|
||||
.withCoreAPI()
|
||||
.usingCategory(rootCategory)
|
||||
@@ -223,7 +223,7 @@ public class CreateCategoriesTests extends CategoriesRestTest
|
||||
|
||||
STEP("Create a category under folder node (as admin)");
|
||||
final RestCategoryModel rootCategory = createCategoryModelWithId(folder.getNodeRef());
|
||||
final RestCategoryModel aCategory = createCategoryModelWithName(RandomData.getRandomName("Category"));
|
||||
final RestCategoryModel aCategory = createCategoryModelWithName(getRandomName("Category"));
|
||||
restClient.authenticateUser(dataUser.getAdminUser())
|
||||
.withCoreAPI()
|
||||
.usingCategory(rootCategory)
|
||||
@@ -231,10 +231,31 @@ public class CreateCategoriesTests extends CategoriesRestTest
|
||||
restClient.assertStatusCodeIs(BAD_REQUEST).assertLastError().containsSummary("Node id does not refer to a valid category");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check weather count present in create category request will be ignored.
|
||||
*/
|
||||
@Test(groups = { TestGroup.REST_API })
|
||||
public void testCreateCategoryUnderRoot_verifyIfCountInRequestIsIgnored()
|
||||
{
|
||||
STEP("Try to create a category with filled count under root");
|
||||
final RestCategoryModel rootCategory = createCategoryModelWithId(ROOT_CATEGORY_ID);
|
||||
final RestCategoryModel aCategory = createCategoryModelWithName(getRandomName("Category"));
|
||||
aCategory.setCount(2);
|
||||
final RestCategoryModel createdCategory = restClient.authenticateUser(dataUser.getAdminUser())
|
||||
.withCoreAPI()
|
||||
.usingCategory(rootCategory)
|
||||
.include(INCLUDE_COUNT_PARAM)
|
||||
.createSingleCategory(aCategory);
|
||||
|
||||
restClient.assertStatusCodeIs(CREATED);
|
||||
createdCategory.assertThat().field(FIELD_NAME).is(aCategory.getName());
|
||||
createdCategory.assertThat().field(FIELD_COUNT).is(0);
|
||||
}
|
||||
|
||||
static List<RestCategoryModel> getCategoriesToCreate(final int count)
|
||||
{
|
||||
return IntStream.range(0, count)
|
||||
.mapToObj(i -> RestCategoryModel.builder().name(RandomData.getRandomName("SubCategory")).create())
|
||||
.mapToObj(i -> RestCategoryModel.builder().name(getRandomName("SubCategory")).create())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
@@ -79,7 +79,7 @@ public class UpdateCategoriesTests extends CategoriesRestTest
|
||||
final RestCategoryModel createdCategory = prepareCategoryUnderRoot();
|
||||
|
||||
STEP("Prepare as admin a subcategory of root's child category");
|
||||
final RestCategoryModel createdSubcategory = prepareCategoryUnder(createdCategory.getId());
|
||||
final RestCategoryModel createdSubcategory = prepareCategoryUnder(createdCategory);
|
||||
|
||||
STEP("Update as admin newly created subcategory");
|
||||
final String categoryNewName = getRandomName(CATEGORY_NEW_NAME_PREFIX);
|
||||
@@ -233,4 +233,27 @@ public class UpdateCategoriesTests extends CategoriesRestTest
|
||||
restClient.assertStatusCodeIs(OK);
|
||||
updatedCategory.assertThat().field(FIELD_NAME).is(categoryNewName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether count present in update category request will be ignored.
|
||||
*/
|
||||
@Test(groups = { TestGroup.REST_API })
|
||||
public void testUpdateCategory_verifyIfCountInRequestIsIgnored()
|
||||
{
|
||||
STEP("Prepare a category under root category");
|
||||
final RestCategoryModel createdCategory = prepareCategoryUnderRoot();
|
||||
|
||||
STEP("Try to update newly created category providing new name and count number");
|
||||
final RestCategoryModel fixedCategoryModel = createCategoryModelWithName(getRandomName(CATEGORY_NEW_NAME_PREFIX));
|
||||
fixedCategoryModel.setCount(2);
|
||||
final RestCategoryModel updatedCategory = restClient.authenticateUser(dataUser.getAdminUser())
|
||||
.withCoreAPI()
|
||||
.usingCategory(createdCategory)
|
||||
.include(INCLUDE_COUNT_PARAM)
|
||||
.updateCategory(fixedCategoryModel);
|
||||
|
||||
restClient.assertStatusCodeIs(OK);
|
||||
updatedCategory.assertThat().field(FIELD_ID).is(createdCategory.getId());
|
||||
updatedCategory.assertThat().field(FIELD_COUNT).is(0);
|
||||
}
|
||||
}
|
||||
|
@@ -14,7 +14,6 @@ import org.alfresco.utility.testrail.annotation.TestRail;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Ignore;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class AddFavoritesTests extends RestTest
|
||||
@@ -355,7 +354,6 @@ public class AddFavoritesTests extends RestTest
|
||||
@TestRail(section = { TestGroup.REST_API, TestGroup.FAVORITES }, executionType = ExecutionType.REGRESSION,
|
||||
description = "Verify add file favorite with tag id returns status code 404")
|
||||
@Test(groups = { TestGroup.REST_API, TestGroup.FAVORITES, TestGroup.REGRESSION })
|
||||
@Ignore
|
||||
public void addFileFavoriteUsingTagId() throws Exception
|
||||
{
|
||||
FileModel file = dataContent.usingSite(siteModel).usingUser(adminUserModel).createContent(CMISUtil.DocumentType.TEXT_PLAIN);
|
||||
|
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2023 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.tags;
|
||||
|
||||
import static org.alfresco.utility.data.RandomData.getRandomName;
|
||||
import static org.alfresco.utility.report.log.Step.STEP;
|
||||
import static org.springframework.http.HttpStatus.BAD_REQUEST;
|
||||
import static org.springframework.http.HttpStatus.CONFLICT;
|
||||
import static org.springframework.http.HttpStatus.CREATED;
|
||||
import static org.springframework.http.HttpStatus.FORBIDDEN;
|
||||
import static org.springframework.http.HttpStatus.OK;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import org.alfresco.rest.RestTest;
|
||||
import org.alfresco.rest.model.RestTagModel;
|
||||
import org.alfresco.rest.model.RestTagModelsCollection;
|
||||
import org.alfresco.utility.model.TestGroup;
|
||||
import org.alfresco.utility.model.UserModel;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class CreateTagsTests extends RestTest
|
||||
{
|
||||
private static final String FIELD_ID = "id";
|
||||
private static final String FIELD_TAG = "tag";
|
||||
private static final String FIELD_COUNT = "count";
|
||||
private static final String TAG_NAME_PREFIX = "tag-name";
|
||||
|
||||
private UserModel admin;
|
||||
private UserModel user;
|
||||
|
||||
@BeforeClass
|
||||
public void init()
|
||||
{
|
||||
admin = dataUser.getAdminUser();
|
||||
user = dataUser.createRandomTestUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if tag does not exist in the system, create one as admin and check if now it's there.
|
||||
*/
|
||||
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS })
|
||||
public void testCreateSingleTag()
|
||||
{
|
||||
STEP("Create single tag as admin");
|
||||
final RestTagModel tagModel = createTagModelWithName(getRandomName("99gat").toLowerCase());
|
||||
final RestTagModel createdTag = restClient.authenticateUser(admin).withCoreAPI().createSingleTag(tagModel);
|
||||
|
||||
restClient.assertStatusCodeIs(CREATED);
|
||||
createdTag.assertThat().field(FIELD_TAG).is(tagModel.getTag())
|
||||
.assertThat().field(FIELD_ID).isNotEmpty();
|
||||
|
||||
STEP("Verify that tag does exist in the system");
|
||||
RestTagModel tag = restClient.authenticateUser(admin).withCoreAPI().getTag(createdTag);
|
||||
restClient.assertStatusCodeIs(OK);
|
||||
tag.assertThat().isEqualTo(createdTag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create multiple orphan tags.
|
||||
*/
|
||||
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS })
|
||||
public void testCreateMultipleTags()
|
||||
{
|
||||
STEP("Create several tags as admin");
|
||||
final List<RestTagModel> tagModels = IntStream.range(0, 3)
|
||||
.mapToObj(i -> createTagModelWithName(getRandomName(TAG_NAME_PREFIX + "-" + i).toLowerCase()))
|
||||
.collect(Collectors.toList());
|
||||
final RestTagModelsCollection createdTags = restClient.authenticateUser(admin).withCoreAPI().createTags(tagModels);
|
||||
|
||||
restClient.assertStatusCodeIs(CREATED);
|
||||
IntStream.range(0, tagModels.size())
|
||||
.forEach(i -> createdTags.getEntries().get(i).onModel()
|
||||
.assertThat().field(FIELD_TAG).is(tagModels.get(i).getTag())
|
||||
.assertThat().field(FIELD_ID).isNotEmpty()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that tag name's case will be lowered.
|
||||
*/
|
||||
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS })
|
||||
public void testCreateSingleTag_usingUppercaseName()
|
||||
{
|
||||
STEP("Create single tag as admin using uppercase name");
|
||||
final RestTagModel tagModel = createTagModelWithName(getRandomName(TAG_NAME_PREFIX).toUpperCase());
|
||||
final RestTagModel createdTag = restClient.authenticateUser(admin).withCoreAPI().createSingleTag(tagModel);
|
||||
|
||||
restClient.assertStatusCodeIs(CREATED);
|
||||
createdTag.assertThat().field(FIELD_TAG).is(tagModel.getTag().toLowerCase())
|
||||
.assertThat().field(FIELD_ID).isNotEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to create few tags including repeating ones. Repeated tags should be omitted.
|
||||
*/
|
||||
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS })
|
||||
public void testCreateMultipleTags_withRepeatedName()
|
||||
{
|
||||
STEP("Create models of tags");
|
||||
final String repeatedTagName = getRandomName(TAG_NAME_PREFIX).toLowerCase();
|
||||
final List<RestTagModel> tagModels = List.of(
|
||||
createTagModelWithName(repeatedTagName),
|
||||
createTagModelWithName(getRandomName(TAG_NAME_PREFIX).toLowerCase()),
|
||||
createTagModelWithName(repeatedTagName)
|
||||
);
|
||||
|
||||
STEP("Create several tags skipping repeating names");
|
||||
final RestTagModelsCollection createdTags = restClient.authenticateUser(admin).withCoreAPI().createTags(tagModels);
|
||||
|
||||
restClient.assertStatusCodeIs(CREATED);
|
||||
createdTags.assertThat().entriesListCountIs(2);
|
||||
createdTags.assertThat().entriesListContains(FIELD_TAG, tagModels.get(0).getTag())
|
||||
.and().entriesListContains(FIELD_TAG, tagModels.get(1).getTag());
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to create a tag as a common user and expect 403 (Forbidden)
|
||||
*/
|
||||
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS })
|
||||
public void testCreateTag_asUser()
|
||||
{
|
||||
STEP("Try to create single tag as a common user and expect 403");
|
||||
final RestTagModel tagModel = createTagModelWithRandomName();
|
||||
restClient.authenticateUser(user).withCoreAPI().createSingleTag(tagModel);
|
||||
|
||||
restClient.assertStatusCodeIs(FORBIDDEN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to call create tag API passing empty list and expect 400 (Bad Request)
|
||||
*/
|
||||
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS })
|
||||
public void testCreateTags_passingEmptyList()
|
||||
{
|
||||
STEP("Pass empty list while creating tags and expect 400");
|
||||
restClient.authenticateUser(admin).withCoreAPI().createTags(Collections.emptyList());
|
||||
|
||||
restClient.assertStatusCodeIs(BAD_REQUEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to create a tag, which already exists in the system and expect 409 (Conflict)
|
||||
*/
|
||||
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS })
|
||||
public void testCreateTag_usingAlreadyExistingTagName()
|
||||
{
|
||||
STEP("Create some tag in the system");
|
||||
final RestTagModel tagToCreate = createTagModelWithRandomName();
|
||||
final RestTagModel alreadyExistingTag = prepareOrphanTag(tagToCreate);
|
||||
// set original name instead the case lowered one
|
||||
alreadyExistingTag.setTag(tagToCreate.getTag());
|
||||
|
||||
STEP("Try to use already existing tag to create duplicate and expect 409");
|
||||
restClient.authenticateUser(admin).withCoreAPI().createSingleTag(alreadyExistingTag);
|
||||
|
||||
restClient
|
||||
.assertStatusCodeIs(CONFLICT)
|
||||
.assertLastError().containsSummary("Duplicate child name not allowed: " + alreadyExistingTag.getTag().toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if count field is 0 for newly created tags.
|
||||
*/
|
||||
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS })
|
||||
public void testCreateTag_includingCount()
|
||||
{
|
||||
STEP("Create single tag as admin including count and verify if it is 0");
|
||||
final RestTagModel tagModel = createTagModelWithName(getRandomName(TAG_NAME_PREFIX).toLowerCase());
|
||||
final RestTagModel createdTag = restClient.authenticateUser(admin).withCoreAPI().include(FIELD_COUNT).createSingleTag(tagModel);
|
||||
|
||||
restClient.assertStatusCodeIs(CREATED);
|
||||
createdTag.assertThat().field(FIELD_TAG).is(tagModel.getTag())
|
||||
.assertThat().field(FIELD_ID).isNotEmpty()
|
||||
.assertThat().field(FIELD_COUNT).is(0);
|
||||
}
|
||||
|
||||
private RestTagModel prepareOrphanTagWithRandomName()
|
||||
{
|
||||
return prepareOrphanTag(createTagModelWithRandomName());
|
||||
}
|
||||
|
||||
private RestTagModel prepareOrphanTag(final RestTagModel tagModel)
|
||||
{
|
||||
final RestTagModel tag = restClient.authenticateUser(admin).withCoreAPI().createSingleTag(tagModel);
|
||||
restClient.assertStatusCodeIs(CREATED);
|
||||
return tag;
|
||||
}
|
||||
|
||||
private static RestTagModel createTagModelWithRandomName()
|
||||
{
|
||||
return createTagModelWithName(getRandomName(TAG_NAME_PREFIX));
|
||||
}
|
||||
|
||||
private static RestTagModel createTagModelWithName(final String tagName)
|
||||
{
|
||||
return RestTagModel.builder().tag(tagName).create();
|
||||
}
|
||||
}
|
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2023 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.tags;
|
||||
|
||||
import org.alfresco.dataprep.CMISUtil;
|
||||
import org.alfresco.rest.model.RestTagModel;
|
||||
import org.alfresco.utility.constants.UserRole;
|
||||
import org.alfresco.utility.model.TestGroup;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static org.alfresco.utility.report.log.Step.STEP;
|
||||
import static org.springframework.http.HttpStatus.FORBIDDEN;
|
||||
import static org.springframework.http.HttpStatus.NOT_FOUND;
|
||||
import static org.springframework.http.HttpStatus.NO_CONTENT;
|
||||
|
||||
public class DeleteTagsTests extends TagsDataPrep
|
||||
{
|
||||
/**
|
||||
* Check we can delete a tag by its id.
|
||||
*/
|
||||
@Test(groups = {TestGroup.REST_API})
|
||||
public void testDeleteTag()
|
||||
{
|
||||
STEP("Create a tag assigned to a document and send a request to delete it.");
|
||||
document = dataContent.usingUser(adminUserModel).usingSite(siteModel).createContent(CMISUtil.DocumentType.TEXT_PLAIN);
|
||||
RestTagModel aTag = createTagForDocument(document);
|
||||
|
||||
restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI().usingTag(aTag).deleteTag();
|
||||
restClient.assertStatusCodeIs(NO_CONTENT);
|
||||
|
||||
STEP("Ensure that the tag has been deleted by sending a GET request and receiving 404.");
|
||||
restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI().getTag(aTag);
|
||||
restClient.assertStatusCodeIs(NOT_FOUND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to delete a tag as a site manager and receive 403 error.
|
||||
* Other user roles have fewer permissions than a SiteManager and thus would also be forbidden from deleting a tag.
|
||||
*/
|
||||
@Test(groups = {TestGroup.REST_API})
|
||||
public void testDeleteTagAsSiteManager_andFail()
|
||||
{
|
||||
STEP("Create a tag assigned to a document and attempt to delete as a site manager");
|
||||
document = dataContent.usingUser(usersWithRoles.getOneUserWithRole(UserRole.SiteManager)).usingSite(siteModel).createContent(CMISUtil.DocumentType.TEXT_PLAIN);
|
||||
RestTagModel aTag = createTagForDocument(document);
|
||||
|
||||
restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteManager)).withCoreAPI().usingTag(aTag).deleteTag();
|
||||
restClient.assertStatusCodeIs(FORBIDDEN).assertLastError().containsSummary("Current user does not have permission to manage a tag");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check we receive 404 error when trying to delete a tag with a non-existent id
|
||||
*/
|
||||
@Test(groups = {TestGroup.REST_API})
|
||||
public void testDeleteNonExistentTag()
|
||||
{
|
||||
STEP("Attempt to delete tag with non-existent id and receive 404 error");
|
||||
final String id = "non-existing-dummy-id";
|
||||
final RestTagModel tagModel = createTagModelWithId(id);
|
||||
|
||||
restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI().usingTag(tagModel).deleteTag();
|
||||
restClient.assertStatusCodeIs(NOT_FOUND);
|
||||
}
|
||||
}
|
@@ -1,5 +1,9 @@
|
||||
package org.alfresco.rest.tags;
|
||||
|
||||
import static org.alfresco.utility.report.log.Step.STEP;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.rest.model.RestErrorModel;
|
||||
import org.alfresco.rest.model.RestTagModel;
|
||||
import org.alfresco.rest.model.RestTagModelsCollection;
|
||||
@@ -9,19 +13,11 @@ import org.alfresco.utility.model.TestGroup;
|
||||
import org.alfresco.utility.testrail.ExecutionType;
|
||||
import org.alfresco.utility.testrail.annotation.TestRail;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
@Test(groups = {TestGroup.REQUIRE_SOLR})
|
||||
public class GetTagsTests extends TagsDataPrep
|
||||
{
|
||||
|
||||
@BeforeClass(alwaysRun = true)
|
||||
public void dataPreparation() throws Exception
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.SANITY, description = "Verify user with Manager role gets tags using REST API and status code is OK (200)")
|
||||
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.SANITY })
|
||||
public void getTagsWithManagerRole() throws Exception
|
||||
@@ -192,7 +188,7 @@ public class GetTagsTests extends TagsDataPrep
|
||||
.and().field("hasMoreItems").is("false")
|
||||
.and().field("count").is("0")
|
||||
.and().field("skipCount").is(20000)
|
||||
.and().field("totalItems").isNull();
|
||||
.and().field("totalItems").is(0);
|
||||
}
|
||||
|
||||
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
|
||||
@@ -219,11 +215,128 @@ public class GetTagsTests extends TagsDataPrep
|
||||
RestTagModel deletedTag = restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteManager))
|
||||
.withCoreAPI().usingResource(document).addTag(removedTag);
|
||||
|
||||
restClient.withCoreAPI().usingResource(document).deleteTag(deletedTag);
|
||||
restClient.authenticateUser(adminUserModel).withCoreAPI().usingTag(deletedTag).deleteTag();
|
||||
restClient.assertStatusCodeIs(HttpStatus.NO_CONTENT);
|
||||
|
||||
returnedCollection = restClient.withParams("maxItems=10000").withCoreAPI().getTags();
|
||||
returnedCollection.assertThat().entriesListIsNotEmpty()
|
||||
.and().entriesListDoesNotContain("tag", removedTag.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if exact name filter can be applied.
|
||||
*/
|
||||
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
|
||||
public void testGetTags_withSingleNameFilter()
|
||||
{
|
||||
STEP("Get tags with names filter using EQUALS and expect one item in result");
|
||||
returnedCollection = restClient.authenticateUser(adminUserModel)
|
||||
.withParams("where=(tag='" + documentTag.getTag() + "')")
|
||||
.withCoreAPI()
|
||||
.getTags();
|
||||
|
||||
restClient.assertStatusCodeIs(HttpStatus.OK);
|
||||
returnedCollection.assertThat()
|
||||
.entrySetMatches("tag", Set.of(documentTagValue.toLowerCase()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if multiple names can be applied as a filter.
|
||||
*/
|
||||
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
|
||||
public void testGetTags_withTwoNameFilters()
|
||||
{
|
||||
STEP("Get tags with names filter using IN and expect two items in result");
|
||||
returnedCollection = restClient.authenticateUser(adminUserModel)
|
||||
.withParams("where=(tag IN ('" + documentTag.getTag() + "', '" + folderTag.getTag() + "'))")
|
||||
.withCoreAPI()
|
||||
.getTags();
|
||||
|
||||
restClient.assertStatusCodeIs(HttpStatus.OK);
|
||||
returnedCollection.assertThat()
|
||||
.entrySetMatches("tag", Set.of(documentTagValue.toLowerCase(), folderTagValue.toLowerCase()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if alike name filter can be applied.
|
||||
*/
|
||||
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
|
||||
public void testGetTags_whichNamesStartsWithOrphan()
|
||||
{
|
||||
STEP("Get tags with names filter using MATCHES and expect one item in result");
|
||||
returnedCollection = restClient.authenticateUser(adminUserModel)
|
||||
.withParams("where=(tag MATCHES ('orphan*'))")
|
||||
.withCoreAPI()
|
||||
.getTags();
|
||||
|
||||
restClient.assertStatusCodeIs(HttpStatus.OK);
|
||||
returnedCollection.assertThat()
|
||||
.entrySetContains("tag", orphanTag.getTag().toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that tags can be filtered by exact name and alike name at the same time.
|
||||
*/
|
||||
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
|
||||
public void testGetTags_withExactNameAndAlikeFilters()
|
||||
{
|
||||
STEP("Get tags with names filter using EQUALS and MATCHES and expect four items in result");
|
||||
returnedCollection = restClient.authenticateUser(adminUserModel)
|
||||
.withParams("where=(tag='" + orphanTag.getTag() + "' OR tag MATCHES ('*tag*'))")
|
||||
.withCoreAPI()
|
||||
.getTags();
|
||||
|
||||
restClient.assertStatusCodeIs(HttpStatus.OK);
|
||||
returnedCollection.assertThat()
|
||||
.entrySetContains("tag", documentTagValue.toLowerCase(), documentTagValue2.toLowerCase(), folderTagValue.toLowerCase(), orphanTag.getTag().toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if multiple alike filters can be applied.
|
||||
*/
|
||||
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
|
||||
public void testGetTags_withTwoAlikeFilters()
|
||||
{
|
||||
STEP("Get tags applying names filter using MATCHES twice and expect four items in result");
|
||||
returnedCollection = restClient.authenticateUser(adminUserModel)
|
||||
.withParams("where=(tag MATCHES ('orphan*') OR tag MATCHES ('tag*'))")
|
||||
.withCoreAPI()
|
||||
.getTags();
|
||||
|
||||
restClient.assertStatusCodeIs(HttpStatus.OK);
|
||||
returnedCollection.assertThat()
|
||||
.entrySetContains("tag", documentTagValue.toLowerCase(), documentTagValue2.toLowerCase(), folderTagValue.toLowerCase(), orphanTag.getTag().toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that providing incorrect field name in where query will result with 400 (Bad Request).
|
||||
*/
|
||||
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
|
||||
public void testGetTags_withWrongWherePropertyNameAndExpect400()
|
||||
{
|
||||
STEP("Try to get tags with names filter using EQUALS and wrong property name and expect 400");
|
||||
returnedCollection = restClient.authenticateUser(adminUserModel)
|
||||
.withParams("where=(name=gat)")
|
||||
.withCoreAPI()
|
||||
.getTags();
|
||||
|
||||
restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST)
|
||||
.assertLastError().containsSummary("Where query error: property with name: name is not expected");
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify tht AND operator is not supported in where query and expect 400 (Bad Request).
|
||||
*/
|
||||
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
|
||||
public void testGetTags_queryAndOperatorNotSupported()
|
||||
{
|
||||
STEP("Try to get tags applying names filter using AND operator and expect 400");
|
||||
returnedCollection = restClient.authenticateUser(adminUserModel)
|
||||
.withParams("where=(name=tag AND name IN ('tag-', 'gat'))")
|
||||
.withCoreAPI()
|
||||
.getTags();
|
||||
|
||||
restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST)
|
||||
.assertLastError().containsSummary("An invalid WHERE query was received. Unsupported Predicate");
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,5 @@
|
||||
package org.alfresco.rest.tags;
|
||||
|
||||
import java.util.Date;
|
||||
import org.alfresco.dataprep.CMISUtil;
|
||||
import org.alfresco.rest.RestTest;
|
||||
import org.alfresco.rest.model.RestTagModel;
|
||||
@@ -12,12 +11,9 @@ import org.alfresco.utility.data.RandomData;
|
||||
import org.alfresco.utility.model.FileModel;
|
||||
import org.alfresco.utility.model.FolderModel;
|
||||
import org.alfresco.utility.model.SiteModel;
|
||||
import org.alfresco.utility.model.TestGroup;
|
||||
import org.alfresco.utility.model.UserModel;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
@Test(groups = {TestGroup.REQUIRE_SOLR})
|
||||
public class TagsDataPrep extends RestTest
|
||||
{
|
||||
|
||||
@@ -28,13 +24,15 @@ public class TagsDataPrep extends RestTest
|
||||
protected static FileModel document;
|
||||
protected static FolderModel folder;
|
||||
protected static String documentTagValue, documentTagValue2, folderTagValue;
|
||||
protected static RestTagModel documentTag, documentTag2, folderTag, returnedModel;
|
||||
protected static RestTagModel documentTag, documentTag2, folderTag, orphanTag, returnedModel;
|
||||
protected static RestTagModelsCollection returnedCollection;
|
||||
|
||||
@BeforeClass
|
||||
public void init() throws Exception
|
||||
{
|
||||
//Create users
|
||||
adminUserModel = dataUser.getAdminUser();
|
||||
userModel = dataUser.createRandomTestUser();
|
||||
//Create public site
|
||||
siteModel = dataSite.usingUser(adminUserModel).createPublicRandomSite();
|
||||
usersWithRoles = dataUser.usingAdmin().addUsersWithRolesToSite(siteModel, UserRole.SiteManager, UserRole.SiteCollaborator, UserRole.SiteConsumer, UserRole.SiteContributor);
|
||||
@@ -49,15 +47,35 @@ public class TagsDataPrep extends RestTest
|
||||
documentTag = restClient.withCoreAPI().usingResource(document).addTag(documentTagValue);
|
||||
documentTag2 = restClient.withCoreAPI().usingResource(document).addTag(documentTagValue2);
|
||||
folderTag = restClient.withCoreAPI().usingResource(folder).addTag(folderTagValue);
|
||||
orphanTag = restClient.withCoreAPI().createSingleTag(RestTagModel.builder().tag(RandomData.getRandomName("orphan-tag")).create());
|
||||
|
||||
// Allow indexing to complete.
|
||||
Utility.sleep(500, 60000, () ->
|
||||
{
|
||||
returnedCollection = restClient.withParams("maxItems=10000").withCoreAPI().getTags();
|
||||
returnedCollection.assertThat().entriesListContains("tag", documentTagValue.toLowerCase())
|
||||
.and().entriesListContains("tag", documentTagValue2.toLowerCase())
|
||||
.and().entriesListContains("tag", folderTagValue.toLowerCase());
|
||||
});
|
||||
{
|
||||
returnedCollection = restClient.withParams("maxItems=10000", "where=(tag MATCHES ('*tag*'))")
|
||||
.withCoreAPI().getTags();
|
||||
returnedCollection.assertThat().entriesListContains("tag", documentTagValue.toLowerCase())
|
||||
.and().entriesListContains("tag", documentTagValue2.toLowerCase())
|
||||
.and().entriesListContains("tag", folderTagValue.toLowerCase());
|
||||
});
|
||||
}
|
||||
|
||||
protected RestTagModel createTagForDocument(FileModel document)
|
||||
{
|
||||
String documentTagValue = RandomData.getRandomName("tag");
|
||||
return restClient.withCoreAPI().usingResource(document).addTag(documentTagValue);
|
||||
}
|
||||
|
||||
protected RestTagModel createTagModelWithId(final String id)
|
||||
{
|
||||
return createTagModelWithIdAndName(id, RandomData.getRandomName("tag"));
|
||||
}
|
||||
|
||||
protected RestTagModel createTagModelWithIdAndName(final String id, final String tag)
|
||||
{
|
||||
return RestTagModel.builder()
|
||||
.id(id)
|
||||
.tag(tag)
|
||||
.create();
|
||||
}
|
||||
}
|
||||
|
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo-tests</artifactId>
|
||||
<version>20.78</version>
|
||||
<version>20.112</version>
|
||||
</parent>
|
||||
|
||||
<developers>
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo-packaging</artifactId>
|
||||
<version>20.78</version>
|
||||
<version>20.112</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
|
57
pom.xml
57
pom.xml
@@ -2,7 +2,7 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>alfresco-community-repo</artifactId>
|
||||
<version>20.78</version>
|
||||
<version>20.112</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>Alfresco Community Repo Parent</name>
|
||||
|
||||
@@ -35,6 +35,8 @@
|
||||
<build-number>local</build-number>
|
||||
<image.tag>latest</image.tag>
|
||||
<image.registry>quay.io</image.registry>
|
||||
<builder.name>entitled-builder</builder.name>
|
||||
<local.registry>127.0.0.1:5000</local.registry>
|
||||
|
||||
<java.version>11</java.version>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
@@ -45,7 +47,7 @@
|
||||
|
||||
<dependency.alfresco-hb-data-sender.version>1.0.12</dependency.alfresco-hb-data-sender.version>
|
||||
<dependency.alfresco-trashcan-cleaner.version>2.4.1</dependency.alfresco-trashcan-cleaner.version>
|
||||
<dependency.alfresco-jlan.version>7.2</dependency.alfresco-jlan.version>
|
||||
<dependency.alfresco-jlan.version>7.4</dependency.alfresco-jlan.version>
|
||||
<dependency.alfresco-server-root.version>6.0.1</dependency.alfresco-server-root.version>
|
||||
<dependency.alfresco-messaging-repo.version>1.2.20</dependency.alfresco-messaging-repo.version>
|
||||
<dependency.activiti-engine.version>5.23.0</dependency.activiti-engine.version>
|
||||
@@ -57,14 +59,14 @@
|
||||
|
||||
<dependency.spring.version>5.3.25</dependency.spring.version>
|
||||
<dependency.antlr.version>3.5.3</dependency.antlr.version>
|
||||
<dependency.jackson.version>2.14.0</dependency.jackson.version>
|
||||
<dependency.jackson.version>2.15.0-rc1</dependency.jackson.version>
|
||||
<dependency.cxf.version>3.5.5</dependency.cxf.version>
|
||||
<dependency.opencmis.version>1.0.0</dependency.opencmis.version>
|
||||
<dependency.webscripts.version>8.33</dependency.webscripts.version>
|
||||
<dependency.webscripts.version>8.38</dependency.webscripts.version>
|
||||
<dependency.bouncycastle.version>1.70</dependency.bouncycastle.version>
|
||||
<dependency.mockito-core.version>4.9.0</dependency.mockito-core.version>
|
||||
<dependency.assertj.version>3.24.2</dependency.assertj.version>
|
||||
<dependency.org-json.version>20220320</dependency.org-json.version>
|
||||
<dependency.org-json.version>20230227</dependency.org-json.version>
|
||||
<dependency.commons-dbcp.version>2.9.0</dependency.commons-dbcp.version>
|
||||
<dependency.commons-io.version>2.11.0</dependency.commons-io.version>
|
||||
<dependency.gson.version>2.8.9</dependency.gson.version>
|
||||
@@ -74,20 +76,20 @@
|
||||
<dependency.xercesImpl.version>2.12.2</dependency.xercesImpl.version>
|
||||
<dependency.slf4j.version>2.0.3</dependency.slf4j.version>
|
||||
<dependency.log4j.version>2.19.0</dependency.log4j.version>
|
||||
<dependency.gytheio.version>0.17</dependency.gytheio.version>
|
||||
<dependency.groovy.version>3.0.12</dependency.groovy.version>
|
||||
<dependency.gytheio.version>0.18</dependency.gytheio.version>
|
||||
<dependency.groovy.version>3.0.16</dependency.groovy.version>
|
||||
<dependency.tika.version>2.4.1</dependency.tika.version>
|
||||
<dependency.spring-security.version>5.7.5</dependency.spring-security.version>
|
||||
<dependency.spring-security.version>5.8.2</dependency.spring-security.version>
|
||||
<dependency.truezip.version>7.7.10</dependency.truezip.version>
|
||||
<dependency.poi.version>5.2.2</dependency.poi.version>
|
||||
<dependency.poi-ooxml-lite.version>5.2.3</dependency.poi-ooxml-lite.version>
|
||||
<dependency.keycloak.version>18.0.0</dependency.keycloak.version>
|
||||
<dependency.jboss.logging.version>3.5.0.Final</dependency.jboss.logging.version>
|
||||
<dependency.camel.version>3.18.2</dependency.camel.version> <!-- when bumping this version, please keep track/sync with included netty.io dependencies -->
|
||||
<dependency.netty.version>4.1.79.Final</dependency.netty.version> <!-- must be in sync with camels transitive dependencies, e.g.: netty-common -->
|
||||
<dependency.netty.qpid.version>4.1.72.Final</dependency.netty.qpid.version> <!-- must be in sync with camels transitive dependencies: native-unix-common/native-epoll/native-kqueue -->
|
||||
<dependency.netty-tcnative.version>2.0.53.Final</dependency.netty-tcnative.version> <!-- must be in sync with camels transitive dependencies -->
|
||||
<dependency.activemq.version>5.17.1</dependency.activemq.version>
|
||||
<dependency.camel.version>3.20.2</dependency.camel.version> <!-- when bumping this version, please keep track/sync with included netty.io dependencies -->
|
||||
<dependency.netty.version>4.1.87.Final</dependency.netty.version> <!-- must be in sync with camels transitive dependencies, e.g.: netty-common -->
|
||||
<dependency.netty.qpid.version>4.1.82.Final</dependency.netty.qpid.version> <!-- must be in sync with camels transitive dependencies: native-unix-common/native-epoll/native-kqueue -->
|
||||
<dependency.netty-tcnative.version>2.0.56.Final</dependency.netty-tcnative.version> <!-- must be in sync with camels transitive dependencies -->
|
||||
<dependency.activemq.version>5.17.4</dependency.activemq.version>
|
||||
<dependency.apache-compress.version>1.22</dependency.apache-compress.version>
|
||||
<dependency.apache.taglibs.version>1.2.5</dependency.apache.taglibs.version>
|
||||
<dependency.awaitility.version>4.2.0</dependency.awaitility.version>
|
||||
@@ -107,6 +109,7 @@
|
||||
<dependency.jakarta-mail-api.version>1.6.5</dependency.jakarta-mail-api.version>
|
||||
<dependency.jakarta-json-api.version>1.1.6</dependency.jakarta-json-api.version>
|
||||
<dependency.jakarta-json-path.version>2.7.0</dependency.jakarta-json-path.version>
|
||||
<dependency.json-smart.version>2.4.8</dependency.json-smart.version>
|
||||
<dependency.jakarta-rpc-api.version>1.1.4</dependency.jakarta-rpc-api.version>
|
||||
|
||||
<alfresco.googledrive.version>3.4.0-M1</alfresco.googledrive.version>
|
||||
@@ -120,7 +123,7 @@
|
||||
<dependency.mysql.version>8.0.30</dependency.mysql.version>
|
||||
<dependency.mysql-image.version>8</dependency.mysql-image.version>
|
||||
<dependency.mariadb.version>2.7.4</dependency.mariadb.version>
|
||||
<dependency.tas-utility.version>3.0.58</dependency.tas-utility.version>
|
||||
<dependency.tas-utility.version>3.0.61</dependency.tas-utility.version>
|
||||
<dependency.rest-assured.version>5.2.0</dependency.rest-assured.version>
|
||||
<dependency.tas-email.version>1.11</dependency.tas-email.version>
|
||||
<dependency.tas-webdav.version>1.7</dependency.tas-webdav.version>
|
||||
@@ -148,7 +151,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>20.78</tag>
|
||||
<tag>20.112</tag>
|
||||
</scm>
|
||||
|
||||
<distributionManagement>
|
||||
@@ -239,6 +242,17 @@
|
||||
<version>${dependency.jakarta-json-api.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.jayway.jsonpath</groupId>
|
||||
<artifactId>json-path</artifactId>
|
||||
<version>${dependency.jakarta-json-path.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId> net.minidev</groupId>
|
||||
<artifactId>json-smart</artifactId>
|
||||
<version>${dependency.json-smart.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.xml.rpc</groupId>
|
||||
<artifactId>jakarta.xml.rpc-api</artifactId>
|
||||
@@ -384,7 +398,7 @@
|
||||
<dependency>
|
||||
<groupId>commons-fileupload</groupId>
|
||||
<artifactId>commons-fileupload</artifactId>
|
||||
<version>1.4</version>
|
||||
<version>1.5</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -516,7 +530,7 @@
|
||||
<dependency>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
<version>1.32</version>
|
||||
<version>2.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
@@ -899,6 +913,13 @@
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-bom</artifactId>
|
||||
<version>${dependency.spring-security.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
@@ -916,7 +937,7 @@
|
||||
<plugin>
|
||||
<groupId>io.fabric8</groupId>
|
||||
<artifactId>docker-maven-plugin</artifactId>
|
||||
<version>0.40.2</version>
|
||||
<version>0.42.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo</artifactId>
|
||||
<version>20.78</version>
|
||||
<version>20.112</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
@@ -26,57 +26,101 @@
|
||||
|
||||
package org.alfresco.rest.api;
|
||||
|
||||
import static org.alfresco.service.cmr.repository.StoreRef.STORE_REF_WORKSPACE_SPACESSTORE;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.rest.api.model.Category;
|
||||
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
|
||||
import org.alfresco.rest.framework.resource.parameters.Parameters;
|
||||
import org.alfresco.service.Experimental;
|
||||
import org.alfresco.service.cmr.repository.StoreRef;
|
||||
|
||||
@Experimental
|
||||
public interface Categories
|
||||
{
|
||||
Category getCategoryById(String id, Parameters parameters);
|
||||
Category getCategoryById(StoreRef storeRef, String id, Parameters parameters);
|
||||
|
||||
List<Category> createSubcategories(String parentCategoryId, List<Category> categories, Parameters parameters);
|
||||
default Category getCategoryById(String id, Parameters parameters)
|
||||
{
|
||||
return getCategoryById(STORE_REF_WORKSPACE_SPACESSTORE, id, parameters);
|
||||
}
|
||||
|
||||
CollectionWithPagingInfo<Category> getCategoryChildren(String parentCategoryId, Parameters parameters);
|
||||
List<Category> createSubcategories(StoreRef storeRef, String parentCategoryId, List<Category> categories, Parameters parameters);
|
||||
|
||||
default List<Category> createSubcategories(String parentCategoryId, List<Category> categories, Parameters parameters)
|
||||
{
|
||||
return createSubcategories(STORE_REF_WORKSPACE_SPACESSTORE, parentCategoryId, categories, parameters);
|
||||
}
|
||||
|
||||
List<Category> getCategoryChildren(StoreRef storeRef, String parentCategoryId, Parameters parameters);
|
||||
|
||||
default List<Category> getCategoryChildren(String parentCategoryId, Parameters parameters)
|
||||
{
|
||||
return getCategoryChildren(STORE_REF_WORKSPACE_SPACESSTORE, parentCategoryId, parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update category by ID. Currently, it's possible only to update the name of category.
|
||||
*
|
||||
* @param storeRef Reference to node store.
|
||||
* @param id Category ID.
|
||||
* @param fixedCategoryModel Fixed category model.
|
||||
* @param parameters Additional parameters.
|
||||
* @return Updated category.
|
||||
*/
|
||||
Category updateCategoryById(String id, Category fixedCategoryModel);
|
||||
Category updateCategoryById(StoreRef storeRef, String id, Category fixedCategoryModel, Parameters parameters);
|
||||
|
||||
void deleteCategoryById(String id, Parameters parameters);
|
||||
default Category updateCategoryById(String id, Category fixedCategoryModel, Parameters parameters)
|
||||
{
|
||||
return updateCategoryById(STORE_REF_WORKSPACE_SPACESSTORE, id, fixedCategoryModel, parameters);
|
||||
}
|
||||
|
||||
void deleteCategoryById(StoreRef storeRef, String id, Parameters parameters);
|
||||
|
||||
default void deleteCategoryById(String id, Parameters parameters)
|
||||
{
|
||||
deleteCategoryById(STORE_REF_WORKSPACE_SPACESSTORE, id, parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get categories linked from node. Read permission on node is required.
|
||||
* Node type is restricted to specified vales from: {@link org.alfresco.util.TypeConstraint}.
|
||||
*
|
||||
* @param nodeId Node ID.
|
||||
* @param parameters Additional parameters.
|
||||
* @return Categories linked from node.
|
||||
*/
|
||||
List<Category> listCategoriesForNode(String nodeId);
|
||||
List<Category> listCategoriesForNode(String nodeId, Parameters parameters);
|
||||
|
||||
/**
|
||||
* Link node to categories. Change permission on node is required.
|
||||
* Node types allowed for categorization are specified within {@link org.alfresco.util.TypeConstraint}.
|
||||
*
|
||||
* @param storeRef Reference to node store.
|
||||
* @param nodeId Node ID.
|
||||
* @param categoryLinks Category IDs to which content should be linked to.
|
||||
* @param parameters Additional parameters.
|
||||
* @return Linked to categories.
|
||||
*/
|
||||
List<Category> linkNodeToCategories(String nodeId, List<Category> categoryLinks);
|
||||
List<Category> linkNodeToCategories(StoreRef storeRef, String nodeId, List<Category> categoryLinks, Parameters parameters);
|
||||
|
||||
default List<Category> linkNodeToCategories(String nodeId, List<Category> categoryLinks, Parameters parameters)
|
||||
{
|
||||
return linkNodeToCategories(STORE_REF_WORKSPACE_SPACESSTORE, nodeId, categoryLinks, parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink node from a category.
|
||||
*
|
||||
* @param storeRef Reference to node store.
|
||||
* @param nodeId Node ID.
|
||||
* @param categoryId Category ID from which content node should be unlinked from.
|
||||
* @param parameters Additional parameters.
|
||||
*/
|
||||
void unlinkNodeFromCategory(String nodeId, String categoryId, Parameters parameters);
|
||||
void unlinkNodeFromCategory(StoreRef storeRef, String nodeId, String categoryId, Parameters parameters);
|
||||
|
||||
default void unlinkNodeFromCategory(String nodeId, String categoryId, Parameters parameters)
|
||||
{
|
||||
unlinkNodeFromCategory(STORE_REF_WORKSPACE_SPACESSTORE, nodeId, categoryId, parameters);
|
||||
}
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -23,22 +23,35 @@
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.rest.api;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.rest.api.model.Tag;
|
||||
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
|
||||
import org.alfresco.rest.framework.resource.parameters.Paging;
|
||||
import org.alfresco.rest.framework.resource.parameters.Parameters;
|
||||
import org.alfresco.service.cmr.repository.StoreRef;
|
||||
|
||||
public interface Tags
|
||||
{
|
||||
public List<Tag> addTags(String nodeId, List<Tag> tags);
|
||||
public Tag getTag(StoreRef storeRef, String tagId);
|
||||
public void deleteTag(String nodeId, String tagId);
|
||||
public CollectionWithPagingInfo<Tag> getTags(StoreRef storeRef, Parameters params);
|
||||
public Tag changeTag(StoreRef storeRef, String tagId, Tag tag);
|
||||
public CollectionWithPagingInfo<Tag> getTags(String nodeId, Parameters params);
|
||||
}
|
||||
package org.alfresco.rest.api;
|
||||
|
||||
import static org.alfresco.service.cmr.repository.StoreRef.STORE_REF_WORKSPACE_SPACESSTORE;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.rest.api.model.Tag;
|
||||
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
|
||||
import org.alfresco.rest.framework.resource.parameters.Parameters;
|
||||
import org.alfresco.service.Experimental;
|
||||
import org.alfresco.service.cmr.repository.StoreRef;
|
||||
|
||||
public interface Tags
|
||||
{
|
||||
List<Tag> addTags(String nodeId, List<Tag> tags);
|
||||
Tag getTag(StoreRef storeRef, String tagId);
|
||||
void deleteTag(String nodeId, String tagId);
|
||||
CollectionWithPagingInfo<Tag> getTags(StoreRef storeRef, Parameters params);
|
||||
Tag changeTag(StoreRef storeRef, String tagId, Tag tag);
|
||||
CollectionWithPagingInfo<Tag> getTags(String nodeId, Parameters params);
|
||||
|
||||
@Experimental
|
||||
List<Tag> createTags(StoreRef storeRef, List<Tag> tags, Parameters parameters);
|
||||
|
||||
@Experimental
|
||||
default List<Tag> createTags(List<Tag> tags, Parameters parameters)
|
||||
{
|
||||
return createTags(STORE_REF_WORKSPACE_SPACESSTORE, tags, parameters);
|
||||
}
|
||||
|
||||
void deleteTagById(StoreRef storeRef, String tagId);
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -45,6 +45,7 @@ public class CategoriesEntityResource implements EntityResourceAction.ReadById<C
|
||||
EntityResourceAction.Update<Category>,
|
||||
EntityResourceAction.Delete
|
||||
{
|
||||
|
||||
private final Categories categories;
|
||||
|
||||
public CategoriesEntityResource(Categories categories)
|
||||
@@ -77,7 +78,7 @@ public class CategoriesEntityResource implements EntityResourceAction.ReadById<C
|
||||
@Override
|
||||
public Category update(String id, Category categoryModel, Parameters parameters)
|
||||
{
|
||||
return categories.updateCategoryById(id, categoryModel);
|
||||
return categories.updateCategoryById(id, categoryModel, parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -63,7 +63,7 @@ public class NodesCategoryLinksRelation implements RelationshipResourceAction.Cr
|
||||
@Override
|
||||
public CollectionWithPagingInfo<Category> readAll(String nodeId, Parameters parameters)
|
||||
{
|
||||
return ListPage.of(categories.listCategoriesForNode(nodeId), parameters.getPaging());
|
||||
return ListPage.of(categories.listCategoriesForNode(nodeId, parameters), parameters.getPaging());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,7 +77,7 @@ public class NodesCategoryLinksRelation implements RelationshipResourceAction.Cr
|
||||
@Override
|
||||
public List<Category> create(String nodeId, List<Category> categoryLinks, Parameters parameters)
|
||||
{
|
||||
return categories.linkNodeToCategories(nodeId, categoryLinks);
|
||||
return categories.linkNodeToCategories(nodeId, categoryLinks, parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,5 +93,4 @@ public class NodesCategoryLinksRelation implements RelationshipResourceAction.Cr
|
||||
{
|
||||
categories.unlinkNodeFromCategory(nodeId, categoryId, parameters);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2022 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -26,9 +26,8 @@
|
||||
|
||||
package org.alfresco.rest.api.categories;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.rest.api.Categories;
|
||||
import org.alfresco.rest.api.model.Category;
|
||||
@@ -36,6 +35,7 @@ import org.alfresco.rest.framework.WebApiDescription;
|
||||
import org.alfresco.rest.framework.resource.RelationshipResource;
|
||||
import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction;
|
||||
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
|
||||
import org.alfresco.rest.framework.resource.parameters.ListPage;
|
||||
import org.alfresco.rest.framework.resource.parameters.Parameters;
|
||||
|
||||
@RelationshipResource(name = "subcategories", entityResource = CategoriesEntityResource.class, title = "Subcategories")
|
||||
@@ -69,8 +69,8 @@ public class SubcategoriesRelation implements RelationshipResourceAction.Create<
|
||||
description = "Lists direct children of a parent category",
|
||||
successStatus = HttpServletResponse.SC_OK)
|
||||
@Override
|
||||
public CollectionWithPagingInfo<Category> readAll(String parentCategoryId, Parameters params)
|
||||
public CollectionWithPagingInfo<Category> readAll(String parentCategoryId, Parameters parameters)
|
||||
{
|
||||
return categories.getCategoryChildren(parentCategoryId, params);
|
||||
return ListPage.of(categories.getCategoryChildren(parentCategoryId, parameters), parameters.getPaging());
|
||||
}
|
||||
}
|
||||
|
@@ -46,10 +46,8 @@ import org.alfresco.rest.api.model.Category;
|
||||
import org.alfresco.rest.api.model.Node;
|
||||
import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
|
||||
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
|
||||
import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException;
|
||||
import org.alfresco.rest.framework.core.exceptions.InvalidNodeTypeException;
|
||||
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
|
||||
import org.alfresco.rest.framework.resource.parameters.ListPage;
|
||||
import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException;
|
||||
import org.alfresco.rest.framework.resource.parameters.Parameters;
|
||||
import org.alfresco.service.Experimental;
|
||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||
@@ -62,6 +60,7 @@ import org.alfresco.service.cmr.security.AuthorityService;
|
||||
import org.alfresco.service.cmr.security.PermissionService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.service.namespace.RegexQNamePattern;
|
||||
import org.alfresco.util.Pair;
|
||||
import org.alfresco.util.TypeConstraint;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -69,6 +68,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||
@Experimental
|
||||
public class CategoriesImpl implements Categories
|
||||
{
|
||||
static final String INCLUDE_COUNT_PARAM = "count";
|
||||
static final String NOT_A_VALID_CATEGORY = "Node id does not refer to a valid category";
|
||||
static final String NO_PERMISSION_TO_MANAGE_A_CATEGORY = "Current user does not have permission to manage a category";
|
||||
static final String NO_PERMISSION_TO_READ_CONTENT = "Current user does not have read permission to content";
|
||||
@@ -83,8 +83,8 @@ public class CategoriesImpl implements Categories
|
||||
private final PermissionService permissionService;
|
||||
private final TypeConstraint typeConstraint;
|
||||
|
||||
public CategoriesImpl(AuthorityService authorityService, CategoryService categoryService, Nodes nodes, NodeService nodeService, PermissionService permissionService,
|
||||
TypeConstraint typeConstraint)
|
||||
public CategoriesImpl(AuthorityService authorityService, CategoryService categoryService, Nodes nodes, NodeService nodeService,
|
||||
PermissionService permissionService, TypeConstraint typeConstraint)
|
||||
{
|
||||
this.authorityService = authorityService;
|
||||
this.categoryService = categoryService;
|
||||
@@ -95,63 +95,89 @@ public class CategoriesImpl implements Categories
|
||||
}
|
||||
|
||||
@Override
|
||||
public Category getCategoryById(final String id, final Parameters params)
|
||||
public Category getCategoryById(final StoreRef storeRef, final String id, final Parameters parameters)
|
||||
{
|
||||
final NodeRef nodeRef = getCategoryNodeRef(id);
|
||||
final NodeRef nodeRef = getCategoryNodeRef(storeRef, id);
|
||||
if (isRootCategory(nodeRef))
|
||||
{
|
||||
throw new InvalidArgumentException(NOT_A_VALID_CATEGORY, new String[]{id});
|
||||
}
|
||||
|
||||
return mapToCategory(nodeRef);
|
||||
final Category category = mapToCategory(nodeRef);
|
||||
|
||||
if (parameters.getInclude().contains(INCLUDE_COUNT_PARAM))
|
||||
{
|
||||
final Map<String, Integer> categoriesCount = getCategoriesCount(storeRef);
|
||||
category.setCount(categoriesCount.getOrDefault(category.getId(), 0));
|
||||
}
|
||||
|
||||
return category;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Category> createSubcategories(String parentCategoryId, List<Category> categories, Parameters parameters)
|
||||
public List<Category> createSubcategories(final StoreRef storeRef, final String parentCategoryId, final List<Category> categories, final Parameters parameters)
|
||||
{
|
||||
verifyAdminAuthority();
|
||||
final NodeRef parentNodeRef = getCategoryNodeRef(parentCategoryId);
|
||||
final List<NodeRef> categoryNodeRefs = categories.stream()
|
||||
final NodeRef parentNodeRef = getCategoryNodeRef(storeRef, parentCategoryId);
|
||||
|
||||
return categories.stream()
|
||||
.map(c -> createCategoryNodeRef(parentNodeRef, c))
|
||||
.collect(Collectors.toList());
|
||||
return categoryNodeRefs.stream()
|
||||
.map(this::mapToCategory)
|
||||
.peek(category -> {
|
||||
if (parameters.getInclude().contains(INCLUDE_COUNT_PARAM))
|
||||
{
|
||||
category.setCount(0);
|
||||
}
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CollectionWithPagingInfo<Category> getCategoryChildren(String parentCategoryId, Parameters params)
|
||||
public List<Category> getCategoryChildren(final StoreRef storeRef, final String parentCategoryId, final Parameters parameters)
|
||||
{
|
||||
final NodeRef parentNodeRef = getCategoryNodeRef(parentCategoryId);
|
||||
final List<ChildAssociationRef> childCategoriesAssocs = nodeService.getChildAssocs(parentNodeRef).stream()
|
||||
.filter(ca -> ContentModel.ASSOC_SUBCATEGORIES.equals(ca.getTypeQName()))
|
||||
.collect(Collectors.toList());
|
||||
final List<Category> categories = childCategoriesAssocs.stream()
|
||||
.map(c -> mapToCategory(c.getChildRef()))
|
||||
.collect(Collectors.toList());
|
||||
return ListPage.of(categories, params.getPaging());
|
||||
final NodeRef parentNodeRef = getCategoryNodeRef(storeRef, parentCategoryId);
|
||||
final List<Category> categories = nodeService.getChildAssocs(parentNodeRef).stream()
|
||||
.filter(ca -> ContentModel.ASSOC_SUBCATEGORIES.equals(ca.getTypeQName()))
|
||||
.map(ChildAssociationRef::getChildRef)
|
||||
.map(this::mapToCategory)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (parameters.getInclude().contains(INCLUDE_COUNT_PARAM))
|
||||
{
|
||||
final Map<String, Integer> categoriesCount = getCategoriesCount(storeRef);
|
||||
categories.forEach(category -> category.setCount(categoriesCount.getOrDefault(category.getId(), 0)));
|
||||
}
|
||||
|
||||
return categories;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Category updateCategoryById(final String id, final Category fixedCategoryModel)
|
||||
public Category updateCategoryById(final StoreRef storeRef, final String id, final Category fixedCategoryModel, final Parameters parameters)
|
||||
{
|
||||
verifyAdminAuthority();
|
||||
final NodeRef categoryNodeRef = getCategoryNodeRef(id);
|
||||
final NodeRef categoryNodeRef = getCategoryNodeRef(storeRef, id);
|
||||
if (isRootCategory(categoryNodeRef))
|
||||
{
|
||||
throw new InvalidArgumentException(NOT_A_VALID_CATEGORY, new String[]{id});
|
||||
}
|
||||
|
||||
validateCategoryFields(fixedCategoryModel);
|
||||
final Category category = mapToCategory(changeCategoryName(categoryNodeRef, fixedCategoryModel.getName()));
|
||||
|
||||
return mapToCategory(changeCategoryName(categoryNodeRef, fixedCategoryModel.getName()));
|
||||
if (parameters.getInclude().contains(INCLUDE_COUNT_PARAM))
|
||||
{
|
||||
final Map<String, Integer> categoriesCount = getCategoriesCount(storeRef);
|
||||
category.setCount(categoriesCount.getOrDefault(category.getId(), 0));
|
||||
}
|
||||
|
||||
return category;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteCategoryById(String id, Parameters parameters)
|
||||
public void deleteCategoryById(final StoreRef storeRef, final String id, final Parameters parameters)
|
||||
{
|
||||
verifyAdminAuthority();
|
||||
final NodeRef nodeRef = getCategoryNodeRef(id);
|
||||
final NodeRef nodeRef = getCategoryNodeRef(storeRef, id);
|
||||
if (isRootCategory(nodeRef))
|
||||
{
|
||||
throw new InvalidArgumentException(NOT_A_VALID_CATEGORY, new String[]{id});
|
||||
@@ -161,7 +187,7 @@ public class CategoriesImpl implements Categories
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Category> listCategoriesForNode(final String nodeId)
|
||||
public List<Category> listCategoriesForNode(final String nodeId, final Parameters parameters)
|
||||
{
|
||||
final NodeRef contentNodeRef = nodes.validateNode(nodeId);
|
||||
verifyReadPermission(contentNodeRef);
|
||||
@@ -178,7 +204,7 @@ public class CategoriesImpl implements Categories
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Category> linkNodeToCategories(final String nodeId, final List<Category> categoryLinks)
|
||||
public List<Category> linkNodeToCategories(final StoreRef storeRef, final String nodeId, final List<Category> categoryLinks, final Parameters parameters)
|
||||
{
|
||||
if (CollectionUtils.isEmpty(categoryLinks))
|
||||
{
|
||||
@@ -194,7 +220,7 @@ public class CategoriesImpl implements Categories
|
||||
.map(Category::getId)
|
||||
.filter(StringUtils::isNotEmpty)
|
||||
.distinct()
|
||||
.map(this::getCategoryNodeRef)
|
||||
.map(id -> getCategoryNodeRef(storeRef, id))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (CollectionUtils.isEmpty(categoryNodeRefs) || isRootCategoryPresent(categoryNodeRefs))
|
||||
@@ -208,9 +234,9 @@ public class CategoriesImpl implements Categories
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unlinkNodeFromCategory(final String nodeId, final String categoryId, Parameters parameters)
|
||||
public void unlinkNodeFromCategory(final StoreRef storeRef, final String nodeId, final String categoryId, final Parameters parameters)
|
||||
{
|
||||
final NodeRef categoryNodeRef = getCategoryNodeRef(categoryId);
|
||||
final NodeRef categoryNodeRef = getCategoryNodeRef(storeRef, categoryId);
|
||||
final NodeRef contentNodeRef = nodes.validateNode(nodeId);
|
||||
verifyChangePermission(contentNodeRef);
|
||||
verifyNodeType(contentNodeRef);
|
||||
@@ -272,13 +298,14 @@ public class CategoriesImpl implements Categories
|
||||
* This method gets category NodeRef for a given category id.
|
||||
* If '-root-' is passed as category id, then it's retrieved as a call to {@link org.alfresco.service.cmr.search.CategoryService#getRootCategoryNodeRef}
|
||||
* In all other cases it's retrieved as a node of a category type {@link #validateCategoryNode(String)}
|
||||
* @param storeRef Reference to node store.
|
||||
* @param nodeId category node id
|
||||
* @return NodRef of category node
|
||||
*/
|
||||
private NodeRef getCategoryNodeRef(String nodeId)
|
||||
private NodeRef getCategoryNodeRef(StoreRef storeRef, String nodeId)
|
||||
{
|
||||
return PATH_ROOT.equals(nodeId) ?
|
||||
categoryService.getRootCategoryNodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE)
|
||||
categoryService.getRootCategoryNodeRef(storeRef)
|
||||
.orElseThrow(() -> new EntityNotFoundException(nodeId)) :
|
||||
validateCategoryNode(nodeId);
|
||||
}
|
||||
@@ -434,4 +461,18 @@ public class CategoriesImpl implements Categories
|
||||
nodeService.setProperty(nodeRef, ContentModel.PROP_CATEGORIES, (Serializable) allCategories);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get categories by usage count. Result is a map of category IDs (short form - UUID) as key and usage count as value.
|
||||
*
|
||||
* @param storeRef Reference to node store.
|
||||
* @return Map of categories IDs and usage count.
|
||||
*/
|
||||
private Map<String, Integer> getCategoriesCount(final StoreRef storeRef)
|
||||
{
|
||||
final String idPrefix = storeRef + "/";
|
||||
return categoryService.getTopCategories(storeRef, ContentModel.ASPECT_GEN_CLASSIFIABLE, Integer.MAX_VALUE)
|
||||
.stream()
|
||||
.collect(Collectors.toMap(pair -> pair.getFirst().toString().replace(idPrefix, StringUtils.EMPTY), Pair::getSecond));
|
||||
}
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -23,122 +23,166 @@
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.rest.api.impl;
|
||||
|
||||
import java.util.AbstractList;
|
||||
package org.alfresco.rest.api.impl;
|
||||
|
||||
import static org.alfresco.rest.antlr.WhereClauseParser.EQUALS;
|
||||
import static org.alfresco.rest.antlr.WhereClauseParser.IN;
|
||||
import static org.alfresco.rest.antlr.WhereClauseParser.MATCHES;
|
||||
|
||||
import java.util.AbstractList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.alfresco.query.PagingResults;
|
||||
import org.alfresco.repo.tagging.NonExistentTagException;
|
||||
import org.alfresco.repo.tagging.TagExistsException;
|
||||
import org.alfresco.repo.tagging.TaggingException;
|
||||
import org.alfresco.rest.api.Nodes;
|
||||
import org.alfresco.rest.api.Tags;
|
||||
import org.alfresco.rest.api.model.Tag;
|
||||
import org.alfresco.rest.framework.core.exceptions.ConstraintViolatedException;
|
||||
import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
|
||||
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
|
||||
import org.alfresco.rest.framework.core.exceptions.NotFoundException;
|
||||
import org.alfresco.rest.framework.core.exceptions.UnsupportedResourceOperationException;
|
||||
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
|
||||
import org.alfresco.rest.framework.resource.parameters.Paging;
|
||||
import org.alfresco.rest.framework.resource.parameters.Parameters;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.StoreRef;
|
||||
import org.alfresco.service.cmr.tagging.TaggingService;
|
||||
import org.alfresco.util.Pair;
|
||||
import org.alfresco.util.TypeConstraint;
|
||||
|
||||
/**
|
||||
* Centralises access to tag services and maps between representations.
|
||||
*
|
||||
* @author steveglover
|
||||
* @since publicapi1.0
|
||||
*/
|
||||
public class TagsImpl implements Tags
|
||||
{
|
||||
private static final Object PARAM_INCLUDE_COUNT = "count";
|
||||
private Nodes nodes;
|
||||
private TaggingService taggingService;
|
||||
private TypeConstraint typeConstraint;
|
||||
|
||||
public void setTypeConstraint(TypeConstraint typeConstraint)
|
||||
{
|
||||
this.typeConstraint = typeConstraint;
|
||||
}
|
||||
|
||||
public void setNodes(Nodes nodes)
|
||||
{
|
||||
this.nodes = nodes;
|
||||
}
|
||||
|
||||
public void setTaggingService(TaggingService taggingService)
|
||||
{
|
||||
this.taggingService = taggingService;
|
||||
}
|
||||
|
||||
public List<Tag> addTags(String nodeId, final List<Tag> tags)
|
||||
{
|
||||
NodeRef nodeRef = nodes.validateNode(nodeId);
|
||||
if(!typeConstraint.matches(nodeRef))
|
||||
{
|
||||
throw new UnsupportedResourceOperationException("Cannot tag this node");
|
||||
}
|
||||
|
||||
List<String> tagValues = new AbstractList<String>()
|
||||
{
|
||||
@Override
|
||||
public String get(int arg0)
|
||||
{
|
||||
String tag = tags.get(arg0).getTag();
|
||||
return tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size()
|
||||
{
|
||||
return tags.size();
|
||||
}
|
||||
};
|
||||
try
|
||||
{
|
||||
List<Pair<String, NodeRef>> tagNodeRefs = taggingService.addTags(nodeRef, tagValues);
|
||||
List<Tag> ret = new ArrayList<Tag>(tags.size());
|
||||
for(Pair<String, NodeRef> pair : tagNodeRefs)
|
||||
{
|
||||
ret.add(new Tag(pair.getSecond(), pair.getFirst()));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
catch(IllegalArgumentException e)
|
||||
{
|
||||
throw new InvalidArgumentException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteTag(String nodeId, String tagId)
|
||||
{
|
||||
NodeRef nodeRef = nodes.validateNode(nodeId);
|
||||
getTag(tagId);
|
||||
NodeRef existingTagNodeRef = validateTag(tagId);
|
||||
String tagValue = taggingService.getTagName(existingTagNodeRef);
|
||||
taggingService.removeTag(nodeRef, tagValue);
|
||||
}
|
||||
|
||||
import org.alfresco.query.PagingResults;
|
||||
import org.alfresco.repo.tagging.NonExistentTagException;
|
||||
import org.alfresco.repo.tagging.TagExistsException;
|
||||
import org.alfresco.repo.tagging.TaggingException;
|
||||
import org.alfresco.rest.api.Nodes;
|
||||
import org.alfresco.rest.api.Tags;
|
||||
import org.alfresco.rest.api.model.Tag;
|
||||
import org.alfresco.rest.framework.core.exceptions.ConstraintViolatedException;
|
||||
import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
|
||||
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
|
||||
import org.alfresco.rest.framework.core.exceptions.NotFoundException;
|
||||
import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException;
|
||||
import org.alfresco.rest.framework.core.exceptions.UnsupportedResourceOperationException;
|
||||
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
|
||||
import org.alfresco.rest.framework.resource.parameters.Paging;
|
||||
import org.alfresco.rest.framework.resource.parameters.Parameters;
|
||||
import org.alfresco.rest.framework.resource.parameters.where.Query;
|
||||
import org.alfresco.rest.framework.resource.parameters.where.QueryHelper;
|
||||
import org.alfresco.rest.framework.resource.parameters.where.QueryImpl;
|
||||
import org.alfresco.service.Experimental;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.cmr.repository.StoreRef;
|
||||
import org.alfresco.service.cmr.security.AuthorityService;
|
||||
import org.alfresco.service.cmr.tagging.TaggingService;
|
||||
import org.alfresco.util.Pair;
|
||||
import org.alfresco.util.TypeConstraint;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Centralises access to tag services and maps between representations.
|
||||
*
|
||||
* @author steveglover
|
||||
* @since publicapi1.0
|
||||
*/
|
||||
public class TagsImpl implements Tags
|
||||
{
|
||||
private static final String PARAM_INCLUDE_COUNT = "count";
|
||||
private static final String PARAM_WHERE_TAG = "tag";
|
||||
static final String NOT_A_VALID_TAG = "An invalid parameter has been supplied";
|
||||
static final String NO_PERMISSION_TO_MANAGE_A_TAG = "Current user does not have permission to manage a tag";
|
||||
private final NodeRef tagParentNodeRef = new NodeRef("workspace://SpacesStore/tag:tag-root");
|
||||
|
||||
private Nodes nodes;
|
||||
private NodeService nodeService;
|
||||
private TaggingService taggingService;
|
||||
private TypeConstraint typeConstraint;
|
||||
private AuthorityService authorityService;
|
||||
|
||||
public void setTypeConstraint(TypeConstraint typeConstraint)
|
||||
{
|
||||
this.typeConstraint = typeConstraint;
|
||||
}
|
||||
|
||||
public void setNodes(Nodes nodes)
|
||||
{
|
||||
this.nodes = nodes;
|
||||
}
|
||||
public void setNodeService(NodeService nodeService)
|
||||
{
|
||||
this.nodeService = nodeService;
|
||||
}
|
||||
|
||||
public void setTaggingService(TaggingService taggingService)
|
||||
{
|
||||
this.taggingService = taggingService;
|
||||
}
|
||||
|
||||
public void setAuthorityService(AuthorityService authorityService)
|
||||
{
|
||||
this.authorityService = authorityService;
|
||||
}
|
||||
|
||||
public List<Tag> addTags(String nodeId, final List<Tag> tags)
|
||||
{
|
||||
NodeRef nodeRef = nodes.validateNode(nodeId);
|
||||
if(!typeConstraint.matches(nodeRef))
|
||||
{
|
||||
throw new UnsupportedResourceOperationException("Cannot tag this node");
|
||||
}
|
||||
|
||||
List<String> tagValues = new AbstractList<String>()
|
||||
{
|
||||
@Override
|
||||
public String get(int arg0)
|
||||
{
|
||||
String tag = tags.get(arg0).getTag();
|
||||
return tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size()
|
||||
{
|
||||
return tags.size();
|
||||
}
|
||||
};
|
||||
try
|
||||
{
|
||||
List<Pair<String, NodeRef>> tagNodeRefs = taggingService.addTags(nodeRef, tagValues);
|
||||
List<Tag> ret = new ArrayList<Tag>(tags.size());
|
||||
for(Pair<String, NodeRef> pair : tagNodeRefs)
|
||||
{
|
||||
ret.add(new Tag(pair.getSecond(), pair.getFirst()));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
catch(IllegalArgumentException e)
|
||||
{
|
||||
throw new InvalidArgumentException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteTag(String nodeId, String tagId)
|
||||
{
|
||||
NodeRef nodeRef = nodes.validateNode(nodeId);
|
||||
getTag(tagId);
|
||||
NodeRef existingTagNodeRef = validateTag(tagId);
|
||||
String tagValue = taggingService.getTagName(existingTagNodeRef);
|
||||
taggingService.removeTag(nodeRef, tagValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteTagById(StoreRef storeRef, String tagId) {
|
||||
verifyAdminAuthority();
|
||||
|
||||
NodeRef tagNodeRef = validateTag(storeRef, tagId);
|
||||
String tagValue = taggingService.getTagName(tagNodeRef);
|
||||
taggingService.deleteTag(storeRef, tagValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CollectionWithPagingInfo<Tag> getTags(StoreRef storeRef, Parameters params)
|
||||
{
|
||||
Paging paging = params.getPaging();
|
||||
PagingResults<Pair<NodeRef, String>> results = taggingService.getTags(storeRef, Util.getPagingRequest(paging));
|
||||
taggingService.getPagedTags(storeRef, 0, paging.getMaxItems());
|
||||
Paging paging = params.getPaging();
|
||||
Map<Integer, Collection<String>> namesFilters = resolveTagNamesQuery(params.getQuery());
|
||||
PagingResults<Pair<NodeRef, String>> results = taggingService.getTags(storeRef, Util.getPagingRequest(paging), namesFilters.get(EQUALS), namesFilters.get(MATCHES));
|
||||
|
||||
Integer totalItems = results.getTotalResultCount().getFirst();
|
||||
List<Pair<NodeRef, String>> page = results.getPage();
|
||||
List<Tag> tags = new ArrayList<Tag>(page.size());
|
||||
List<Pair<String, Integer>> tagsByCount = null;
|
||||
Map<String, Integer> tagsByCountMap = new HashMap<String, Integer>();
|
||||
|
||||
List<Tag> tags = new ArrayList<>(page.size());
|
||||
List<Pair<String, Integer>> tagsByCount;
|
||||
Map<String, Integer> tagsByCountMap = new HashMap<>();
|
||||
if (params.getInclude().contains(PARAM_INCLUDE_COUNT))
|
||||
{
|
||||
tagsByCount = taggingService.findTaggedNodesAndCountByTagName(storeRef);
|
||||
@@ -153,82 +197,150 @@ public class TagsImpl implements Tags
|
||||
for (Pair<NodeRef, String> pair : page)
|
||||
{
|
||||
Tag selectedTag = new Tag(pair.getFirst(), pair.getSecond());
|
||||
selectedTag.setCount(tagsByCountMap.get(selectedTag.getTag()));
|
||||
selectedTag.setCount(Optional.ofNullable(tagsByCountMap.get(selectedTag.getTag())).orElse(0));
|
||||
tags.add(selectedTag);
|
||||
}
|
||||
|
||||
return CollectionWithPagingInfo.asPaged(paging, tags, results.hasMoreItems(), (totalItems == null ? null : totalItems.intValue()));
|
||||
return CollectionWithPagingInfo.asPaged(paging, tags, results.hasMoreItems(), totalItems);
|
||||
}
|
||||
|
||||
public NodeRef validateTag(String tagId)
|
||||
{
|
||||
NodeRef tagNodeRef = nodes.validateNode(tagId);
|
||||
if(tagNodeRef == null)
|
||||
{
|
||||
throw new EntityNotFoundException(tagId);
|
||||
}
|
||||
return tagNodeRef;
|
||||
}
|
||||
|
||||
public NodeRef validateTag(StoreRef storeRef, String tagId)
|
||||
{
|
||||
NodeRef tagNodeRef = nodes.validateNode(storeRef, tagId);
|
||||
if(tagNodeRef == null)
|
||||
{
|
||||
throw new EntityNotFoundException(tagId);
|
||||
}
|
||||
return tagNodeRef;
|
||||
}
|
||||
|
||||
public Tag changeTag(StoreRef storeRef, String tagId, Tag tag)
|
||||
{
|
||||
try
|
||||
{
|
||||
NodeRef existingTagNodeRef = validateTag(storeRef, tagId);
|
||||
String existingTagName = taggingService.getTagName(existingTagNodeRef);
|
||||
String newTagName = tag.getTag();
|
||||
NodeRef newTagNodeRef = taggingService.changeTag(storeRef, existingTagName, newTagName);
|
||||
return new Tag(newTagNodeRef, newTagName);
|
||||
}
|
||||
catch(NonExistentTagException e)
|
||||
{
|
||||
throw new NotFoundException(e.getMessage());
|
||||
}
|
||||
catch(TagExistsException e)
|
||||
{
|
||||
throw new ConstraintViolatedException(e.getMessage());
|
||||
}
|
||||
catch(TaggingException e)
|
||||
{
|
||||
throw new InvalidArgumentException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public Tag getTag(String tagId)
|
||||
{
|
||||
return getTag(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, tagId);
|
||||
}
|
||||
|
||||
public Tag getTag(StoreRef storeRef, String tagId)
|
||||
{
|
||||
NodeRef tagNodeRef = validateTag(storeRef, tagId);
|
||||
String tagValue = taggingService.getTagName(tagNodeRef);
|
||||
return new Tag(tagNodeRef, tagValue);
|
||||
}
|
||||
|
||||
public CollectionWithPagingInfo<Tag> getTags(String nodeId, Parameters params)
|
||||
{
|
||||
NodeRef nodeRef = validateTag(nodeId);
|
||||
|
||||
PagingResults<Pair<NodeRef, String>> results = taggingService.getTags(nodeRef, Util.getPagingRequest(params.getPaging()));
|
||||
Integer totalItems = results.getTotalResultCount().getFirst();
|
||||
List<Pair<NodeRef, String>> page = results.getPage();
|
||||
List<Tag> tags = new ArrayList<Tag>(page.size());
|
||||
for(Pair<NodeRef, String> pair : page)
|
||||
{
|
||||
tags.add(new Tag(pair.getFirst(), pair.getSecond()));
|
||||
}
|
||||
|
||||
return CollectionWithPagingInfo.asPaged(params.getPaging(), tags, results.hasMoreItems(), (totalItems == null ? null : totalItems.intValue()));
|
||||
}
|
||||
}
|
||||
|
||||
public NodeRef validateTag(String tagId)
|
||||
{
|
||||
NodeRef tagNodeRef = nodes.validateNode(tagId);
|
||||
return checkTagRootAsNodePrimaryParent(tagId, tagNodeRef);
|
||||
}
|
||||
|
||||
public NodeRef validateTag(StoreRef storeRef, String tagId)
|
||||
{
|
||||
NodeRef tagNodeRef = nodes.validateNode(storeRef, tagId);
|
||||
return checkTagRootAsNodePrimaryParent(tagId, tagNodeRef);
|
||||
}
|
||||
|
||||
public Tag changeTag(StoreRef storeRef, String tagId, Tag tag)
|
||||
{
|
||||
try
|
||||
{
|
||||
NodeRef existingTagNodeRef = validateTag(storeRef, tagId);
|
||||
String existingTagName = taggingService.getTagName(existingTagNodeRef);
|
||||
String newTagName = tag.getTag();
|
||||
NodeRef newTagNodeRef = taggingService.changeTag(storeRef, existingTagName, newTagName);
|
||||
return new Tag(newTagNodeRef, newTagName);
|
||||
}
|
||||
catch(NonExistentTagException e)
|
||||
{
|
||||
throw new NotFoundException(e.getMessage());
|
||||
}
|
||||
catch(TagExistsException e)
|
||||
{
|
||||
throw new ConstraintViolatedException(e.getMessage());
|
||||
}
|
||||
catch(TaggingException e)
|
||||
{
|
||||
throw new InvalidArgumentException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public Tag getTag(String tagId)
|
||||
{
|
||||
return getTag(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, tagId);
|
||||
}
|
||||
|
||||
public Tag getTag(StoreRef storeRef, String tagId)
|
||||
{
|
||||
NodeRef tagNodeRef = validateTag(storeRef, tagId);
|
||||
String tagValue = taggingService.getTagName(tagNodeRef);
|
||||
return new Tag(tagNodeRef, tagValue);
|
||||
}
|
||||
|
||||
public CollectionWithPagingInfo<Tag> getTags(String nodeId, Parameters params)
|
||||
{
|
||||
NodeRef nodeRef = nodes.validateNode(nodeId);
|
||||
PagingResults<Pair<NodeRef, String>> results = taggingService.getTags(nodeRef, Util.getPagingRequest(params.getPaging()));
|
||||
Integer totalItems = results.getTotalResultCount().getFirst();
|
||||
List<Pair<NodeRef, String>> page = results.getPage();
|
||||
List<Tag> tags = new ArrayList<Tag>(page.size());
|
||||
for(Pair<NodeRef, String> pair : page)
|
||||
{
|
||||
tags.add(new Tag(pair.getFirst(), pair.getSecond()));
|
||||
}
|
||||
|
||||
return CollectionWithPagingInfo.asPaged(params.getPaging(), tags, results.hasMoreItems(), (totalItems == null ? null : totalItems.intValue()));
|
||||
}
|
||||
|
||||
@Experimental
|
||||
@Override
|
||||
public List<Tag> createTags(final StoreRef storeRef, final List<Tag> tags, final Parameters parameters)
|
||||
{
|
||||
verifyAdminAuthority();
|
||||
final List<String> tagNames = Optional.ofNullable(tags).orElse(Collections.emptyList()).stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(Tag::getTag)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (CollectionUtils.isEmpty(tagNames))
|
||||
{
|
||||
throw new InvalidArgumentException(NOT_A_VALID_TAG);
|
||||
}
|
||||
|
||||
return taggingService.createTags(storeRef, tagNames).stream()
|
||||
.map(pair -> Tag.builder().tag(pair.getFirst()).nodeRef(pair.getSecond()).create())
|
||||
.peek(tag -> {
|
||||
if (parameters.getInclude().contains(PARAM_INCLUDE_COUNT))
|
||||
{
|
||||
tag.setCount(0);
|
||||
}
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void verifyAdminAuthority()
|
||||
{
|
||||
if (!authorityService.hasAdminAuthority())
|
||||
{
|
||||
throw new PermissionDeniedException(NO_PERMISSION_TO_MANAGE_A_TAG);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method resolves where query looking for clauses: EQUALS, IN or MATCHES.
|
||||
* Expected values for EQUALS and IN will be merged under EQUALS clause.
|
||||
* @param namesQuery Where query with expected tag name(s).
|
||||
* @return Map of expected exact and alike tag names.
|
||||
*/
|
||||
private Map<Integer, Collection<String>> resolveTagNamesQuery(final Query namesQuery)
|
||||
{
|
||||
if (namesQuery == null || namesQuery == QueryImpl.EMPTY)
|
||||
{
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
final Map<Integer, Collection<String>> properties = QueryHelper
|
||||
.resolve(namesQuery)
|
||||
.usingOrOperator()
|
||||
.withoutNegations()
|
||||
.getProperty(PARAM_WHERE_TAG)
|
||||
.getExpectedValuesForAnyOf(EQUALS, IN, MATCHES)
|
||||
.skipNegated();
|
||||
|
||||
return properties.entrySet().stream()
|
||||
.collect(Collectors.groupingBy((entry) -> {
|
||||
if (entry.getKey() == EQUALS || entry.getKey() == IN)
|
||||
{
|
||||
return EQUALS;
|
||||
}
|
||||
else
|
||||
{
|
||||
return MATCHES;
|
||||
}
|
||||
}, Collectors.flatMapping((entry) -> entry.getValue().stream().map(String::toLowerCase), Collectors.toCollection(HashSet::new))));
|
||||
}
|
||||
|
||||
private NodeRef checkTagRootAsNodePrimaryParent(String tagId, NodeRef tagNodeRef)
|
||||
{
|
||||
if ( tagNodeRef == null || !nodeService.getPrimaryParent(tagNodeRef).getParentRef().equals(tagParentNodeRef))
|
||||
{
|
||||
throw new EntityNotFoundException(tagId);
|
||||
}
|
||||
return tagNodeRef;
|
||||
}
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2022 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -34,6 +34,7 @@ public class Category
|
||||
private String name;
|
||||
private String parentId;
|
||||
private boolean hasChildren;
|
||||
private Integer count;
|
||||
|
||||
public String getId()
|
||||
{
|
||||
@@ -80,20 +81,38 @@ public class Category
|
||||
this.hasChildren = hasChildren;
|
||||
}
|
||||
|
||||
public Integer getCount()
|
||||
{
|
||||
return count;
|
||||
}
|
||||
|
||||
public void setCount(Integer count)
|
||||
{
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
Category category = (Category) o;
|
||||
return hasChildren == category.hasChildren && Objects.equals(id, category.id) && name.equals(category.name) &&
|
||||
Objects.equals(parentId, category.parentId);
|
||||
return hasChildren == category.hasChildren && Objects.equals(id, category.id) && Objects.equals(name, category.name) && Objects.equals(parentId, category.parentId)
|
||||
&& Objects.equals(count, category.count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hash(id, name, parentId, hasChildren);
|
||||
return Objects.hash(id, name, parentId, hasChildren, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "Category{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", parentId='" + parentId + '\'' + ", hasChildren=" + hasChildren + ", count=" + count + '}';
|
||||
}
|
||||
|
||||
public static Builder builder()
|
||||
@@ -107,6 +126,7 @@ public class Category
|
||||
private String name;
|
||||
private String parentId;
|
||||
private boolean hasChildren;
|
||||
private Integer count;
|
||||
|
||||
public Builder id(String id)
|
||||
{
|
||||
@@ -132,6 +152,12 @@ public class Category
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder count(Integer count)
|
||||
{
|
||||
this.count = count;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Category create()
|
||||
{
|
||||
final Category category = new Category();
|
||||
@@ -139,6 +165,7 @@ public class Category
|
||||
category.setName(name);
|
||||
category.setParentId(parentId);
|
||||
category.setHasChildren(hasChildren);
|
||||
category.setCount(count);
|
||||
return category;
|
||||
}
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -25,6 +25,8 @@
|
||||
*/
|
||||
package org.alfresco.rest.api.model;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.alfresco.rest.framework.resource.UniqueId;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
@@ -99,42 +101,64 @@ public class Tag implements Comparable<Tag>
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((nodeRef == null) ? 0 : nodeRef.hashCode());
|
||||
return result;
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
Tag tag1 = (Tag) o;
|
||||
return Objects.equals(nodeRef, tag1.nodeRef) && Objects.equals(tag, tag1.tag) && Objects.equals(count, tag1.count);
|
||||
}
|
||||
|
||||
/*
|
||||
* Tags are equal if they have the same NodeRef
|
||||
*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
public int hashCode()
|
||||
{
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
Tag other = (Tag) obj;
|
||||
if (nodeRef == null) {
|
||||
if (other.nodeRef != null)
|
||||
return false;
|
||||
} else if (!nodeRef.equals(other.nodeRef))
|
||||
return false;
|
||||
return true;
|
||||
return Objects.hash(nodeRef, tag, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "Tag [nodeRef=" + nodeRef + ", tag=" + tag + "]";
|
||||
return "Tag{" + "nodeRef=" + nodeRef + ", tag='" + tag + '\'' + ", count=" + count + '}';
|
||||
}
|
||||
|
||||
public static Builder builder()
|
||||
{
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder
|
||||
{
|
||||
private NodeRef nodeRef;
|
||||
private String tag;
|
||||
private Integer count;
|
||||
|
||||
public Builder nodeRef(NodeRef nodeRef)
|
||||
{
|
||||
this.nodeRef = nodeRef;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder tag(String tag)
|
||||
{
|
||||
this.tag = tag;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder count(Integer count)
|
||||
{
|
||||
this.count = count;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Tag create()
|
||||
{
|
||||
final Tag tag = new Tag();
|
||||
tag.setNodeRef(nodeRef);
|
||||
tag.setTag(this.tag);
|
||||
tag.setCount(count);
|
||||
return tag;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,30 +1,33 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* 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%
|
||||
*/
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2023 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.tags;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.rest.api.Tags;
|
||||
import org.alfresco.rest.api.model.Tag;
|
||||
import org.alfresco.rest.framework.WebApiDescription;
|
||||
@@ -33,12 +36,14 @@ import org.alfresco.rest.framework.resource.EntityResource;
|
||||
import org.alfresco.rest.framework.resource.actions.interfaces.EntityResourceAction;
|
||||
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
|
||||
import org.alfresco.rest.framework.resource.parameters.Parameters;
|
||||
import org.alfresco.service.Experimental;
|
||||
import org.alfresco.service.cmr.repository.StoreRef;
|
||||
import org.alfresco.util.ParameterCheck;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
|
||||
@EntityResource(name="tags", title = "Tags")
|
||||
public class TagsEntityResource implements EntityResourceAction.Read<Tag>, EntityResourceAction.ReadById<Tag>, EntityResourceAction.Update<Tag>, InitializingBean
|
||||
public class TagsEntityResource implements EntityResourceAction.Read<Tag>,
|
||||
EntityResourceAction.ReadById<Tag>, EntityResourceAction.Update<Tag>, EntityResourceAction.Create<Tag>, EntityResourceAction.Delete, InitializingBean
|
||||
{
|
||||
private Tags tags;
|
||||
|
||||
@@ -54,9 +59,8 @@ public class TagsEntityResource implements EntityResourceAction.Read<Tag>, Entit
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Returns a paged list of all currently used tags in the store workspace://SpacesStore for the current tenant.
|
||||
*
|
||||
* GET /tags
|
||||
*/
|
||||
@Override
|
||||
@WebApiDescription(title="A paged list of all tags in the network.")
|
||||
@@ -77,4 +81,25 @@ public class TagsEntityResource implements EntityResourceAction.Read<Tag>, Entit
|
||||
{
|
||||
return tags.getTag(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /tags
|
||||
*/
|
||||
@Experimental
|
||||
@WebApiDescription(
|
||||
title = "Create an orphan tag",
|
||||
description = "Creates a tag, which is not associated with any node",
|
||||
successStatus = HttpServletResponse.SC_CREATED
|
||||
)
|
||||
@Override
|
||||
public List<Tag> create(List<Tag> tags, Parameters parameters)
|
||||
{
|
||||
return this.tags.createTags(tags, parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String id, Parameters parameters)
|
||||
{
|
||||
tags.deleteTagById(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, id);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2023 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.framework.resource.parameters.where;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.alfresco.rest.antlr.WhereClauseParser;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* Basic implementation of {@link QueryHelper.WalkerCallbackAdapter} providing universal handling of Where query clauses.
|
||||
* This implementation supports AND operator and all clause types.
|
||||
* Be default, walker verifies strictly if expected or unexpected properties, and it's comparison types are present in query
|
||||
* and throws {@link InvalidQueryException} if they are missing.
|
||||
*/
|
||||
public class BasicQueryWalker extends QueryHelper.WalkerCallbackAdapter
|
||||
{
|
||||
private static final String EQUALS_AND_IN_NOT_ALLOWED_TOGETHER = "Where query error: cannot use '=' (EQUALS) AND 'IN' clauses with same property: %s";
|
||||
private static final String MISSING_PROPERTY = "Where query error: property with name: %s not present";
|
||||
static final String MISSING_CLAUSE_TYPE = "Where query error: property with name: %s expects clause: %s";
|
||||
static final String MISSING_ANY_CLAUSE_OF_TYPE = "Where query error: property with name: %s expects at least one of clauses: %s";
|
||||
private static final String PROPERTY_NOT_EXPECTED = "Where query error: property with name: %s is not expected";
|
||||
private static final String PROPERTY_NOT_NEGATABLE = "Where query error: property with name: %s cannot be negated";
|
||||
private static final String PROPERTY_NAMES_EMPTY = "Cannot verify WHERE query without expected property names";
|
||||
|
||||
private Collection<String> expectedPropertyNames;
|
||||
private final Map<String, WhereProperty> properties;
|
||||
protected boolean clausesNegatable = true;
|
||||
protected boolean validateStrictly = true;
|
||||
|
||||
public BasicQueryWalker()
|
||||
{
|
||||
this.properties = new HashMap<>();
|
||||
}
|
||||
|
||||
public BasicQueryWalker(final String... expectedPropertyNames)
|
||||
{
|
||||
this();
|
||||
this.expectedPropertyNames = Set.of(expectedPropertyNames);
|
||||
}
|
||||
|
||||
public BasicQueryWalker(final Collection<String> expectedPropertyNames)
|
||||
{
|
||||
this();
|
||||
this.expectedPropertyNames = expectedPropertyNames;
|
||||
}
|
||||
|
||||
public void setClausesNegatable(final boolean clausesNegatable)
|
||||
{
|
||||
this.clausesNegatable = clausesNegatable;
|
||||
}
|
||||
|
||||
public void setValidateStrictly(boolean validateStrictly)
|
||||
{
|
||||
this.validateStrictly = validateStrictly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exists(String propertyName, boolean negated)
|
||||
{
|
||||
verifyPropertyExpectedness(propertyName);
|
||||
verifyClausesNegatability(negated, propertyName);
|
||||
addProperties(propertyName, WhereClauseParser.EXISTS, negated);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void between(String propertyName, String firstValue, String secondValue, boolean negated)
|
||||
{
|
||||
verifyPropertyExpectedness(propertyName);
|
||||
verifyClausesNegatability(negated, propertyName);
|
||||
addProperties(propertyName, WhereClauseParser.BETWEEN, negated, firstValue, secondValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void comparison(int type, String propertyName, String propertyValue, boolean negated)
|
||||
{
|
||||
verifyPropertyExpectedness(propertyName);
|
||||
verifyClausesNegatability(negated, propertyName);
|
||||
if (WhereClauseParser.EQUALS == type && isAndSupported() && containsProperty(propertyName, WhereClauseParser.IN, negated))
|
||||
{
|
||||
throw new InvalidQueryException(String.format(EQUALS_AND_IN_NOT_ALLOWED_TOGETHER, propertyName));
|
||||
}
|
||||
|
||||
addProperties(propertyName, type, negated, propertyValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void in(String propertyName, boolean negated, String... propertyValues)
|
||||
{
|
||||
verifyPropertyExpectedness(propertyName);
|
||||
verifyClausesNegatability(negated, propertyName);
|
||||
if (isAndSupported() && containsProperty(propertyName, WhereClauseParser.EQUALS, negated))
|
||||
{
|
||||
throw new InvalidQueryException(String.format(EQUALS_AND_IN_NOT_ALLOWED_TOGETHER, propertyName));
|
||||
}
|
||||
|
||||
addProperties(propertyName, WhereClauseParser.IN, negated, propertyValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void matches(final String propertyName, String propertyValue, boolean negated)
|
||||
{
|
||||
verifyPropertyExpectedness(propertyName);
|
||||
verifyClausesNegatability(negated, propertyName);
|
||||
addProperties(propertyName, WhereClauseParser.MATCHES, negated, propertyValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void and()
|
||||
{
|
||||
// Don't need to do anything here - it's enough to enable AND operator.
|
||||
// OR is not supported at the same time.
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if property is expected, if not throws {@link InvalidQueryException}.
|
||||
*/
|
||||
protected void verifyPropertyExpectedness(final String propertyName)
|
||||
{
|
||||
if (validateStrictly && CollectionUtils.isNotEmpty(expectedPropertyNames) && !this.expectedPropertyNames.contains(propertyName))
|
||||
{
|
||||
throw new InvalidQueryException(String.format(PROPERTY_NOT_EXPECTED, propertyName));
|
||||
}
|
||||
else if (validateStrictly && CollectionUtils.isEmpty(expectedPropertyNames))
|
||||
{
|
||||
throw new IllegalStateException(PROPERTY_NAMES_EMPTY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if clause negations are allowed, if not throws {@link InvalidQueryException}.
|
||||
*/
|
||||
protected void verifyClausesNegatability(final boolean negated, final String propertyName)
|
||||
{
|
||||
if (!clausesNegatable && negated)
|
||||
{
|
||||
throw new InvalidQueryException(String.format(PROPERTY_NOT_NEGATABLE, propertyName));
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isAndSupported()
|
||||
{
|
||||
try
|
||||
{
|
||||
and();
|
||||
return true;
|
||||
}
|
||||
catch (InvalidQueryException ignore)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected void addProperties(final String propertyName, final int clauseType, final String... propertyValues)
|
||||
{
|
||||
this.addProperties(propertyName, clauseType, false, propertyValues);
|
||||
}
|
||||
|
||||
protected void addProperties(final String propertyName, final int clauseType, final boolean negated, final String... propertyValues)
|
||||
{
|
||||
final WhereProperty.ClauseType type = WhereProperty.ClauseType.of(clauseType, negated);
|
||||
final Set<String> propertiesToAdd = Optional.ofNullable(propertyValues).map(Set::of).orElse(Collections.emptySet());
|
||||
if (this.containsProperty(propertyName))
|
||||
{
|
||||
this.properties.get(propertyName).addValuesToType(type, propertiesToAdd);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.properties.put(propertyName, new WhereProperty(propertyName, type, propertiesToAdd, validateStrictly));
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean containsProperty(final String propertyName)
|
||||
{
|
||||
return this.properties.containsKey(propertyName);
|
||||
}
|
||||
|
||||
protected boolean containsProperty(final String propertyName, final int clauseType, final boolean negated)
|
||||
{
|
||||
return this.properties.containsKey(propertyName) && this.properties.get(propertyName).containsType(clauseType, negated);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getProperty(String propertyName, int type, boolean negated)
|
||||
{
|
||||
return this.getProperty(propertyName).getExpectedValuesFor(type, negated);
|
||||
}
|
||||
|
||||
public WhereProperty getProperty(final String propertyName)
|
||||
{
|
||||
if (validateStrictly && !this.containsProperty(propertyName))
|
||||
{
|
||||
throw new InvalidQueryException(String.format(MISSING_PROPERTY, propertyName));
|
||||
}
|
||||
|
||||
return this.properties.get(propertyName);
|
||||
}
|
||||
|
||||
public List<WhereProperty> getProperties(final String... propertyNames)
|
||||
{
|
||||
return Arrays.stream(propertyNames)
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.distinct()
|
||||
.peek(propertyName -> {
|
||||
if (validateStrictly && !this.containsProperty(propertyName))
|
||||
{
|
||||
throw new InvalidQueryException(String.format(MISSING_PROPERTY, propertyName));
|
||||
}
|
||||
})
|
||||
.map(this.properties::get)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public Map<String, WhereProperty> getPropertiesAsMap(final String... propertyNames)
|
||||
{
|
||||
return Arrays.stream(propertyNames)
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.distinct()
|
||||
.peek(propertyName -> {
|
||||
if (validateStrictly && !this.containsProperty(propertyName))
|
||||
{
|
||||
throw new InvalidQueryException(String.format(MISSING_PROPERTY, propertyName));
|
||||
}
|
||||
})
|
||||
.collect(Collectors.toMap(propertyName -> propertyName, this.properties::get));
|
||||
}
|
||||
}
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -25,10 +25,19 @@
|
||||
*/
|
||||
package org.alfresco.rest.framework.resource.parameters.where;
|
||||
|
||||
import static org.alfresco.rest.antlr.WhereClauseParser.BETWEEN;
|
||||
import static org.alfresco.rest.antlr.WhereClauseParser.EQUALS;
|
||||
import static org.alfresco.rest.antlr.WhereClauseParser.EXISTS;
|
||||
import static org.alfresco.rest.antlr.WhereClauseParser.IN;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.alfresco.rest.antlr.WhereClauseParser;
|
||||
import org.antlr.runtime.tree.CommonTree;
|
||||
@@ -45,14 +54,19 @@ public abstract class QueryHelper
|
||||
/**
|
||||
* An interface used when walking a query tree. Calls are made to methods when the particular clause is encountered.
|
||||
*/
|
||||
public static interface WalkerCallback
|
||||
public interface WalkerCallback
|
||||
{
|
||||
InvalidQueryException UNSUPPORTED = new InvalidQueryException("Unsupported Predicate");
|
||||
|
||||
/**
|
||||
* Called any time an EXISTS clause is encountered.
|
||||
* @param propertyName Name of the property
|
||||
* @param negated returns true if "NOT EXISTS" was used
|
||||
*/
|
||||
void exists(String propertyName, boolean negated);
|
||||
default void exists(String propertyName, boolean negated)
|
||||
{
|
||||
throw UNSUPPORTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called any time a BETWEEN clause is encountered.
|
||||
@@ -61,12 +75,18 @@ public abstract class QueryHelper
|
||||
* @param secondValue String
|
||||
* @param negated returns true if "NOT BETWEEN" was used
|
||||
*/
|
||||
void between(String propertyName, String firstValue, String secondValue, boolean negated);
|
||||
default void between(String propertyName, String firstValue, String secondValue, boolean negated)
|
||||
{
|
||||
throw UNSUPPORTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* One of EQUALS LESSTHAN GREATERTHAN LESSTHANOREQUALS GREATERTHANOREQUALS;
|
||||
*/
|
||||
void comparison(int type, String propertyName, String propertyValue, boolean negated);
|
||||
default void comparison(int type, String propertyName, String propertyValue, boolean negated)
|
||||
{
|
||||
throw UNSUPPORTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called any time an IN clause is encountered.
|
||||
@@ -74,7 +94,10 @@ public abstract class QueryHelper
|
||||
* @param negated returns true if "NOT IN" was used
|
||||
* @param propertyValues the property values
|
||||
*/
|
||||
void in(String property, boolean negated, String... propertyValues);
|
||||
default void in(String property, boolean negated, String... propertyValues)
|
||||
{
|
||||
throw UNSUPPORTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called any time a MATCHES clause is encountered.
|
||||
@@ -82,42 +105,37 @@ public abstract class QueryHelper
|
||||
* @param propertyValue String
|
||||
* @param negated returns true if "NOT MATCHES" was used
|
||||
*/
|
||||
void matches(String property, String propertyValue, boolean negated);
|
||||
default void matches(String property, String propertyValue, boolean negated)
|
||||
{
|
||||
throw UNSUPPORTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called any time an AND is encountered.
|
||||
*/
|
||||
void and();
|
||||
*/
|
||||
default void and()
|
||||
{
|
||||
throw UNSUPPORTED;
|
||||
}
|
||||
/**
|
||||
* Called any time an OR is encountered.
|
||||
*/
|
||||
void or();
|
||||
*/
|
||||
default void or()
|
||||
{
|
||||
throw UNSUPPORTED;
|
||||
}
|
||||
|
||||
default Collection<String> getProperty(String propertyName, int type, boolean negated)
|
||||
{
|
||||
throw UNSUPPORTED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation. Override the methods you are interested in. If you don't
|
||||
* override the methods then an InvalidQueryException will be thrown.
|
||||
*/
|
||||
public static class WalkerCallbackAdapter implements WalkerCallback
|
||||
{
|
||||
private static final String UNSUPPORTED_TEXT = "Unsupported Predicate";
|
||||
protected static final InvalidQueryException UNSUPPORTED = new InvalidQueryException(UNSUPPORTED_TEXT);
|
||||
|
||||
@Override
|
||||
public void exists(String propertyName, boolean negated) { throw UNSUPPORTED;}
|
||||
@Override
|
||||
public void between(String propertyName, String firstValue, String secondValue, boolean negated) { throw UNSUPPORTED;}
|
||||
@Override
|
||||
public void comparison(int type, String propertyName, String propertyValue, boolean negated) { throw UNSUPPORTED;}
|
||||
@Override
|
||||
public void in(String propertyName, boolean negated, String... propertyValues) { throw UNSUPPORTED;}
|
||||
@Override
|
||||
public void matches(String property, String value, boolean negated) { throw UNSUPPORTED;}
|
||||
@Override
|
||||
public void and() {throw UNSUPPORTED;}
|
||||
@Override
|
||||
public void or() {throw UNSUPPORTED;}
|
||||
}
|
||||
public static class WalkerCallbackAdapter implements WalkerCallback {}
|
||||
|
||||
/**
|
||||
* Walks a query with a callback for each operation
|
||||
@@ -146,7 +164,7 @@ public abstract class QueryHelper
|
||||
if (tree != null)
|
||||
{
|
||||
switch (tree.getType()) {
|
||||
case WhereClauseParser.EXISTS:
|
||||
case EXISTS:
|
||||
if (WhereClauseParser.PROPERTYNAME == tree.getChild(0).getType())
|
||||
{
|
||||
callback.exists(tree.getChild(0).getText(), negated);
|
||||
@@ -160,7 +178,7 @@ public abstract class QueryHelper
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case WhereClauseParser.IN:
|
||||
case IN:
|
||||
if (WhereClauseParser.PROPERTYNAME == tree.getChild(0).getType())
|
||||
{
|
||||
List<Tree> children = getChildren(tree);
|
||||
@@ -174,14 +192,14 @@ public abstract class QueryHelper
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case WhereClauseParser.BETWEEN:
|
||||
case BETWEEN:
|
||||
if (WhereClauseParser.PROPERTYNAME == tree.getChild(0).getType())
|
||||
{
|
||||
callback.between(tree.getChild(0).getText(), stripQuotes(tree.getChild(1).getText()), stripQuotes(tree.getChild(2).getText()), negated);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case WhereClauseParser.EQUALS: //fall through (comparison)
|
||||
case EQUALS: //fall through (comparison)
|
||||
case WhereClauseParser.LESSTHAN: //fall through (comparison)
|
||||
case WhereClauseParser.GREATERTHAN: //fall through (comparison)
|
||||
case WhereClauseParser.LESSTHANOREQUALS: //fall through (comparison)
|
||||
@@ -286,4 +304,180 @@ public abstract class QueryHelper
|
||||
}
|
||||
return toBeStripped; //default to return the String unchanged.
|
||||
}
|
||||
|
||||
public static QueryResolver.WalkerSpecifier resolve(final Query query)
|
||||
{
|
||||
return new QueryResolver.WalkerSpecifier(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class allowing WHERE query resolving using query walker. By default {@link BasicQueryWalker} is used, but different walker can be supplied.
|
||||
*/
|
||||
public static abstract class QueryResolver<S extends QueryResolver<?>>
|
||||
{
|
||||
private final Query query;
|
||||
protected WalkerCallback queryWalker;
|
||||
protected Function<Collection<String>, BasicQueryWalker> orQueryWalkerSupplier;
|
||||
protected boolean clausesNegatable = true;
|
||||
protected boolean validateLeniently = false;
|
||||
protected abstract S self();
|
||||
|
||||
public QueryResolver(Query query)
|
||||
{
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get property expected values.
|
||||
* @param propertyName Property name.
|
||||
* @param clauseType Property comparison type.
|
||||
* @param negated Comparison type negation.
|
||||
* @return Map composed of all comparators and compared values.
|
||||
*/
|
||||
public Collection<String> getProperty(final String propertyName, final int clauseType, final boolean negated)
|
||||
{
|
||||
processQuery(propertyName);
|
||||
return queryWalker.getProperty(propertyName, clauseType, negated);
|
||||
}
|
||||
|
||||
protected void processQuery(final String... propertyNames)
|
||||
{
|
||||
if (queryWalker == null)
|
||||
{
|
||||
if (orQueryWalkerSupplier != null)
|
||||
{
|
||||
queryWalker = orQueryWalkerSupplier.apply(Set.of(propertyNames));
|
||||
}
|
||||
else
|
||||
{
|
||||
queryWalker = new BasicQueryWalker(propertyNames);
|
||||
}
|
||||
}
|
||||
if (queryWalker instanceof BasicQueryWalker)
|
||||
{
|
||||
((BasicQueryWalker) queryWalker).setClausesNegatable(clausesNegatable);
|
||||
((BasicQueryWalker) queryWalker).setValidateStrictly(!validateLeniently);
|
||||
}
|
||||
walk(query, queryWalker);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class providing methods related with default query walker {@link BasicQueryWalker}.
|
||||
*/
|
||||
public static class DefaultWalkerOperations<R extends DefaultWalkerOperations<?>> extends QueryResolver<R>
|
||||
{
|
||||
public DefaultWalkerOperations(Query query)
|
||||
{
|
||||
super(query);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected R self()
|
||||
{
|
||||
return (R) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies that query properties and comparison types should NOT be verified strictly.
|
||||
*/
|
||||
public R leniently()
|
||||
{
|
||||
this.validateLeniently = true;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies that clause types negations are not allowed in query.
|
||||
*/
|
||||
public R withoutNegations()
|
||||
{
|
||||
this.clausesNegatable = false;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get property with expected values.
|
||||
* @param propertyName Property name.
|
||||
* @return Map composed of all comparators and compared values.
|
||||
*/
|
||||
public WhereProperty getProperty(final String propertyName)
|
||||
{
|
||||
processQuery(propertyName);
|
||||
return ((BasicQueryWalker) this.queryWalker).getProperty(propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get multiple properties with it's expected values.
|
||||
* @param propertyNames Property names.
|
||||
* @return List of maps composed of all comparators and compared values.
|
||||
*/
|
||||
public List<WhereProperty> getProperties(final String... propertyNames)
|
||||
{
|
||||
processQuery(propertyNames);
|
||||
return ((BasicQueryWalker) this.queryWalker).getProperties(propertyNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get multiple properties with it's expected values.
|
||||
* @param propertyNames Property names.
|
||||
* @return Map composed of property names and maps composed of all comparators and compared values.
|
||||
*/
|
||||
public Map<String, WhereProperty> getPropertiesAsMap(final String... propertyNames)
|
||||
{
|
||||
processQuery(propertyNames);
|
||||
return ((BasicQueryWalker) this.queryWalker).getPropertiesAsMap(propertyNames);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class allowing to specify custom {@link WalkerCallback} implementation or {@link BasicQueryWalker} extension.
|
||||
*/
|
||||
public static class WalkerSpecifier extends DefaultWalkerOperations<WalkerSpecifier>
|
||||
{
|
||||
public WalkerSpecifier(Query query)
|
||||
{
|
||||
super(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected WalkerSpecifier self()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies that OR operator instead of AND should be used while resolving the query.
|
||||
*/
|
||||
public DefaultWalkerOperations<? extends DefaultWalkerOperations<?>> usingOrOperator()
|
||||
{
|
||||
this.orQueryWalkerSupplier = (propertyNames) -> new BasicQueryWalker(propertyNames)
|
||||
{
|
||||
@Override
|
||||
public void or() {/*Enable OR support, disable AND support*/}
|
||||
@Override
|
||||
public void and() {throw UNSUPPORTED;}
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to specify custom {@link BasicQueryWalker} extension, which should be used to resolve the query.
|
||||
*/
|
||||
public <T extends BasicQueryWalker> DefaultWalkerOperations<? extends DefaultWalkerOperations<?>> usingWalker(final T queryWalker)
|
||||
{
|
||||
this.queryWalker = queryWalker;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to specify custom {@link WalkerCallback} implementation, which should be used to resolve the query.
|
||||
*/
|
||||
public <T extends WalkerCallback> QueryResolver<? extends QueryResolver<?>> usingWalker(final T queryWalker)
|
||||
{
|
||||
this.queryWalker = queryWalker;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,351 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2023 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.framework.resource.parameters.where;
|
||||
|
||||
import static java.util.function.Predicate.not;
|
||||
|
||||
import static org.alfresco.rest.framework.resource.parameters.where.BasicQueryWalker.MISSING_ANY_CLAUSE_OF_TYPE;
|
||||
import static org.alfresco.rest.framework.resource.parameters.where.BasicQueryWalker.MISSING_CLAUSE_TYPE;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.alfresco.rest.antlr.WhereClauseParser;
|
||||
|
||||
/**
|
||||
* Map composed of property comparison type and compared values.
|
||||
* Map key is clause (comparison) type.
|
||||
*/
|
||||
public class WhereProperty extends HashMap<WhereProperty.ClauseType, Collection<String>>
|
||||
{
|
||||
private final String name;
|
||||
private boolean validateStrictly;
|
||||
|
||||
public WhereProperty(final String name, final ClauseType clauseType, final Collection<String> values)
|
||||
{
|
||||
super(Map.of(clauseType, new HashSet<>(values)));
|
||||
this.name = name;
|
||||
this.validateStrictly = true;
|
||||
}
|
||||
|
||||
public WhereProperty(final String name, final ClauseType clauseType, final Collection<String> values, final boolean validateStrictly)
|
||||
{
|
||||
this(name, clauseType, values);
|
||||
this.validateStrictly = validateStrictly;
|
||||
}
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
public void addValuesToType(final ClauseType clauseType, final Collection<String> values)
|
||||
{
|
||||
if (this.containsKey(clauseType))
|
||||
{
|
||||
this.get(clauseType).addAll(values);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.put(clauseType, new HashSet<>(values));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean containsType(final ClauseType clauseType)
|
||||
{
|
||||
return this.containsKey(clauseType);
|
||||
}
|
||||
|
||||
public boolean containsType(final int clauseType, final boolean negated)
|
||||
{
|
||||
return this.containsKey(ClauseType.of(clauseType, negated));
|
||||
}
|
||||
|
||||
public boolean containsAllTypes(final ClauseType... clauseType)
|
||||
{
|
||||
return Arrays.stream(clauseType).distinct().filter(this::containsKey).count() == clauseType.length;
|
||||
}
|
||||
|
||||
public boolean containsAnyOfTypes(final ClauseType... clauseType)
|
||||
{
|
||||
return Arrays.stream(clauseType).distinct().anyMatch(this::containsKey);
|
||||
}
|
||||
|
||||
public Collection<String> getExpectedValuesFor(final ClauseType clauseType)
|
||||
{
|
||||
verifyAllClausesPresence(clauseType);
|
||||
return this.get(clauseType);
|
||||
}
|
||||
|
||||
public HashMap<ClauseType, Collection<String>> getExpectedValuesForAllOf(final ClauseType... clauseTypes)
|
||||
{
|
||||
verifyAllClausesPresence(clauseTypes);
|
||||
return Arrays.stream(clauseTypes)
|
||||
.distinct()
|
||||
.collect(Collectors.toMap(type -> type, this::get, (type1, type2) -> type1, MultiTypeNegatableValuesMap::new));
|
||||
}
|
||||
|
||||
public HashMap<ClauseType, Collection<String>> getExpectedValuesForAnyOf(final ClauseType... clauseTypes)
|
||||
{
|
||||
verifyAnyClausesPresence(clauseTypes);
|
||||
return Arrays.stream(clauseTypes)
|
||||
.distinct()
|
||||
.collect(Collectors.toMap(type -> type, this::get, (type1, type2) -> type1, MultiTypeNegatableValuesMap::new));
|
||||
}
|
||||
|
||||
public Collection<String> getExpectedValuesFor(final int clauseType, final boolean negated)
|
||||
{
|
||||
verifyAllClausesPresence(ClauseType.of(clauseType, negated));
|
||||
return this.get(ClauseType.of(clauseType, negated));
|
||||
}
|
||||
|
||||
public NegatableValuesMap getExpectedValuesFor(final int clauseType)
|
||||
{
|
||||
verifyAllClausesPresence(clauseType);
|
||||
final NegatableValuesMap values = new NegatableValuesMap();
|
||||
final ClauseType type = ClauseType.of(clauseType);
|
||||
final ClauseType negatedType = type.negate();
|
||||
if (this.containsKey(type))
|
||||
{
|
||||
values.put(false, this.get(type));
|
||||
}
|
||||
if (this.containsKey(negatedType))
|
||||
{
|
||||
values.put(true, this.get(negatedType));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
public MultiTypeNegatableValuesMap getExpectedValuesForAllOf(final int... clauseTypes)
|
||||
{
|
||||
verifyAllClausesPresence(clauseTypes);
|
||||
return getExpectedValuesFor(clauseTypes);
|
||||
}
|
||||
|
||||
public MultiTypeNegatableValuesMap getExpectedValuesForAnyOf(final int... clauseTypes)
|
||||
{
|
||||
verifyAnyClausesPresence(clauseTypes);
|
||||
return getExpectedValuesFor(clauseTypes);
|
||||
}
|
||||
|
||||
private MultiTypeNegatableValuesMap getExpectedValuesFor(final int... clauseTypes)
|
||||
{
|
||||
final MultiTypeNegatableValuesMap values = new MultiTypeNegatableValuesMap();
|
||||
Arrays.stream(clauseTypes).distinct().forEach(clauseType -> {
|
||||
final ClauseType type = ClauseType.of(clauseType);
|
||||
final ClauseType negatedType = type.negate();
|
||||
if (this.containsKey(type))
|
||||
{
|
||||
values.put(type, this.get(type));
|
||||
}
|
||||
if (this.containsKey(negatedType))
|
||||
{
|
||||
values.put(negatedType, this.get(negatedType));
|
||||
}
|
||||
});
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if all specified clause types are present in this map, if not than throw {@link InvalidQueryException}.
|
||||
*/
|
||||
private void verifyAllClausesPresence(final ClauseType... clauseTypes)
|
||||
{
|
||||
if (validateStrictly)
|
||||
{
|
||||
Arrays.stream(clauseTypes).distinct().forEach(clauseType -> {
|
||||
if (!this.containsType(clauseType))
|
||||
{
|
||||
throw new InvalidQueryException(String.format(MISSING_CLAUSE_TYPE, this.name, WhereClauseParser.tokenNames[clauseType.getTypeNumber()]));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if all specified clause types are present in this map, if not than throw {@link InvalidQueryException}.
|
||||
* Exception is thrown when both, negated and non-negated types are missing.
|
||||
*/
|
||||
private void verifyAllClausesPresence(final int... clauseTypes)
|
||||
{
|
||||
if (validateStrictly)
|
||||
{
|
||||
Arrays.stream(clauseTypes).distinct().forEach(clauseType -> {
|
||||
if (!this.containsType(clauseType, false) && !this.containsType(clauseType, true))
|
||||
{
|
||||
throw new InvalidQueryException(String.format(MISSING_CLAUSE_TYPE, this.name, WhereClauseParser.tokenNames[clauseType]));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if any of specified clause types are present in this map, if not than throw {@link InvalidQueryException}.
|
||||
*/
|
||||
private void verifyAnyClausesPresence(final ClauseType... clauseTypes)
|
||||
{
|
||||
if (validateStrictly)
|
||||
{
|
||||
if (!this.containsAnyOfTypes(clauseTypes))
|
||||
{
|
||||
throw new InvalidQueryException(String.format(MISSING_ANY_CLAUSE_OF_TYPE,
|
||||
this.name, Arrays.stream(clauseTypes).map(type -> WhereClauseParser.tokenNames[type.getTypeNumber()]).collect(Collectors.toList())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if any of specified clause types are present in this map, if not than throw {@link InvalidQueryException}.
|
||||
* Exception is thrown when both, negated and non-negated types are missing.
|
||||
*/
|
||||
private void verifyAnyClausesPresence(final int... clauseTypes)
|
||||
{
|
||||
if (validateStrictly)
|
||||
{
|
||||
final Collection<ClauseType> expectedTypes = Arrays.stream(clauseTypes)
|
||||
.distinct()
|
||||
.boxed()
|
||||
.flatMap(type -> Stream.of(ClauseType.of(type), ClauseType.of(type, true)))
|
||||
.collect(Collectors.toSet());
|
||||
if (!this.containsAnyOfTypes(expectedTypes.toArray(ClauseType[]::new)))
|
||||
{
|
||||
throw new InvalidQueryException(String.format(MISSING_ANY_CLAUSE_OF_TYPE,
|
||||
this.name, Arrays.stream(clauseTypes).mapToObj(type -> WhereClauseParser.tokenNames[type]).collect(Collectors.toList())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum ClauseType
|
||||
{
|
||||
EQUALS(WhereClauseParser.EQUALS),
|
||||
NOT_EQUALS(WhereClauseParser.EQUALS, true),
|
||||
GREATER_THAN(WhereClauseParser.GREATERTHAN),
|
||||
NOT_GREATER_THAN(WhereClauseParser.GREATERTHAN, true),
|
||||
LESS_THAN(WhereClauseParser.LESSTHAN),
|
||||
NOT_LESS_THAN(WhereClauseParser.LESSTHAN, true),
|
||||
GREATER_THAN_OR_EQUALS(WhereClauseParser.GREATERTHANOREQUALS),
|
||||
NOT_GREATER_THAN_OR_EQUALS(WhereClauseParser.GREATERTHANOREQUALS, true),
|
||||
LESS_THAN_OR_EQUALS(WhereClauseParser.LESSTHANOREQUALS),
|
||||
NOT_LESS_THAN_OR_EQUALS(WhereClauseParser.LESSTHANOREQUALS, true),
|
||||
BETWEEN(WhereClauseParser.BETWEEN),
|
||||
NOT_BETWEEN(WhereClauseParser.BETWEEN, true),
|
||||
IN(WhereClauseParser.IN),
|
||||
NOT_IN(WhereClauseParser.IN, true),
|
||||
MATCHES(WhereClauseParser.MATCHES),
|
||||
NOT_MATCHES(WhereClauseParser.MATCHES, true),
|
||||
EXISTS(WhereClauseParser.EXISTS),
|
||||
NOT_EXISTS(WhereClauseParser.EXISTS, true);
|
||||
|
||||
private final int typeNumber;
|
||||
private final boolean negated;
|
||||
|
||||
ClauseType(final int typeNumber)
|
||||
{
|
||||
this.typeNumber = typeNumber;
|
||||
this.negated = false;
|
||||
}
|
||||
|
||||
ClauseType(final int typeNumber, final boolean negated)
|
||||
{
|
||||
this.typeNumber = typeNumber;
|
||||
this.negated = negated;
|
||||
}
|
||||
|
||||
public static ClauseType of(final int type)
|
||||
{
|
||||
return of(type, false);
|
||||
}
|
||||
|
||||
public static ClauseType of(final int type, final boolean negated)
|
||||
{
|
||||
return Arrays.stream(ClauseType.values())
|
||||
.filter(clauseType -> clauseType.typeNumber == type && clauseType.negated == negated)
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
}
|
||||
|
||||
public ClauseType negate()
|
||||
{
|
||||
return of(typeNumber, !negated);
|
||||
}
|
||||
|
||||
public int getTypeNumber()
|
||||
{
|
||||
return typeNumber;
|
||||
}
|
||||
|
||||
public boolean isNegated()
|
||||
{
|
||||
return negated;
|
||||
}
|
||||
}
|
||||
|
||||
public static class NegatableValuesMap extends HashMap<Boolean, Collection<String>>
|
||||
{
|
||||
public Collection<String> skipNegated()
|
||||
{
|
||||
return this.get(false);
|
||||
}
|
||||
|
||||
public Collection<String> onlyNegated()
|
||||
{
|
||||
return this.get(true);
|
||||
}
|
||||
}
|
||||
|
||||
public static class MultiTypeNegatableValuesMap extends HashMap<ClauseType, Collection<String>>
|
||||
{
|
||||
public Map<Integer, Collection<String>> skipNegated()
|
||||
{
|
||||
return this.keySet().stream()
|
||||
.filter(not(ClauseType::isNegated))
|
||||
.collect(Collectors.toMap(key -> key.typeNumber, this::get));
|
||||
}
|
||||
|
||||
public Collection<String> skipNegated(final int clauseType)
|
||||
{
|
||||
return this.get(ClauseType.of(clauseType));
|
||||
}
|
||||
|
||||
public Map<Integer, Collection<String>> onlyNegated()
|
||||
{
|
||||
return this.keySet().stream()
|
||||
.filter(not(ClauseType::isNegated))
|
||||
.collect(Collectors.toMap(key -> key.typeNumber, this::get));
|
||||
}
|
||||
|
||||
public Collection<String> onlyNegated(final int clauseType)
|
||||
{
|
||||
return this.get(ClauseType.of(clauseType, true));
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,32 +1,33 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* 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%
|
||||
*/
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2023 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.workflow.api.impl;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -51,7 +52,7 @@ import org.apache.commons.beanutils.ConvertUtils;
|
||||
* {@link InvalidArgumentException} is thrown unless the method
|
||||
* {@link #handleUnmatchedComparison(int, String, String)} returns true (default
|
||||
* implementation returns false).
|
||||
*
|
||||
*
|
||||
* @author Frederik Heremans
|
||||
* @author Tijs Rademakers
|
||||
*/
|
||||
@@ -72,21 +73,21 @@ public class MapBasedQueryWalker extends WalkerCallbackAdapter
|
||||
private Map<String, String> equalsProperties;
|
||||
|
||||
private Map<String, String> matchesProperties;
|
||||
|
||||
|
||||
private Map<String, String> greaterThanProperties;
|
||||
|
||||
|
||||
private Map<String, String> greaterThanOrEqualProperties;
|
||||
|
||||
|
||||
private Map<String, String> lessThanProperties;
|
||||
|
||||
|
||||
private Map<String, String> lessThanOrEqualProperties;
|
||||
|
||||
|
||||
private List<QueryVariableHolder> variableProperties;
|
||||
|
||||
|
||||
private boolean variablesEnabled;
|
||||
|
||||
|
||||
private NamespaceService namespaceService;
|
||||
|
||||
|
||||
private DictionaryService dictionaryService;
|
||||
|
||||
public MapBasedQueryWalker(Set<String> supportedEqualsParameters, Set<String> supportedMatchesParameters)
|
||||
@@ -132,7 +133,7 @@ public class MapBasedQueryWalker extends WalkerCallbackAdapter
|
||||
lessThanOrEqualProperties = new HashMap<String, String>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void enableVariablesSupport(NamespaceService namespaceService, DictionaryService dictionaryService)
|
||||
{
|
||||
variablesEnabled = true;
|
||||
@@ -148,7 +149,7 @@ public class MapBasedQueryWalker extends WalkerCallbackAdapter
|
||||
this.dictionaryService = dictionaryService;
|
||||
variableProperties = new ArrayList<QueryVariableHolder>();
|
||||
}
|
||||
|
||||
|
||||
public List<QueryVariableHolder> getVariableProperties() {
|
||||
return variableProperties;
|
||||
}
|
||||
@@ -158,9 +159,9 @@ public class MapBasedQueryWalker extends WalkerCallbackAdapter
|
||||
{
|
||||
if(negated)
|
||||
{
|
||||
throw new InvalidArgumentException("Cannot use negated matching for property: " + property);
|
||||
throw new InvalidArgumentException("Cannot use negated matching for property: " + property);
|
||||
}
|
||||
if (variablesEnabled && property.startsWith("variables/"))
|
||||
if (variablesEnabled && property.startsWith("variables/"))
|
||||
{
|
||||
processVariable(property, value, WhereClauseParser.MATCHES);
|
||||
}
|
||||
@@ -170,19 +171,19 @@ public class MapBasedQueryWalker extends WalkerCallbackAdapter
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidArgumentException("Cannot use matching for property: " + property);
|
||||
throw new InvalidArgumentException("Cannot use matching for property: " + property);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void comparison(int type, String propertyName, String propertyValue, boolean negated)
|
||||
{
|
||||
if (variablesEnabled && propertyName.startsWith("variables/"))
|
||||
if (variablesEnabled && propertyName.startsWith("variables/"))
|
||||
{
|
||||
processVariable(propertyName, propertyValue, type);
|
||||
processVariable(propertyName, propertyValue, type);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
boolean throwError = false;
|
||||
if (type == WhereClauseParser.EQUALS)
|
||||
{
|
||||
@@ -192,7 +193,7 @@ public class MapBasedQueryWalker extends WalkerCallbackAdapter
|
||||
}
|
||||
else
|
||||
{
|
||||
throwError = !handleUnmatchedComparison(type, propertyName, propertyValue);
|
||||
throwError = !handleUnmatchedComparison(type, propertyName, propertyValue);
|
||||
}
|
||||
}
|
||||
else if (type == WhereClauseParser.MATCHES)
|
||||
@@ -203,7 +204,7 @@ public class MapBasedQueryWalker extends WalkerCallbackAdapter
|
||||
}
|
||||
else
|
||||
{
|
||||
throwError = !handleUnmatchedComparison(type, propertyName, propertyValue);
|
||||
throwError = !handleUnmatchedComparison(type, propertyName, propertyValue);
|
||||
}
|
||||
}
|
||||
else if (type == WhereClauseParser.GREATERTHAN)
|
||||
@@ -214,7 +215,7 @@ public class MapBasedQueryWalker extends WalkerCallbackAdapter
|
||||
}
|
||||
else
|
||||
{
|
||||
throwError = !handleUnmatchedComparison(type, propertyName, propertyValue);
|
||||
throwError = !handleUnmatchedComparison(type, propertyName, propertyValue);
|
||||
}
|
||||
}
|
||||
else if (type == WhereClauseParser.GREATERTHANOREQUALS)
|
||||
@@ -225,7 +226,7 @@ public class MapBasedQueryWalker extends WalkerCallbackAdapter
|
||||
}
|
||||
else
|
||||
{
|
||||
throwError = !handleUnmatchedComparison(type, propertyName, propertyValue);
|
||||
throwError = !handleUnmatchedComparison(type, propertyName, propertyValue);
|
||||
}
|
||||
}
|
||||
else if (type == WhereClauseParser.LESSTHAN)
|
||||
@@ -236,7 +237,7 @@ public class MapBasedQueryWalker extends WalkerCallbackAdapter
|
||||
}
|
||||
else
|
||||
{
|
||||
throwError = !handleUnmatchedComparison(type, propertyName, propertyValue);
|
||||
throwError = !handleUnmatchedComparison(type, propertyName, propertyValue);
|
||||
}
|
||||
}
|
||||
else if (type == WhereClauseParser.LESSTHANOREQUALS)
|
||||
@@ -247,7 +248,7 @@ public class MapBasedQueryWalker extends WalkerCallbackAdapter
|
||||
}
|
||||
else
|
||||
{
|
||||
throwError = !handleUnmatchedComparison(type, propertyName, propertyValue);
|
||||
throwError = !handleUnmatchedComparison(type, propertyName, propertyValue);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -255,15 +256,24 @@ public class MapBasedQueryWalker extends WalkerCallbackAdapter
|
||||
throwError = !handleUnmatchedComparison(type, propertyName, propertyValue);
|
||||
}
|
||||
|
||||
if (throwError)
|
||||
{
|
||||
throw new InvalidArgumentException("framework.exception.InvalidProperty", new Object[] {propertyName, propertyValue, WhereClauseParser.tokenNames[type]});
|
||||
}
|
||||
else if (negated)
|
||||
{
|
||||
// Throw error for the unsupported negation only if the property was valid for comparison, show the more meaningful error first.
|
||||
throw new InvalidArgumentException("Cannot use NOT for " + WhereClauseParser.tokenNames[type] + " comparison.");
|
||||
if (throwError)
|
||||
{
|
||||
throw new InvalidArgumentException("framework.exception.InvalidProperty", new Object[] {propertyName, propertyValue, WhereClauseParser.tokenNames[type]});
|
||||
}
|
||||
else if (negated)
|
||||
{
|
||||
// Throw error for the unsupported negation only if the property was valid for comparison, show the more meaningful error first.
|
||||
throw new InvalidArgumentException("Cannot use NOT for " + WhereClauseParser.tokenNames[type] + " comparison.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get expected value for property and comparison type. This class supports only non-negated comparisons, thus parameter negated is ignored in bellow method.
|
||||
*/
|
||||
@Override
|
||||
public Collection<String> getProperty(String propertyName, int type, boolean negated)
|
||||
{
|
||||
return Set.of(this.getProperty(propertyName, type));
|
||||
}
|
||||
|
||||
public String getProperty(String propertyName, int type)
|
||||
@@ -300,7 +310,7 @@ public class MapBasedQueryWalker extends WalkerCallbackAdapter
|
||||
|
||||
/**
|
||||
* Get the property value, converted to the requested type.
|
||||
*
|
||||
*
|
||||
* @param propertyName name of the parameter
|
||||
* @param type int
|
||||
* @param returnType type of object to return
|
||||
@@ -334,7 +344,7 @@ public class MapBasedQueryWalker extends WalkerCallbackAdapter
|
||||
{
|
||||
// Conversion failed, wrap in Illegal
|
||||
throw new InvalidArgumentException("Query property value for '" + propertyName + "' should be a valid "
|
||||
+ returnType.getSimpleName());
|
||||
+ returnType.getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,7 +355,7 @@ public class MapBasedQueryWalker extends WalkerCallbackAdapter
|
||||
// method indicates that AND is
|
||||
// supported. OR is not supported at the same time.
|
||||
}
|
||||
|
||||
|
||||
protected void processVariable(String propertyName, String propertyValue, int type)
|
||||
{
|
||||
String localPropertyName = propertyName.replaceFirst("variables/", "");
|
||||
@@ -353,25 +363,25 @@ public class MapBasedQueryWalker extends WalkerCallbackAdapter
|
||||
DataTypeDefinition dataTypeDefinition = null;
|
||||
// variable scope global is default
|
||||
String scopeDef = "global";
|
||||
|
||||
|
||||
// look for variable scope
|
||||
if (localPropertyName.contains("local/"))
|
||||
{
|
||||
scopeDef = "local";
|
||||
localPropertyName = localPropertyName.replaceFirst("local/", "");
|
||||
}
|
||||
|
||||
|
||||
if (localPropertyName.contains("global/"))
|
||||
{
|
||||
localPropertyName = localPropertyName.replaceFirst("global/", "");
|
||||
}
|
||||
|
||||
|
||||
// look for variable type definition
|
||||
if ((propertyValue.contains("_") || propertyValue.contains(":")) && propertyValue.contains(" "))
|
||||
if ((propertyValue.contains("_") || propertyValue.contains(":")) && propertyValue.contains(" "))
|
||||
{
|
||||
int indexOfSpace = propertyValue.indexOf(' ');
|
||||
if ((propertyValue.contains("_") && indexOfSpace > propertyValue.indexOf("_")) ||
|
||||
(propertyValue.contains(":") && indexOfSpace > propertyValue.indexOf(":")))
|
||||
if ((propertyValue.contains("_") && indexOfSpace > propertyValue.indexOf("_")) ||
|
||||
(propertyValue.contains(":") && indexOfSpace > propertyValue.indexOf(":")))
|
||||
{
|
||||
String typeDef = propertyValue.substring(0, indexOfSpace);
|
||||
try
|
||||
@@ -386,7 +396,7 @@ public class MapBasedQueryWalker extends WalkerCallbackAdapter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (dataTypeDefinition != null && "java.util.Date".equalsIgnoreCase(dataTypeDefinition.getJavaClassName()))
|
||||
{
|
||||
// fix for different ISO 8601 Date format classes in Alfresco (org.alfresco.util and Spring Surf)
|
||||
@@ -396,18 +406,18 @@ public class MapBasedQueryWalker extends WalkerCallbackAdapter
|
||||
{
|
||||
actualValue = DefaultTypeConverter.INSTANCE.convert(dataTypeDefinition, propertyValue);
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
actualValue = propertyValue;
|
||||
}
|
||||
|
||||
|
||||
variableProperties.add(new QueryVariableHolder(localPropertyName, type, actualValue, scopeDef));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when unsupported property is encountered or comparison operator
|
||||
* other than equals.
|
||||
*
|
||||
*
|
||||
* @return true, if the comparison is handles successfully. False, if an
|
||||
* exception should be thrown because the comparison can't be
|
||||
* handled.
|
||||
@@ -416,25 +426,25 @@ public class MapBasedQueryWalker extends WalkerCallbackAdapter
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public static class QueryVariableHolder implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
|
||||
private String propertyName;
|
||||
private int operator;
|
||||
private Object propertyValue;
|
||||
private String scope;
|
||||
|
||||
|
||||
public QueryVariableHolder() {}
|
||||
|
||||
|
||||
public QueryVariableHolder(String propertyName, int operator, Object propertyValue, String scope) {
|
||||
this.propertyName = propertyName;
|
||||
this.operator = operator;
|
||||
this.propertyValue = propertyValue;
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
|
||||
public String getPropertyName()
|
||||
{
|
||||
return propertyName;
|
||||
|
@@ -856,7 +856,9 @@
|
||||
<bean id="tags" class="org.alfresco.rest.api.impl.TagsImpl">
|
||||
<property name="nodes" ref="nodes" />
|
||||
<property name="taggingService" ref="TaggingService" />
|
||||
<property name="authorityService" ref="AuthorityService" />
|
||||
<property name="typeConstraint" ref="nodeTypeConstraint" />
|
||||
<property name="nodeService" ref="NodeService"/>
|
||||
</bean>
|
||||
|
||||
<bean id="Tags" class="org.springframework.aop.framework.ProxyFactoryBean">
|
||||
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2022 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -49,6 +49,8 @@ import org.junit.runners.Suite;
|
||||
org.alfresco.repo.webdav.WebDAVHelperTest.class,
|
||||
org.alfresco.repo.webdav.WebDAVLockServiceImplTest.class,
|
||||
org.alfresco.rest.api.RulesUnitTests.class,
|
||||
org.alfresco.rest.api.CategoriesUnitTests.class,
|
||||
org.alfresco.rest.api.TagsUnitTests.class,
|
||||
org.alfresco.rest.api.impl.ContentStorageInformationImplTest.class,
|
||||
org.alfresco.rest.api.nodes.NodeStorageInfoRelationTest.class,
|
||||
org.alfresco.rest.api.search.ResultMapperTests.class,
|
||||
|
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2023 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;
|
||||
|
||||
import org.alfresco.rest.api.categories.CategoriesEntityResourceTest;
|
||||
import org.alfresco.rest.api.categories.NodesCategoryLinksRelationTest;
|
||||
import org.alfresco.rest.api.categories.SubcategoriesRelationTest;
|
||||
import org.alfresco.rest.api.impl.CategoriesImplTest;
|
||||
import org.alfresco.service.Experimental;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Suite;
|
||||
|
||||
@Experimental
|
||||
@RunWith(Suite.class)
|
||||
@Suite.SuiteClasses({
|
||||
CategoriesImplTest.class,
|
||||
CategoriesEntityResourceTest.class,
|
||||
SubcategoriesRelationTest.class,
|
||||
NodesCategoryLinksRelationTest.class
|
||||
})
|
||||
public class CategoriesUnitTests
|
||||
{
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2023 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;
|
||||
|
||||
import org.alfresco.rest.api.impl.TagsImplTest;
|
||||
import org.alfresco.rest.api.tags.TagsEntityResourceTest;
|
||||
import org.alfresco.service.Experimental;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Suite;
|
||||
|
||||
@Experimental
|
||||
@RunWith(Suite.class)
|
||||
@Suite.SuiteClasses({
|
||||
TagsImplTest.class,
|
||||
TagsEntityResourceTest.class
|
||||
})
|
||||
public class TagsUnitTests
|
||||
{
|
||||
}
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2022 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -26,15 +26,21 @@
|
||||
|
||||
package org.alfresco.rest.api.categories;
|
||||
|
||||
import static org.alfresco.service.cmr.repository.StoreRef.STORE_REF_WORKSPACE_SPACESSTORE;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.BDDMockito.willCallRealMethod;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.rest.api.Categories;
|
||||
import org.alfresco.rest.api.model.Category;
|
||||
import org.alfresco.rest.framework.resource.parameters.Parameters;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
@@ -45,6 +51,7 @@ import org.mockito.junit.MockitoJUnitRunner;
|
||||
public class CategoriesEntityResourceTest
|
||||
{
|
||||
private static final String CATEGORY_ID = "category-node-id";
|
||||
|
||||
@Mock
|
||||
private Categories categoriesMock;
|
||||
@Mock
|
||||
@@ -58,26 +65,37 @@ public class CategoriesEntityResourceTest
|
||||
@Test
|
||||
public void testReadCategoryById()
|
||||
{
|
||||
given(categoriesMock.getCategoryById(CATEGORY_ID, parametersMock)).willReturn(categoryMock);
|
||||
given(categoriesMock.getCategoryById(any(), any())).willCallRealMethod();
|
||||
given(categoriesMock.getCategoryById(any(), any(), any())).willReturn(categoryMock);
|
||||
|
||||
//when
|
||||
final Category category = objectUnderTest.readById(CATEGORY_ID, parametersMock);
|
||||
|
||||
then(categoriesMock).should().getCategoryById(CATEGORY_ID, parametersMock);
|
||||
then(categoriesMock).shouldHaveNoMoreInteractions();
|
||||
then(categoriesMock).should().getCategoryById(STORE_REF_WORKSPACE_SPACESSTORE, CATEGORY_ID, parametersMock);
|
||||
assertEquals(categoryMock, category);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateCategoryById()
|
||||
{
|
||||
given(categoriesMock.updateCategoryById(any(), any())).willReturn(categoryMock);
|
||||
given(categoriesMock.updateCategoryById(any(), any(), any())).willCallRealMethod();
|
||||
given(categoriesMock.updateCategoryById(any(), any(), any(), any())).willReturn(categoryMock);
|
||||
|
||||
// when
|
||||
final Category actualCategory = objectUnderTest.update(CATEGORY_ID, categoryMock, parametersMock);
|
||||
|
||||
then(categoriesMock).should().updateCategoryById(CATEGORY_ID, categoryMock);
|
||||
then(categoriesMock).shouldHaveNoMoreInteractions();
|
||||
then(categoriesMock).should().updateCategoryById(STORE_REF_WORKSPACE_SPACESSTORE, CATEGORY_ID, categoryMock, parametersMock);
|
||||
assertThat(actualCategory).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteCategoryById()
|
||||
{
|
||||
willCallRealMethod().given(categoriesMock).deleteCategoryById(any(), any());
|
||||
|
||||
// when
|
||||
objectUnderTest.delete(CATEGORY_ID, parametersMock);
|
||||
|
||||
then(categoriesMock).should().deleteCategoryById(STORE_REF_WORKSPACE_SPACESSTORE, CATEGORY_ID, parametersMock);
|
||||
}
|
||||
}
|
||||
|
@@ -47,6 +47,7 @@ import org.mockito.junit.MockitoJUnitRunner;
|
||||
public class NodesCategoryLinksRelationTest
|
||||
{
|
||||
private static final String CONTENT_ID = "content-node-id";
|
||||
private static final String CATEGORY_ID = "category-id";
|
||||
|
||||
@Mock
|
||||
private Categories categoriesMock;
|
||||
@@ -61,12 +62,12 @@ public class NodesCategoryLinksRelationTest
|
||||
@Test
|
||||
public void testReadAll()
|
||||
{
|
||||
given(categoriesMock.listCategoriesForNode(any())).willReturn(List.of(categoryMock));
|
||||
given(categoriesMock.listCategoriesForNode(any(), any())).willReturn(List.of(categoryMock));
|
||||
|
||||
// when
|
||||
final CollectionWithPagingInfo<Category> actualCategoriesPage = objectUnderTest.readAll(CONTENT_ID, parametersMock);
|
||||
|
||||
then(categoriesMock).should().listCategoriesForNode(CONTENT_ID);
|
||||
then(categoriesMock).should().listCategoriesForNode(CONTENT_ID, parametersMock);
|
||||
then(categoriesMock).shouldHaveNoMoreInteractions();
|
||||
assertThat(actualCategoriesPage)
|
||||
.isNotNull()
|
||||
@@ -77,15 +78,25 @@ public class NodesCategoryLinksRelationTest
|
||||
@Test
|
||||
public void testCreate()
|
||||
{
|
||||
given(categoriesMock.linkNodeToCategories(any(), any())).willReturn(List.of(categoryMock));
|
||||
given(categoriesMock.linkNodeToCategories(any(), any(), any())).willReturn(List.of(categoryMock));
|
||||
|
||||
// when
|
||||
final List<Category> actualCategories = objectUnderTest.create(CONTENT_ID, List.of(categoryMock), parametersMock);
|
||||
|
||||
then(categoriesMock).should().linkNodeToCategories(CONTENT_ID, List.of(categoryMock));
|
||||
then(categoriesMock).should().linkNodeToCategories(CONTENT_ID, List.of(categoryMock), parametersMock);
|
||||
then(categoriesMock).shouldHaveNoMoreInteractions();
|
||||
assertThat(actualCategories)
|
||||
.isNotNull()
|
||||
.isEqualTo(List.of(categoryMock));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDelete()
|
||||
{
|
||||
// when
|
||||
objectUnderTest.delete(CONTENT_ID, CATEGORY_ID, parametersMock);
|
||||
|
||||
then(categoriesMock).should().unlinkNodeFromCategory(CONTENT_ID, CATEGORY_ID, parametersMock);
|
||||
then(categoriesMock).shouldHaveNoMoreInteractions();
|
||||
}
|
||||
}
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2022 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -26,7 +26,10 @@
|
||||
|
||||
package org.alfresco.rest.api.categories;
|
||||
|
||||
import static org.alfresco.service.cmr.repository.StoreRef.STORE_REF_WORKSPACE_SPACESSTORE;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
|
||||
@@ -37,8 +40,8 @@ import java.util.stream.IntStream;
|
||||
import org.alfresco.rest.api.Categories;
|
||||
import org.alfresco.rest.api.model.Category;
|
||||
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
|
||||
import org.alfresco.rest.framework.resource.parameters.Paging;
|
||||
import org.alfresco.rest.framework.resource.parameters.Parameters;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
@@ -67,39 +70,37 @@ public class SubcategoriesRelationTest
|
||||
final Category categoryToCreate = Category.builder().name(CATEGORY_NAME).create();
|
||||
final Category category = Category.builder().name(CATEGORY_NAME).parentId(PARENT_CATEGORY_ID).hasChildren(false).id(CATEGORY_ID).create();
|
||||
final List<Category> categoriesToCreate = List.of(categoryToCreate);
|
||||
given(categoriesMock.createSubcategories(PARENT_CATEGORY_ID, categoriesToCreate, parametersMock)).willReturn(List.of(category));
|
||||
given(categoriesMock.createSubcategories(any(), any(), any())).willCallRealMethod();
|
||||
given(categoriesMock.createSubcategories(any(), any(), any(), any())).willReturn(List.of(category));
|
||||
|
||||
//when
|
||||
List<Category> categories = objectUnderTest.create(PARENT_CATEGORY_ID, categoriesToCreate, parametersMock);
|
||||
|
||||
then(categoriesMock).should().createSubcategories(PARENT_CATEGORY_ID, categoriesToCreate, parametersMock);
|
||||
then(categoriesMock).shouldHaveNoMoreInteractions();
|
||||
then(categoriesMock).should().createSubcategories(STORE_REF_WORKSPACE_SPACESSTORE, PARENT_CATEGORY_ID, categoriesToCreate, parametersMock);
|
||||
assertEquals(List.of(category), categories);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCategoryChildren() {
|
||||
final CollectionWithPagingInfo<Category> categoryChildren = getCategories(3);
|
||||
given(categoriesMock.getCategoryChildren(PARENT_CATEGORY_ID, parametersMock)).willReturn(categoryChildren);
|
||||
final List<Category> categoryChildren = getCategories(3);
|
||||
given(categoriesMock.getCategoryChildren(any(), any())).willCallRealMethod();
|
||||
given(categoriesMock.getCategoryChildren(any(), any(), any())).willReturn(categoryChildren);
|
||||
|
||||
//when
|
||||
final CollectionWithPagingInfo<Category> returnedChildren = objectUnderTest.readAll(PARENT_CATEGORY_ID, parametersMock);
|
||||
|
||||
then(categoriesMock).should().getCategoryChildren(PARENT_CATEGORY_ID, parametersMock);
|
||||
then(categoriesMock).shouldHaveNoMoreInteractions();
|
||||
assertEquals(categoryChildren, returnedChildren);
|
||||
then(categoriesMock).should().getCategoryChildren(STORE_REF_WORKSPACE_SPACESSTORE, PARENT_CATEGORY_ID, parametersMock);
|
||||
assertEquals(categoryChildren, returnedChildren.getCollection());
|
||||
}
|
||||
|
||||
private CollectionWithPagingInfo<Category> getCategories(final int count)
|
||||
private List<Category> getCategories(final int count)
|
||||
{
|
||||
return CollectionWithPagingInfo.asPaged(Paging.DEFAULT,
|
||||
IntStream.range(0, count)
|
||||
.mapToObj(i -> Category.builder().name(SUBCATEGORY_NAME_PREFIX + "-" + i)
|
||||
.parentId(PARENT_CATEGORY_ID)
|
||||
.hasChildren(false)
|
||||
.id(CATEGORY_ID + "-" + i)
|
||||
.create())
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
return IntStream.range(0, count)
|
||||
.mapToObj(i -> Category.builder().name(SUBCATEGORY_NAME_PREFIX + "-" + i)
|
||||
.parentId(PARENT_CATEGORY_ID)
|
||||
.hasChildren(false)
|
||||
.id(CATEGORY_ID + "-" + i)
|
||||
.create())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
@@ -27,11 +27,13 @@
|
||||
package org.alfresco.rest.api.impl;
|
||||
|
||||
import static org.alfresco.rest.api.Nodes.PATH_ROOT;
|
||||
import static org.alfresco.rest.api.impl.CategoriesImpl.INCLUDE_COUNT_PARAM;
|
||||
import static org.alfresco.rest.api.impl.CategoriesImpl.INVALID_NODE_TYPE;
|
||||
import static org.alfresco.rest.api.impl.CategoriesImpl.NOT_A_VALID_CATEGORY;
|
||||
import static org.alfresco.rest.api.impl.CategoriesImpl.NOT_NULL_OR_EMPTY;
|
||||
import static org.alfresco.rest.api.impl.CategoriesImpl.NO_PERMISSION_TO_CHANGE_CONTENT;
|
||||
import static org.alfresco.rest.api.impl.CategoriesImpl.NO_PERMISSION_TO_READ_CONTENT;
|
||||
import static org.alfresco.service.cmr.repository.StoreRef.STORE_REF_WORKSPACE_SPACESSTORE;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.catchThrowable;
|
||||
@@ -39,6 +41,7 @@ import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
@@ -64,7 +67,6 @@ import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
|
||||
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
|
||||
import org.alfresco.rest.framework.core.exceptions.InvalidNodeTypeException;
|
||||
import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException;
|
||||
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
|
||||
import org.alfresco.rest.framework.resource.parameters.Parameters;
|
||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
@@ -77,6 +79,7 @@ import org.alfresco.service.cmr.security.PermissionService;
|
||||
import org.alfresco.service.namespace.NamespaceService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.service.namespace.RegexQNamePattern;
|
||||
import org.alfresco.util.Pair;
|
||||
import org.alfresco.util.TypeConstraint;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.junit.Before;
|
||||
@@ -94,7 +97,7 @@ public class CategoriesImplTest
|
||||
private static final String PARENT_ID = "parent-node-id";
|
||||
private static final String CAT_ROOT_NODE_ID = "cat-root-node-id";
|
||||
private static final NodeRef CATEGORY_NODE_REF = createNodeRefWithId(CATEGORY_ID);
|
||||
private static final Category CATEGORY = createDefaultCategoryWithName(CATEGORY_NAME);
|
||||
private static final Category CATEGORY = createDefaultCategory();
|
||||
private static final String CONTENT_NODE_ID = "content-node-id";
|
||||
private static final NodeRef CONTENT_NODE_REF = createNodeRefWithId(CONTENT_NODE_ID);
|
||||
|
||||
@@ -103,8 +106,6 @@ public class CategoriesImplTest
|
||||
@Mock
|
||||
private NodeService nodeServiceMock;
|
||||
@Mock
|
||||
private Parameters parametersMock;
|
||||
@Mock
|
||||
private AuthorityService authorityServiceMock;
|
||||
@Mock
|
||||
private CategoryService categoryServiceMock;
|
||||
@@ -116,6 +117,8 @@ public class CategoriesImplTest
|
||||
private PermissionService permissionServiceMock;
|
||||
@Mock
|
||||
private TypeConstraint typeConstraint;
|
||||
@Mock
|
||||
private Parameters parametersMock;
|
||||
|
||||
@InjectMocks
|
||||
private CategoriesImpl objectUnderTest;
|
||||
@@ -135,7 +138,7 @@ public class CategoriesImplTest
|
||||
@Test
|
||||
public void shouldNotGetRootCategoryById()
|
||||
{
|
||||
final NodeRef categoryRootNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, CAT_ROOT_NODE_ID);
|
||||
final NodeRef categoryRootNodeRef = createNodeRefWithId(CAT_ROOT_NODE_ID);
|
||||
given(nodesMock.validateNode(CAT_ROOT_NODE_ID)).willReturn(categoryRootNodeRef);
|
||||
given(categoryChildAssociationRefMock.getQName()).willReturn(ContentModel.ASPECT_GEN_CLASSIFIABLE);
|
||||
given(nodeServiceMock.getParentAssocs(categoryRootNodeRef)).willReturn(List.of(categoryChildAssociationRefMock));
|
||||
@@ -155,31 +158,26 @@ public class CategoriesImplTest
|
||||
@Test
|
||||
public void testGetCategoryById_withChildren()
|
||||
{
|
||||
final NodeRef categoryNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, CATEGORY_ID);
|
||||
given(nodesMock.validateNode(CATEGORY_ID)).willReturn(categoryNodeRef);
|
||||
final Node categoryNode = new Node();
|
||||
categoryNode.setName(CATEGORY_NAME);
|
||||
categoryNode.setNodeId(CATEGORY_ID);
|
||||
final NodeRef parentNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, PARENT_ID);
|
||||
categoryNode.setParentId(parentNodeRef);
|
||||
final NodeRef parentNodeRef = createNodeRefWithId(PARENT_ID);
|
||||
final Node categoryNode = createNode(CATEGORY_NAME, CATEGORY_ID, parentNodeRef);
|
||||
given(nodesMock.getNode(CATEGORY_ID)).willReturn(categoryNode);
|
||||
final ChildAssociationRef parentAssoc = new ChildAssociationRef(null, parentNodeRef, null, categoryNodeRef);
|
||||
given(nodeServiceMock.getPrimaryParent(categoryNodeRef)).willReturn(parentAssoc);
|
||||
final ChildAssociationRef parentAssoc = new ChildAssociationRef(null, parentNodeRef, null, CATEGORY_NODE_REF);
|
||||
given(nodeServiceMock.getPrimaryParent(CATEGORY_NODE_REF)).willReturn(parentAssoc);
|
||||
final List<ChildAssociationRef> dummyChildren = List.of(dummyChildAssociationRefMock);
|
||||
given(nodeServiceMock.getChildAssocs(categoryNodeRef, RegexQNamePattern.MATCH_ALL, RegexQNamePattern.MATCH_ALL, false))
|
||||
given(nodeServiceMock.getChildAssocs(CATEGORY_NODE_REF, RegexQNamePattern.MATCH_ALL, RegexQNamePattern.MATCH_ALL, false))
|
||||
.willReturn(dummyChildren);
|
||||
//when
|
||||
final Category category = objectUnderTest.getCategoryById(CATEGORY_ID, parametersMock);
|
||||
|
||||
then(nodesMock).should().validateNode(CATEGORY_ID);
|
||||
then(nodesMock).should().isSubClass(categoryNodeRef, ContentModel.TYPE_CATEGORY, false);
|
||||
then(nodesMock).should().isSubClass(CATEGORY_NODE_REF, ContentModel.TYPE_CATEGORY, false);
|
||||
then(nodesMock).should().getNode(CATEGORY_ID);
|
||||
then(nodesMock).shouldHaveNoMoreInteractions();
|
||||
|
||||
then(nodeServiceMock).should().getPrimaryParent(categoryNodeRef);
|
||||
then(nodeServiceMock).should().getPrimaryParent(CATEGORY_NODE_REF);
|
||||
then(nodeServiceMock).should().getParentAssocs(parentNodeRef);
|
||||
then(nodeServiceMock).should().getParentAssocs(categoryNodeRef);
|
||||
then(nodeServiceMock).should().getChildAssocs(categoryNodeRef, RegexQNamePattern.MATCH_ALL, RegexQNamePattern.MATCH_ALL, false);
|
||||
then(nodeServiceMock).should().getParentAssocs(CATEGORY_NODE_REF);
|
||||
then(nodeServiceMock).should().getChildAssocs(CATEGORY_NODE_REF, RegexQNamePattern.MATCH_ALL, RegexQNamePattern.MATCH_ALL, false);
|
||||
then(nodeServiceMock).shouldHaveNoMoreInteractions();
|
||||
|
||||
then(categoryServiceMock).shouldHaveNoInteractions();
|
||||
@@ -197,30 +195,25 @@ public class CategoriesImplTest
|
||||
@Test
|
||||
public void testGetCategoryById_withoutChildren()
|
||||
{
|
||||
final NodeRef categoryNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, CATEGORY_ID);
|
||||
given(nodesMock.validateNode(CATEGORY_ID)).willReturn(categoryNodeRef);
|
||||
final Node categoryNode = new Node();
|
||||
categoryNode.setName(CATEGORY_NAME);
|
||||
categoryNode.setNodeId(CATEGORY_ID);
|
||||
final NodeRef parentNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, PARENT_ID);
|
||||
categoryNode.setParentId(parentNodeRef);
|
||||
final NodeRef parentNodeRef = createNodeRefWithId(PARENT_ID);
|
||||
final Node categoryNode = createNode(CATEGORY_NAME, CATEGORY_ID, parentNodeRef);
|
||||
given(nodesMock.getNode(CATEGORY_ID)).willReturn(categoryNode);
|
||||
final ChildAssociationRef parentAssoc = new ChildAssociationRef(null, parentNodeRef, null, categoryNodeRef);
|
||||
given(nodeServiceMock.getPrimaryParent(categoryNodeRef)).willReturn(parentAssoc);
|
||||
given(nodeServiceMock.getChildAssocs(categoryNodeRef, RegexQNamePattern.MATCH_ALL, RegexQNamePattern.MATCH_ALL, false))
|
||||
final ChildAssociationRef parentAssoc = new ChildAssociationRef(null, parentNodeRef, null, CATEGORY_NODE_REF);
|
||||
given(nodeServiceMock.getPrimaryParent(CATEGORY_NODE_REF)).willReturn(parentAssoc);
|
||||
given(nodeServiceMock.getChildAssocs(CATEGORY_NODE_REF, RegexQNamePattern.MATCH_ALL, RegexQNamePattern.MATCH_ALL, false))
|
||||
.willReturn(Collections.emptyList());
|
||||
//when
|
||||
final Category category = objectUnderTest.getCategoryById(CATEGORY_ID, parametersMock);
|
||||
|
||||
then(nodesMock).should().validateNode(CATEGORY_ID);
|
||||
then(nodesMock).should().isSubClass(categoryNodeRef, ContentModel.TYPE_CATEGORY, false);
|
||||
then(nodesMock).should().isSubClass(CATEGORY_NODE_REF, ContentModel.TYPE_CATEGORY, false);
|
||||
then(nodesMock).should().getNode(CATEGORY_ID);
|
||||
then(nodesMock).shouldHaveNoMoreInteractions();
|
||||
|
||||
then(nodeServiceMock).should().getPrimaryParent(categoryNodeRef);
|
||||
then(nodeServiceMock).should().getPrimaryParent(CATEGORY_NODE_REF);
|
||||
then(nodeServiceMock).should().getParentAssocs(parentNodeRef);
|
||||
then(nodeServiceMock).should().getParentAssocs(categoryNodeRef);
|
||||
then(nodeServiceMock).should().getChildAssocs(categoryNodeRef, RegexQNamePattern.MATCH_ALL, RegexQNamePattern.MATCH_ALL, false);
|
||||
then(nodeServiceMock).should().getParentAssocs(CATEGORY_NODE_REF);
|
||||
then(nodeServiceMock).should().getChildAssocs(CATEGORY_NODE_REF, RegexQNamePattern.MATCH_ALL, RegexQNamePattern.MATCH_ALL, false);
|
||||
then(nodeServiceMock).shouldHaveNoMoreInteractions();
|
||||
|
||||
then(categoryServiceMock).shouldHaveNoInteractions();
|
||||
@@ -235,6 +228,30 @@ public class CategoriesImplTest
|
||||
assertEquals(expectedCategory, category);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCategoryById_includeCount()
|
||||
{
|
||||
final QName categoryQName = createCmQNameOf(CATEGORY_NAME);
|
||||
final NodeRef parentCategoryNodeRef = createNodeRefWithId(PARENT_ID);
|
||||
final ChildAssociationRef parentAssociation = createAssociationOf(parentCategoryNodeRef, CATEGORY_NODE_REF, categoryQName);
|
||||
given(nodesMock.getNode(any())).willReturn(createNode());
|
||||
given(nodeServiceMock.getPrimaryParent(any())).willReturn(parentAssociation);
|
||||
given(parametersMock.getInclude()).willReturn(List.of(INCLUDE_COUNT_PARAM));
|
||||
given(categoryServiceMock.getTopCategories(any(), any(), anyInt())).willReturn(List.of(new Pair<>(CATEGORY_NODE_REF, 1)));
|
||||
|
||||
// when
|
||||
final Category actualCategory = objectUnderTest.getCategoryById(CATEGORY_ID, parametersMock);
|
||||
|
||||
then(categoryServiceMock).should().getTopCategories(STORE_REF_WORKSPACE_SPACESSTORE, ContentModel.ASPECT_GEN_CLASSIFIABLE, Integer.MAX_VALUE);
|
||||
then(categoryServiceMock).shouldHaveNoMoreInteractions();
|
||||
|
||||
assertThat(actualCategory)
|
||||
.isNotNull()
|
||||
.extracting(Category::getCount)
|
||||
.isNotNull()
|
||||
.isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCategoryById_notACategory()
|
||||
{
|
||||
@@ -274,14 +291,9 @@ public class CategoriesImplTest
|
||||
public void testDeleteCategoryById_asAdmin()
|
||||
{
|
||||
given(authorityServiceMock.hasAdminAuthority()).willReturn(true);
|
||||
final NodeRef categoryNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, CATEGORY_ID);
|
||||
final NodeRef categoryNodeRef = createNodeRefWithId(CATEGORY_ID);
|
||||
given(nodesMock.validateNode(CATEGORY_ID)).willReturn(categoryNodeRef);
|
||||
given(nodesMock.isSubClass(categoryNodeRef, ContentModel.TYPE_CATEGORY, false)).willReturn(true);
|
||||
final Node categoryNode = new Node();
|
||||
categoryNode.setName(CATEGORY_NAME);
|
||||
categoryNode.setNodeId(CATEGORY_ID);
|
||||
final NodeRef parentNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, PARENT_ID);
|
||||
categoryNode.setParentId(parentNodeRef);
|
||||
|
||||
//when
|
||||
objectUnderTest.deleteCategoryById(CATEGORY_ID, parametersMock);
|
||||
@@ -317,7 +329,7 @@ public class CategoriesImplTest
|
||||
public void testDeleteCategoryById_nonCategoryId()
|
||||
{
|
||||
given(authorityServiceMock.hasAdminAuthority()).willReturn(true);
|
||||
final NodeRef categoryNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, CATEGORY_ID);
|
||||
final NodeRef categoryNodeRef = createNodeRefWithId(CATEGORY_ID);
|
||||
given(nodesMock.validateNode(CATEGORY_ID)).willReturn(categoryNodeRef);
|
||||
given(nodesMock.isSubClass(categoryNodeRef, ContentModel.TYPE_CATEGORY, false)).willReturn(false);
|
||||
|
||||
@@ -336,7 +348,7 @@ public class CategoriesImplTest
|
||||
public void testDeleteCategoryById_rootCategory()
|
||||
{
|
||||
given(authorityServiceMock.hasAdminAuthority()).willReturn(true);
|
||||
final NodeRef categoryRootNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, CAT_ROOT_NODE_ID);
|
||||
final NodeRef categoryRootNodeRef = createNodeRefWithId(CAT_ROOT_NODE_ID);
|
||||
given(nodesMock.validateNode(CAT_ROOT_NODE_ID)).willReturn(categoryRootNodeRef);
|
||||
given(nodesMock.isSubClass(categoryRootNodeRef, ContentModel.TYPE_CATEGORY, false)).willReturn(true);
|
||||
given(categoryChildAssociationRefMock.getQName()).willReturn(ContentModel.ASPECT_GEN_CLASSIFIABLE);
|
||||
@@ -359,13 +371,13 @@ public class CategoriesImplTest
|
||||
@Test
|
||||
public void testCreateCategoryUnderRoot()
|
||||
{
|
||||
final NodeRef parentCategoryNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, PATH_ROOT);
|
||||
final NodeRef parentCategoryNodeRef = createNodeRefWithId(PATH_ROOT);
|
||||
given(categoryServiceMock.getRootCategoryNodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE))
|
||||
.willReturn(Optional.of(parentCategoryNodeRef));
|
||||
final NodeRef categoryNodeRef = createNodeRefWithId(CATEGORY_ID);
|
||||
given(categoryServiceMock.createCategory(parentCategoryNodeRef, CATEGORY_NAME)).willReturn(categoryNodeRef);
|
||||
given(nodesMock.getNode(CATEGORY_ID)).willReturn(prepareCategoryNode());
|
||||
final ChildAssociationRef parentAssoc = new ChildAssociationRef(null, parentCategoryNodeRef, null, categoryNodeRef);
|
||||
given(nodesMock.getNode(CATEGORY_ID)).willReturn(createNode());
|
||||
final ChildAssociationRef parentAssoc = createAssociationOf(parentCategoryNodeRef, categoryNodeRef);
|
||||
given(nodeServiceMock.getPrimaryParent(categoryNodeRef)).willReturn(parentAssoc);
|
||||
given(nodeServiceMock.getParentAssocs(parentCategoryNodeRef)).willReturn(List.of(parentAssoc));
|
||||
given(nodeServiceMock.getChildAssocs(categoryNodeRef, RegexQNamePattern.MATCH_ALL, RegexQNamePattern.MATCH_ALL, false))
|
||||
@@ -400,12 +412,12 @@ public class CategoriesImplTest
|
||||
@Test
|
||||
public void testCreateCategory()
|
||||
{
|
||||
final NodeRef parentCategoryNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, PARENT_ID);
|
||||
final NodeRef parentCategoryNodeRef = createNodeRefWithId(PARENT_ID);
|
||||
given(nodesMock.validateNode(PARENT_ID)).willReturn(parentCategoryNodeRef);
|
||||
final NodeRef categoryNodeRef = createNodeRefWithId(CATEGORY_ID);
|
||||
given(categoryServiceMock.createCategory(parentCategoryNodeRef, CATEGORY_NAME)).willReturn(categoryNodeRef);
|
||||
given(nodesMock.getNode(CATEGORY_ID)).willReturn(prepareCategoryNode());
|
||||
final ChildAssociationRef parentAssoc = new ChildAssociationRef(null, parentCategoryNodeRef, null, categoryNodeRef);
|
||||
given(nodesMock.getNode(CATEGORY_ID)).willReturn(createNode());
|
||||
final ChildAssociationRef parentAssoc = createAssociationOf(parentCategoryNodeRef, categoryNodeRef);
|
||||
given(nodeServiceMock.getPrimaryParent(categoryNodeRef)).willReturn(parentAssoc);
|
||||
given(nodeServiceMock.getParentAssocs(parentCategoryNodeRef)).willReturn(List.of(parentAssoc));
|
||||
given(nodeServiceMock.getChildAssocs(categoryNodeRef, RegexQNamePattern.MATCH_ALL, RegexQNamePattern.MATCH_ALL, false))
|
||||
@@ -438,6 +450,35 @@ public class CategoriesImplTest
|
||||
assertEquals(expectedCategory, createdCategory);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateCategory_includeCount()
|
||||
{
|
||||
final QName categoryQName = createCmQNameOf(CATEGORY_NAME);
|
||||
final NodeRef categoryNodeRef = createNodeRefWithId(CATEGORY_ID);
|
||||
final NodeRef parentCategoryNodeRef = createNodeRefWithId(PARENT_ID);
|
||||
final ChildAssociationRef parentAssociation = createAssociationOf(parentCategoryNodeRef, CATEGORY_NODE_REF, categoryQName);
|
||||
given(nodesMock.validateNode(PARENT_ID)).willReturn(parentCategoryNodeRef);
|
||||
given(categoryServiceMock.createCategory(parentCategoryNodeRef, CATEGORY_NAME)).willReturn(categoryNodeRef);
|
||||
given(nodesMock.getNode(any())).willReturn(createNode());
|
||||
given(nodeServiceMock.getPrimaryParent(any())).willReturn(parentAssociation);
|
||||
given(parametersMock.getInclude()).willReturn(List.of(INCLUDE_COUNT_PARAM));
|
||||
final List<Category> categoryModels = prepareCategories().stream().peek(category -> category.setCount(1)).collect(Collectors.toList());
|
||||
|
||||
// when
|
||||
final List<Category> actualCreatedCategories = objectUnderTest.createSubcategories(PARENT_ID, categoryModels, parametersMock);
|
||||
|
||||
then(categoryServiceMock).should().createCategory(any(), any());
|
||||
then(categoryServiceMock).shouldHaveNoMoreInteractions();
|
||||
|
||||
assertThat(actualCreatedCategories)
|
||||
.isNotNull()
|
||||
.hasSize(1)
|
||||
.element(0)
|
||||
.extracting(Category::getCount)
|
||||
.isNotNull()
|
||||
.isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateCategories_noPermissions()
|
||||
{
|
||||
@@ -494,7 +535,7 @@ public class CategoriesImplTest
|
||||
@Test
|
||||
public void testGetRootCategoryChildren()
|
||||
{
|
||||
final NodeRef parentCategoryNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, PATH_ROOT);
|
||||
final NodeRef parentCategoryNodeRef = createNodeRefWithId(PATH_ROOT);
|
||||
given(categoryServiceMock.getRootCategoryNodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE))
|
||||
.willReturn(Optional.of(parentCategoryNodeRef));
|
||||
final int childrenCount = 3;
|
||||
@@ -503,7 +544,7 @@ public class CategoriesImplTest
|
||||
childAssociationRefMocks.forEach(this::prepareCategoryNodeMocks);
|
||||
|
||||
//when
|
||||
final CollectionWithPagingInfo<Category> categoryChildren = objectUnderTest.getCategoryChildren(PATH_ROOT, parametersMock);
|
||||
final List<Category> categoryChildren = objectUnderTest.getCategoryChildren(PATH_ROOT, parametersMock);
|
||||
|
||||
then(categoryServiceMock).should().getRootCategoryNodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE);
|
||||
then(categoryServiceMock).shouldHaveNoMoreInteractions();
|
||||
@@ -521,16 +562,14 @@ public class CategoriesImplTest
|
||||
|
||||
then(authorityServiceMock).shouldHaveNoInteractions();
|
||||
|
||||
assertEquals(childAssociationRefMocks.size(), categoryChildren.getTotalItems().intValue());
|
||||
final List<Category> categoryChildrenList = new ArrayList<>(categoryChildren.getCollection());
|
||||
assertEquals(childAssociationRefMocks.size(), categoryChildrenList.size());
|
||||
IntStream.range(0, childrenCount).forEach(i -> doCategoryAssertions(categoryChildrenList.get(i), i, PATH_ROOT));
|
||||
assertEquals(childAssociationRefMocks.size(), categoryChildren.size());
|
||||
IntStream.range(0, childrenCount).forEach(i -> doCategoryAssertions(categoryChildren.get(i), i, PATH_ROOT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCategoryChildren()
|
||||
{
|
||||
final NodeRef parentCategoryNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, PARENT_ID);
|
||||
final NodeRef parentCategoryNodeRef = createNodeRefWithId(PARENT_ID);
|
||||
given(nodesMock.validateNode(PARENT_ID)).willReturn(parentCategoryNodeRef);
|
||||
given(nodesMock.isSubClass(parentCategoryNodeRef, ContentModel.TYPE_CATEGORY, false)).willReturn(true);
|
||||
final int childrenCount = 3;
|
||||
@@ -539,7 +578,7 @@ public class CategoriesImplTest
|
||||
childAssociationRefMocks.forEach(this::prepareCategoryNodeMocks);
|
||||
|
||||
//when
|
||||
final CollectionWithPagingInfo<Category> categoryChildren = objectUnderTest.getCategoryChildren(PARENT_ID, parametersMock);
|
||||
final List<Category> categoryChildren = objectUnderTest.getCategoryChildren(PARENT_ID, parametersMock);
|
||||
|
||||
then(nodesMock).should().validateNode(PARENT_ID);
|
||||
then(nodesMock).should().isSubClass(parentCategoryNodeRef, ContentModel.TYPE_CATEGORY, false);
|
||||
@@ -558,24 +597,47 @@ public class CategoriesImplTest
|
||||
then(authorityServiceMock).shouldHaveNoInteractions();
|
||||
then(categoryServiceMock).shouldHaveNoInteractions();
|
||||
|
||||
assertEquals(childAssociationRefMocks.size(), categoryChildren.getTotalItems().intValue());
|
||||
final List<Category> categoryChildrenList = new ArrayList<>(categoryChildren.getCollection());
|
||||
assertEquals(childAssociationRefMocks.size(), categoryChildrenList.size());
|
||||
IntStream.range(0, childrenCount).forEach(i -> doCategoryAssertions(categoryChildrenList.get(i), i, PARENT_ID));
|
||||
assertEquals(childAssociationRefMocks.size(), categoryChildren.size());
|
||||
IntStream.range(0, childrenCount).forEach(i -> doCategoryAssertions(categoryChildren.get(i), i, PARENT_ID));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCategoryChildren_includeCount()
|
||||
{
|
||||
final NodeRef parentCategoryNodeRef = createNodeRefWithId(PARENT_ID);
|
||||
given(nodesMock.validateNode(PARENT_ID)).willReturn(parentCategoryNodeRef);
|
||||
given(nodesMock.isSubClass(parentCategoryNodeRef, ContentModel.TYPE_CATEGORY, false)).willReturn(true);
|
||||
final int childrenCount = 3;
|
||||
final List<ChildAssociationRef> childAssociationRefMocks = prepareChildAssocMocks(childrenCount, parentCategoryNodeRef);
|
||||
given(nodeServiceMock.getChildAssocs(parentCategoryNodeRef)).willReturn(childAssociationRefMocks);
|
||||
childAssociationRefMocks.forEach(this::prepareCategoryNodeMocks);
|
||||
given(parametersMock.getInclude()).willReturn(List.of(INCLUDE_COUNT_PARAM));
|
||||
given(categoryServiceMock.getTopCategories(any(), any(), anyInt())).willReturn(List.of(new Pair<>(createNodeRefWithId(CATEGORY_ID.concat("-1")), 2)));
|
||||
|
||||
// when
|
||||
final List<Category> actualCategoryChildren = objectUnderTest.getCategoryChildren(PARENT_ID, parametersMock);
|
||||
|
||||
then(categoryServiceMock).should().getTopCategories(STORE_REF_WORKSPACE_SPACESSTORE, ContentModel.ASPECT_GEN_CLASSIFIABLE, Integer.MAX_VALUE);
|
||||
then(categoryServiceMock).shouldHaveNoMoreInteractions();
|
||||
|
||||
assertThat(actualCategoryChildren)
|
||||
.isNotNull()
|
||||
.hasSize(3)
|
||||
.extracting(Category::getCount)
|
||||
.isNotNull()
|
||||
.isEqualTo(List.of(0, 2, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCategoryChildren_noChildren()
|
||||
{
|
||||
final NodeRef parentCategoryNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, PARENT_ID);
|
||||
final NodeRef parentCategoryNodeRef = createNodeRefWithId(PARENT_ID);
|
||||
given(nodesMock.validateNode(PARENT_ID)).willReturn(parentCategoryNodeRef);
|
||||
given(nodesMock.isSubClass(parentCategoryNodeRef, ContentModel.TYPE_CATEGORY, false)).willReturn(true);
|
||||
|
||||
given(nodeServiceMock.getChildAssocs(parentCategoryNodeRef)).willReturn(Collections.emptyList());
|
||||
|
||||
|
||||
//when
|
||||
final CollectionWithPagingInfo<Category> categoryChildren = objectUnderTest.getCategoryChildren(PARENT_ID, parametersMock);
|
||||
final List<Category> categoryChildren = objectUnderTest.getCategoryChildren(PARENT_ID, parametersMock);
|
||||
|
||||
then(nodesMock).should().validateNode(PARENT_ID);
|
||||
then(nodesMock).should().isSubClass(parentCategoryNodeRef, ContentModel.TYPE_CATEGORY, false);
|
||||
@@ -586,13 +648,13 @@ public class CategoriesImplTest
|
||||
then(authorityServiceMock).shouldHaveNoInteractions();
|
||||
then(categoryServiceMock).shouldHaveNoInteractions();
|
||||
|
||||
assertEquals(0, categoryChildren.getTotalItems().intValue());
|
||||
assertEquals(0, categoryChildren.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCategoryChildren_wrongParentNodeType()
|
||||
{
|
||||
final NodeRef parentCategoryNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, PARENT_ID);
|
||||
final NodeRef parentCategoryNodeRef = createNodeRefWithId(PARENT_ID);
|
||||
given(nodesMock.validateNode(PARENT_ID)).willReturn(parentCategoryNodeRef);
|
||||
given(nodesMock.isSubClass(parentCategoryNodeRef, ContentModel.TYPE_CATEGORY, false)).willReturn(false);
|
||||
|
||||
@@ -633,12 +695,12 @@ public class CategoriesImplTest
|
||||
final QName categoryQName = createCmQNameOf(CATEGORY_NAME);
|
||||
final NodeRef parentCategoryNodeRef = createNodeRefWithId(PARENT_ID);
|
||||
final ChildAssociationRef parentAssociation = createAssociationOf(parentCategoryNodeRef, CATEGORY_NODE_REF, categoryQName);
|
||||
given(nodesMock.getNode(any())).willReturn(prepareCategoryNode(categoryNewName));
|
||||
given(nodesMock.getNode(any())).willReturn(createNode(categoryNewName));
|
||||
given(nodeServiceMock.getPrimaryParent(any())).willReturn(parentAssociation);
|
||||
given(nodeServiceMock.moveNode(any(), any(), any(), any())).willReturn(createAssociationOf(parentCategoryNodeRef, CATEGORY_NODE_REF, createCmQNameOf(categoryNewName)));
|
||||
|
||||
// when
|
||||
final Category actualCategory = objectUnderTest.updateCategoryById(CATEGORY_ID, fixedCategory);
|
||||
final Category actualCategory = objectUnderTest.updateCategoryById(CATEGORY_ID, fixedCategory, parametersMock);
|
||||
|
||||
then(authorityServiceMock).should().hasAdminAuthority();
|
||||
then(authorityServiceMock).shouldHaveNoMoreInteractions();
|
||||
@@ -661,13 +723,41 @@ public class CategoriesImplTest
|
||||
.isEqualTo(expectedCategory);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateCategoryById_includeCount()
|
||||
{
|
||||
final String categoryNewName = "categoryNewName";
|
||||
final Category fixedCategory = createCategoryOnlyWithName(categoryNewName);
|
||||
fixedCategory.setCount(9);
|
||||
final QName categoryQName = createCmQNameOf(CATEGORY_NAME);
|
||||
final NodeRef parentCategoryNodeRef = createNodeRefWithId(PARENT_ID);
|
||||
final ChildAssociationRef parentAssociation = createAssociationOf(parentCategoryNodeRef, CATEGORY_NODE_REF, categoryQName);
|
||||
given(nodesMock.getNode(any())).willReturn(createNode(categoryNewName));
|
||||
given(nodeServiceMock.getPrimaryParent(any())).willReturn(parentAssociation);
|
||||
given(nodeServiceMock.moveNode(any(), any(), any(), any())).willReturn(createAssociationOf(parentCategoryNodeRef, CATEGORY_NODE_REF, createCmQNameOf(categoryNewName)));
|
||||
given(parametersMock.getInclude()).willReturn(List.of(INCLUDE_COUNT_PARAM));
|
||||
given(categoryServiceMock.getTopCategories(any(), any(), anyInt())).willReturn(List.of(new Pair<>(CATEGORY_NODE_REF, 1)));
|
||||
|
||||
// when
|
||||
final Category actualCategory = objectUnderTest.updateCategoryById(CATEGORY_ID, fixedCategory, parametersMock);
|
||||
|
||||
then(categoryServiceMock).should().getTopCategories(STORE_REF_WORKSPACE_SPACESSTORE, ContentModel.ASPECT_GEN_CLASSIFIABLE, Integer.MAX_VALUE);
|
||||
then(categoryServiceMock).shouldHaveNoMoreInteractions();
|
||||
|
||||
assertThat(actualCategory)
|
||||
.isNotNull()
|
||||
.extracting(Category::getCount)
|
||||
.isNotNull()
|
||||
.isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateCategoryById_noPermission()
|
||||
{
|
||||
given(authorityServiceMock.hasAdminAuthority()).willReturn(false);
|
||||
|
||||
// when
|
||||
assertThatExceptionOfType(PermissionDeniedException.class).isThrownBy(() -> objectUnderTest.updateCategoryById(CATEGORY_ID, CATEGORY));
|
||||
assertThatExceptionOfType(PermissionDeniedException.class).isThrownBy(() -> objectUnderTest.updateCategoryById(CATEGORY_ID, CATEGORY, parametersMock));
|
||||
|
||||
then(nodesMock).shouldHaveNoInteractions();
|
||||
then(nodeServiceMock).shouldHaveNoInteractions();
|
||||
@@ -679,7 +769,7 @@ public class CategoriesImplTest
|
||||
given(nodesMock.validateNode(any(String.class))).willThrow(EntityNotFoundException.class);
|
||||
|
||||
// when
|
||||
assertThatExceptionOfType(EntityNotFoundException.class).isThrownBy(() -> objectUnderTest.updateCategoryById(CATEGORY_ID, CATEGORY));
|
||||
assertThatExceptionOfType(EntityNotFoundException.class).isThrownBy(() -> objectUnderTest.updateCategoryById(CATEGORY_ID, CATEGORY, parametersMock));
|
||||
|
||||
then(nodeServiceMock).shouldHaveNoInteractions();
|
||||
}
|
||||
@@ -690,7 +780,7 @@ public class CategoriesImplTest
|
||||
given(nodesMock.isSubClass(any(), any(), eq(false))).willReturn(false);
|
||||
|
||||
// when
|
||||
assertThatExceptionOfType(InvalidArgumentException.class).isThrownBy(() -> objectUnderTest.updateCategoryById(CATEGORY_ID, CATEGORY))
|
||||
assertThatExceptionOfType(InvalidArgumentException.class).isThrownBy(() -> objectUnderTest.updateCategoryById(CATEGORY_ID, CATEGORY, parametersMock))
|
||||
.withMessageContaining(NOT_A_VALID_CATEGORY);
|
||||
|
||||
then(nodeServiceMock).shouldHaveNoInteractions();
|
||||
@@ -704,7 +794,7 @@ public class CategoriesImplTest
|
||||
given(categoryChildAssociationRefMock.getQName()).willReturn(ContentModel.ASPECT_GEN_CLASSIFIABLE);
|
||||
|
||||
// when
|
||||
assertThatExceptionOfType(InvalidArgumentException.class).isThrownBy(() -> objectUnderTest.updateCategoryById(PATH_ROOT, CATEGORY))
|
||||
assertThatExceptionOfType(InvalidArgumentException.class).isThrownBy(() -> objectUnderTest.updateCategoryById(PATH_ROOT, CATEGORY, parametersMock))
|
||||
.withMessageContaining(NOT_A_VALID_CATEGORY);
|
||||
|
||||
then(categoryServiceMock).should().getRootCategoryNodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE);
|
||||
@@ -727,7 +817,7 @@ public class CategoriesImplTest
|
||||
final Category categoryWithoutName = createCategoryOnlyWithName(invalidName);
|
||||
|
||||
// when
|
||||
assertThatExceptionOfType(InvalidArgumentException.class).isThrownBy(() -> objectUnderTest.updateCategoryById(CATEGORY_ID, categoryWithoutName))
|
||||
assertThatExceptionOfType(InvalidArgumentException.class).isThrownBy(() -> objectUnderTest.updateCategoryById(CATEGORY_ID, categoryWithoutName, parametersMock))
|
||||
.withMessageContaining(NOT_NULL_OR_EMPTY);
|
||||
}
|
||||
}
|
||||
@@ -741,12 +831,12 @@ public class CategoriesImplTest
|
||||
final QName categoryQName = createCmQNameOf(CATEGORY_NAME);
|
||||
final NodeRef parentCategoryNodeRef = createNodeRefWithId(PARENT_ID);
|
||||
final ChildAssociationRef parentAssociation = createAssociationOf(parentCategoryNodeRef, CATEGORY_NODE_REF, categoryQName);
|
||||
given(nodesMock.getNode(any())).willReturn(prepareCategoryNode(categoryNewName));
|
||||
given(nodesMock.getNode(any())).willReturn(createNode(categoryNewName));
|
||||
given(nodeServiceMock.getPrimaryParent(any())).willReturn(parentAssociation);
|
||||
given(nodeServiceMock.moveNode(any(), any(), any(), any())).willReturn(createAssociationOf(parentCategoryNodeRef, CATEGORY_NODE_REF, createCmQNameOf(categoryNewName)));
|
||||
|
||||
// when
|
||||
final Category actualCategory = objectUnderTest.updateCategoryById(CATEGORY_ID, categoryWithInvalidId);
|
||||
final Category actualCategory = objectUnderTest.updateCategoryById(CATEGORY_ID, categoryWithInvalidId, parametersMock);
|
||||
|
||||
final Category expectedCategory = createDefaultCategoryWithName(categoryNewName);
|
||||
assertThat(actualCategory)
|
||||
@@ -763,12 +853,12 @@ public class CategoriesImplTest
|
||||
final QName categoryQName = createCmQNameOf(CATEGORY_NAME);
|
||||
final NodeRef parentCategoryNodeRef = createNodeRefWithId(PARENT_ID);
|
||||
final ChildAssociationRef parentAssociation = createAssociationOf(parentCategoryNodeRef, CATEGORY_NODE_REF, categoryQName);
|
||||
given(nodesMock.getNode(any())).willReturn(prepareCategoryNode(categoryNewName));
|
||||
given(nodesMock.getNode(any())).willReturn(createNode(categoryNewName));
|
||||
given(nodeServiceMock.getPrimaryParent(any())).willReturn(parentAssociation);
|
||||
given(nodeServiceMock.moveNode(any(), any(), any(), any())).willReturn(createAssociationOf(parentCategoryNodeRef, CATEGORY_NODE_REF, createCmQNameOf(categoryNewName)));
|
||||
|
||||
// when
|
||||
final Category actualCategory = objectUnderTest.updateCategoryById(CATEGORY_ID, categoryWithInvalidParentId);
|
||||
final Category actualCategory = objectUnderTest.updateCategoryById(CATEGORY_ID, categoryWithInvalidParentId, parametersMock);
|
||||
|
||||
final Category expectedCategory = createDefaultCategoryWithName(categoryNewName);
|
||||
assertThat(actualCategory)
|
||||
@@ -785,12 +875,12 @@ public class CategoriesImplTest
|
||||
final QName categoryQName = createCmQNameOf(CATEGORY_NAME);
|
||||
final NodeRef parentCategoryNodeRef = createNodeRefWithId(PARENT_ID);
|
||||
final ChildAssociationRef parentAssociation = createAssociationOf(parentCategoryNodeRef, CATEGORY_NODE_REF, categoryQName);
|
||||
given(nodesMock.getNode(any())).willReturn(prepareCategoryNode(categoryNewName));
|
||||
given(nodesMock.getNode(any())).willReturn(createNode(categoryNewName));
|
||||
given(nodeServiceMock.getPrimaryParent(any())).willReturn(parentAssociation);
|
||||
given(nodeServiceMock.moveNode(any(), any(), any(), any())).willReturn(createAssociationOf(parentCategoryNodeRef, CATEGORY_NODE_REF, createCmQNameOf(categoryNewName)));
|
||||
|
||||
// when
|
||||
final Category actualCategory = objectUnderTest.updateCategoryById(CATEGORY_ID, categoryWithInvalidHasChildren);
|
||||
final Category actualCategory = objectUnderTest.updateCategoryById(CATEGORY_ID, categoryWithInvalidHasChildren, parametersMock);
|
||||
|
||||
final Category expectedCategory = createDefaultCategoryWithName(categoryNewName);
|
||||
assertThat(actualCategory)
|
||||
@@ -804,11 +894,11 @@ public class CategoriesImplTest
|
||||
final List<Category> categoryLinks = List.of(CATEGORY);
|
||||
final NodeRef categoryParentNodeRef = createNodeRefWithId(PARENT_ID);
|
||||
final ChildAssociationRef parentAssociation = createAssociationOf(categoryParentNodeRef, CATEGORY_NODE_REF);
|
||||
given(nodesMock.getNode(any())).willReturn(prepareCategoryNode());
|
||||
given(nodesMock.getNode(any())).willReturn(createNode());
|
||||
given(nodeServiceMock.getPrimaryParent(any())).willReturn(parentAssociation);
|
||||
|
||||
// when
|
||||
final List<Category> actualLinkedCategories = objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, categoryLinks);
|
||||
final List<Category> actualLinkedCategories = objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, categoryLinks, parametersMock);
|
||||
|
||||
then(nodesMock).should().validateNode(CONTENT_NODE_ID);
|
||||
then(permissionServiceMock).should().hasPermission(CONTENT_NODE_REF, PermissionService.CHANGE_PERMISSIONS);
|
||||
@@ -838,12 +928,12 @@ public class CategoriesImplTest
|
||||
{
|
||||
final NodeRef categoryParentNodeRef = createNodeRefWithId(PARENT_ID);
|
||||
final ChildAssociationRef parentAssociation = createAssociationOf(categoryParentNodeRef, CATEGORY_NODE_REF);
|
||||
given(nodesMock.getNode(any())).willReturn(prepareCategoryNode());
|
||||
given(nodesMock.getNode(any())).willReturn(createNode());
|
||||
given(nodeServiceMock.getPrimaryParent(any())).willReturn(parentAssociation);
|
||||
given(nodeServiceMock.hasAspect(any(), any())).willReturn(true);
|
||||
|
||||
// when
|
||||
final List<Category> actualLinkedCategories = objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, List.of(CATEGORY));
|
||||
final List<Category> actualLinkedCategories = objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, List.of(CATEGORY), parametersMock);
|
||||
|
||||
then(nodesMock).should().getNode(CATEGORY_ID);
|
||||
then(nodeServiceMock).should().getChildAssocs(CATEGORY_NODE_REF, RegexQNamePattern.MATCH_ALL, RegexQNamePattern.MATCH_ALL, false);
|
||||
@@ -873,11 +963,11 @@ public class CategoriesImplTest
|
||||
final ChildAssociationRef categoryParentAssociation = createAssociationOf(categoryParentNodeRef, CATEGORY_NODE_REF);
|
||||
final ChildAssociationRef secondCategoryParentAssociation = createAssociationOf(categoryParentNodeRef, secondCategoryNodeRef);
|
||||
given(nodesMock.validateNode(secondCategoryId)).willReturn(secondCategoryNodeRef);
|
||||
given(nodesMock.getNode(any())).willReturn(prepareCategoryNode(), prepareCategoryNode(secondCategoryName));
|
||||
given(nodesMock.getNode(any())).willReturn(createNode(), createNode(secondCategoryName));
|
||||
given(nodeServiceMock.getPrimaryParent(any())).willReturn(categoryParentAssociation, secondCategoryParentAssociation);
|
||||
|
||||
// when
|
||||
final List<Category> actualLinkedCategories = objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, categoryLinks);
|
||||
final List<Category> actualLinkedCategories = objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, categoryLinks, parametersMock);
|
||||
|
||||
then(nodesMock).should().validateNode(CATEGORY_ID);
|
||||
then(nodesMock).should().validateNode(secondCategoryId);
|
||||
@@ -902,13 +992,13 @@ public class CategoriesImplTest
|
||||
final Serializable previousCategories = (Serializable) List.of(otherCategoryNodeRef);
|
||||
final NodeRef categoryParentNodeRef = createNodeRefWithId(PARENT_ID);
|
||||
final ChildAssociationRef parentAssociation = createAssociationOf(categoryParentNodeRef, CATEGORY_NODE_REF);
|
||||
given(nodesMock.getNode(any())).willReturn(prepareCategoryNode());
|
||||
given(nodesMock.getNode(any())).willReturn(createNode());
|
||||
given(nodeServiceMock.getPrimaryParent(any())).willReturn(parentAssociation);
|
||||
given(nodeServiceMock.hasAspect(any(), any())).willReturn(true);
|
||||
given(nodeServiceMock.getProperty(any(), eq(ContentModel.PROP_CATEGORIES))).willReturn(previousCategories);
|
||||
|
||||
// when
|
||||
final List<Category> actualLinkedCategories = objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, List.of(CATEGORY));
|
||||
final List<Category> actualLinkedCategories = objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, List.of(CATEGORY), parametersMock);
|
||||
|
||||
final Serializable expectedCategories = (Serializable) Set.of(otherCategoryNodeRef, CATEGORY_NODE_REF);
|
||||
then(nodeServiceMock).should().setProperty(CONTENT_NODE_REF, ContentModel.PROP_CATEGORIES, expectedCategories);
|
||||
@@ -924,7 +1014,7 @@ public class CategoriesImplTest
|
||||
given(nodesMock.validateNode(CONTENT_NODE_ID)).willThrow(EntityNotFoundException.class);
|
||||
|
||||
// when
|
||||
final Throwable actualException = catchThrowable(() -> objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, List.of(CATEGORY)));
|
||||
final Throwable actualException = catchThrowable(() -> objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, List.of(CATEGORY), parametersMock));
|
||||
|
||||
then(nodesMock).should().validateNode(CONTENT_NODE_ID);
|
||||
then(permissionServiceMock).shouldHaveNoInteractions();
|
||||
@@ -939,7 +1029,7 @@ public class CategoriesImplTest
|
||||
given(permissionServiceMock.hasPermission(any(), any())).willReturn(AccessStatus.DENIED);
|
||||
|
||||
// when
|
||||
final Throwable actualException = catchThrowable(() -> objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, List.of(CATEGORY)));
|
||||
final Throwable actualException = catchThrowable(() -> objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, List.of(CATEGORY), parametersMock));
|
||||
|
||||
then(nodesMock).should().validateNode(CONTENT_NODE_ID);
|
||||
then(permissionServiceMock).should().hasPermission(CONTENT_NODE_REF, PermissionService.CHANGE_PERMISSIONS);
|
||||
@@ -954,9 +1044,8 @@ public class CategoriesImplTest
|
||||
{
|
||||
given(typeConstraint.matches(any())).willReturn(false);
|
||||
|
||||
|
||||
// when
|
||||
final Throwable actualException = catchThrowable(() -> objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, List.of(CATEGORY)));
|
||||
final Throwable actualException = catchThrowable(() -> objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, List.of(CATEGORY), parametersMock));
|
||||
|
||||
then(typeConstraint).should().matches(CONTENT_NODE_REF);
|
||||
then(nodeServiceMock).shouldHaveNoInteractions();
|
||||
@@ -971,7 +1060,7 @@ public class CategoriesImplTest
|
||||
final List<Category> categoryLinks = Collections.emptyList();
|
||||
|
||||
// when
|
||||
final Throwable actualException = catchThrowable(() -> objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, categoryLinks));
|
||||
final Throwable actualException = catchThrowable(() -> objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, categoryLinks, parametersMock));
|
||||
|
||||
then(nodesMock).shouldHaveNoInteractions();
|
||||
then(permissionServiceMock).shouldHaveNoInteractions();
|
||||
@@ -992,7 +1081,7 @@ public class CategoriesImplTest
|
||||
categoryLinks.add(categoryLinkWithEmptyId);
|
||||
|
||||
// when
|
||||
final Throwable actualException = catchThrowable(() -> objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, categoryLinks));
|
||||
final Throwable actualException = catchThrowable(() -> objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, categoryLinks, parametersMock));
|
||||
|
||||
then(nodeServiceMock).shouldHaveNoInteractions();
|
||||
assertThat(actualException)
|
||||
@@ -1006,11 +1095,11 @@ public class CategoriesImplTest
|
||||
final List<Category> categoryLinks = List.of(CATEGORY, CATEGORY);
|
||||
final NodeRef categoryParentNodeRef = createNodeRefWithId(PARENT_ID);
|
||||
final ChildAssociationRef parentAssociation = createAssociationOf(categoryParentNodeRef, CATEGORY_NODE_REF);
|
||||
given(nodesMock.getNode(any())).willReturn(prepareCategoryNode());
|
||||
given(nodesMock.getNode(any())).willReturn(createNode());
|
||||
given(nodeServiceMock.getPrimaryParent(any())).willReturn(parentAssociation);
|
||||
|
||||
// when
|
||||
final List<Category> actualLinkedCategories = objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, categoryLinks);
|
||||
final List<Category> actualLinkedCategories = objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, categoryLinks, parametersMock);
|
||||
|
||||
final Map<QName, Serializable> expectedProperties = Map.of(ContentModel.PROP_CATEGORIES, (Serializable) List.of(CATEGORY_NODE_REF));
|
||||
then(nodeServiceMock).should().addAspect(CONTENT_NODE_REF, ContentModel.ASPECT_GEN_CLASSIFIABLE, expectedProperties);
|
||||
@@ -1045,7 +1134,7 @@ public class CategoriesImplTest
|
||||
given(nodeServiceMock.hasAspect(CONTENT_NODE_REF, ContentModel.ASPECT_GEN_CLASSIFIABLE)).willReturn(false);
|
||||
|
||||
//when
|
||||
final Throwable actualException = catchThrowable(() -> objectUnderTest.unlinkNodeFromCategory(CONTENT_NODE_ID,CATEGORY_ID, parametersMock));
|
||||
final Throwable actualException = catchThrowable(() -> objectUnderTest.unlinkNodeFromCategory(CONTENT_NODE_ID, CATEGORY_ID, parametersMock));
|
||||
|
||||
then(nodeServiceMock).should().hasAspect(CONTENT_NODE_REF,ContentModel.ASPECT_GEN_CLASSIFIABLE);
|
||||
then(nodeServiceMock).shouldHaveNoMoreInteractions();
|
||||
@@ -1060,11 +1149,11 @@ public class CategoriesImplTest
|
||||
final NodeRef categoryParentNodeRef = createNodeRefWithId(PARENT_ID);
|
||||
final ChildAssociationRef parentAssociation = createAssociationOf(categoryParentNodeRef, CATEGORY_NODE_REF);
|
||||
given(nodeServiceMock.getProperty(any(), eq(ContentModel.PROP_CATEGORIES))).willReturn((Serializable) List.of(CATEGORY_NODE_REF));
|
||||
given(nodesMock.getNode(any())).willReturn(prepareCategoryNode());
|
||||
given(nodesMock.getNode(any())).willReturn(createNode());
|
||||
given(nodeServiceMock.getPrimaryParent(any())).willReturn(parentAssociation);
|
||||
|
||||
// when
|
||||
final List<Category> actualCategories = objectUnderTest.listCategoriesForNode(CONTENT_NODE_ID);
|
||||
final List<Category> actualCategories = objectUnderTest.listCategoriesForNode(CONTENT_NODE_ID, parametersMock);
|
||||
|
||||
then(nodesMock).should().validateNode(CONTENT_NODE_ID);
|
||||
then(permissionServiceMock).should().hasReadPermission(CONTENT_NODE_REF);
|
||||
@@ -1090,7 +1179,7 @@ public class CategoriesImplTest
|
||||
given(nodesMock.validateNode(CONTENT_NODE_ID)).willThrow(EntityNotFoundException.class);
|
||||
|
||||
// when
|
||||
final Throwable actualException = catchThrowable(() -> objectUnderTest.listCategoriesForNode(CONTENT_NODE_ID));
|
||||
final Throwable actualException = catchThrowable(() -> objectUnderTest.listCategoriesForNode(CONTENT_NODE_ID, parametersMock));
|
||||
|
||||
then(nodesMock).should().validateNode(CONTENT_NODE_ID);
|
||||
then(nodeServiceMock).shouldHaveNoInteractions();
|
||||
@@ -1104,7 +1193,7 @@ public class CategoriesImplTest
|
||||
given(permissionServiceMock.hasReadPermission(any())).willReturn(AccessStatus.DENIED);
|
||||
|
||||
// when
|
||||
final Throwable actualException = catchThrowable(() -> objectUnderTest.listCategoriesForNode(CONTENT_NODE_ID));
|
||||
final Throwable actualException = catchThrowable(() -> objectUnderTest.listCategoriesForNode(CONTENT_NODE_ID, parametersMock));
|
||||
|
||||
then(nodesMock).should().validateNode(CONTENT_NODE_ID);
|
||||
then(permissionServiceMock).should().hasReadPermission(CONTENT_NODE_REF);
|
||||
@@ -1120,7 +1209,7 @@ public class CategoriesImplTest
|
||||
given(typeConstraint.matches(any())).willReturn(false);
|
||||
|
||||
// when
|
||||
final Throwable actualException = catchThrowable(() -> objectUnderTest.listCategoriesForNode(CONTENT_NODE_ID));
|
||||
final Throwable actualException = catchThrowable(() -> objectUnderTest.listCategoriesForNode(CONTENT_NODE_ID, parametersMock));
|
||||
|
||||
then(typeConstraint).should().matches(CONTENT_NODE_REF);
|
||||
then(nodeServiceMock).shouldHaveNoInteractions();
|
||||
@@ -1136,45 +1225,19 @@ public class CategoriesImplTest
|
||||
given(nodeServiceMock.getProperty(any(), eq(ContentModel.PROP_CATEGORIES))).willReturn((Serializable) nullOrEmptyList);
|
||||
|
||||
// when
|
||||
final List<Category> actualCategories = objectUnderTest.listCategoriesForNode(CONTENT_NODE_ID);
|
||||
final List<Category> actualCategories = objectUnderTest.listCategoriesForNode(CONTENT_NODE_ID, parametersMock);
|
||||
|
||||
assertThat(actualCategories).isNotNull().isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
private Node prepareCategoryNode(final String name, final String id, final NodeRef parentNodeRef)
|
||||
{
|
||||
final Node categoryNode = new Node();
|
||||
categoryNode.setName(name);
|
||||
categoryNode.setNodeId(id);
|
||||
categoryNode.setParentId(parentNodeRef);
|
||||
return categoryNode;
|
||||
}
|
||||
|
||||
private Node prepareCategoryNode(final String name)
|
||||
{
|
||||
return prepareCategoryNode(name, CATEGORY_ID, createNodeRefWithId(PARENT_ID));
|
||||
}
|
||||
|
||||
private Node prepareCategoryNode()
|
||||
{
|
||||
return prepareCategoryNode(CATEGORY_NAME);
|
||||
}
|
||||
|
||||
private List<Category> prepareCategories()
|
||||
{
|
||||
return List.of(Category.builder()
|
||||
.name(CATEGORY_NAME)
|
||||
.create());
|
||||
}
|
||||
|
||||
private List<ChildAssociationRef> prepareChildAssocMocks(final int count, NodeRef parentCategoryNodeRef)
|
||||
{
|
||||
return IntStream.range(0, count).mapToObj(i -> {
|
||||
ChildAssociationRef dummyChildAssocMock = mock(ChildAssociationRef.class);
|
||||
given(dummyChildAssocMock.getTypeQName()).willReturn(ContentModel.ASSOC_SUBCATEGORIES);
|
||||
given(dummyChildAssocMock.getChildRef())
|
||||
.willReturn(new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, CATEGORY_ID + "-" + i));
|
||||
.willReturn(createNodeRefWithId(CATEGORY_ID + "-" + i));
|
||||
given(dummyChildAssocMock.getParentRef()).willReturn(parentCategoryNodeRef);
|
||||
return dummyChildAssocMock;
|
||||
}).collect(Collectors.toList());
|
||||
@@ -1186,8 +1249,8 @@ public class CategoriesImplTest
|
||||
final String id = childRef.getId();
|
||||
final String name = id.replace(CATEGORY_ID, CATEGORY_NAME);
|
||||
final NodeRef parentRef = childAssociationRef.getParentRef();
|
||||
given(nodesMock.getNode(id)).willReturn(prepareCategoryNode(name, id, parentRef));
|
||||
final ChildAssociationRef parentAssoc = new ChildAssociationRef(null, parentRef, null, childRef);
|
||||
given(nodesMock.getNode(id)).willReturn(createNode(name, id, parentRef));
|
||||
final ChildAssociationRef parentAssoc = createAssociationOf(parentRef, childRef);
|
||||
given(nodeServiceMock.getPrimaryParent(childRef)).willReturn(parentAssoc);
|
||||
given(nodeServiceMock.getParentAssocs(parentRef)).willReturn(List.of(parentAssoc));
|
||||
}
|
||||
@@ -1195,14 +1258,38 @@ public class CategoriesImplTest
|
||||
private void doCategoryAssertions(final Category category, final int index, final String parentId)
|
||||
{
|
||||
final Category expectedCategory = Category.builder()
|
||||
.id(CATEGORY_ID + "-" + index)
|
||||
.name(CATEGORY_NAME + "-" + index)
|
||||
.parentId(parentId)
|
||||
.hasChildren(false)
|
||||
.create();
|
||||
.id(CATEGORY_ID + "-" + index)
|
||||
.name(CATEGORY_NAME + "-" + index)
|
||||
.parentId(parentId)
|
||||
.hasChildren(false)
|
||||
.create();
|
||||
assertEquals(expectedCategory, category);
|
||||
}
|
||||
|
||||
private List<Category> prepareCategories()
|
||||
{
|
||||
return List.of(createCategoryOnlyWithName(CATEGORY_NAME));
|
||||
}
|
||||
|
||||
private static Node createNode(final String name, final String id, final NodeRef parentNodeRef)
|
||||
{
|
||||
final Node categoryNode = new Node();
|
||||
categoryNode.setName(name);
|
||||
categoryNode.setNodeId(id);
|
||||
categoryNode.setParentId(parentNodeRef);
|
||||
return categoryNode;
|
||||
}
|
||||
|
||||
private static Node createNode(final String name)
|
||||
{
|
||||
return createNode(name, CATEGORY_ID, createNodeRefWithId(PARENT_ID));
|
||||
}
|
||||
|
||||
private static Node createNode()
|
||||
{
|
||||
return createNode(CATEGORY_NAME);
|
||||
}
|
||||
|
||||
private static NodeRef createNodeRefWithId(final String id)
|
||||
{
|
||||
return new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, id);
|
||||
@@ -1213,6 +1300,11 @@ public class CategoriesImplTest
|
||||
return Category.builder().name(name).create();
|
||||
}
|
||||
|
||||
private static Category createDefaultCategory()
|
||||
{
|
||||
return createDefaultCategoryWithName(CATEGORY_NAME);
|
||||
}
|
||||
|
||||
private static Category createDefaultCategoryWithName(final String name)
|
||||
{
|
||||
return Category.builder()
|
||||
|
@@ -0,0 +1,451 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.rest.api.impl;
|
||||
|
||||
import static org.alfresco.rest.api.impl.TagsImpl.NOT_A_VALID_TAG;
|
||||
import static org.alfresco.rest.api.impl.TagsImpl.NO_PERMISSION_TO_MANAGE_A_TAG;
|
||||
import static org.alfresco.service.cmr.repository.StoreRef.STORE_REF_WORKSPACE_SPACESSTORE;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.catchThrowable;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.alfresco.query.PagingRequest;
|
||||
import org.alfresco.query.PagingResults;
|
||||
import org.alfresco.rest.api.Nodes;
|
||||
import org.alfresco.rest.api.model.Tag;
|
||||
import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
|
||||
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
|
||||
import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException;
|
||||
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
|
||||
import org.alfresco.rest.framework.resource.parameters.Paging;
|
||||
import org.alfresco.rest.framework.resource.parameters.Parameters;
|
||||
import org.alfresco.rest.framework.resource.parameters.where.InvalidQueryException;
|
||||
import org.alfresco.rest.framework.tools.RecognizedParamsExtractor;
|
||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||
import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.cmr.repository.StoreRef;
|
||||
import org.alfresco.service.cmr.security.AuthorityService;
|
||||
import org.alfresco.service.cmr.tagging.TaggingService;
|
||||
import org.alfresco.util.Pair;
|
||||
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;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class TagsImplTest
|
||||
{
|
||||
private static final String TAG_ID = "tag-node-id";
|
||||
private static final String PARENT_NODE_ID = "tag:tag-root";
|
||||
private static final String TAG_NAME = "tag-dummy-name";
|
||||
private static final NodeRef TAG_NODE_REF = new NodeRef(STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID.concat("-").concat(TAG_NAME));
|
||||
private static final NodeRef TAG_PARENT_NODE_REF = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, PARENT_NODE_ID);
|
||||
|
||||
private final RecognizedParamsExtractor queryExtractor = new RecognizedParamsExtractor() {};
|
||||
|
||||
@Mock
|
||||
private Nodes nodesMock;
|
||||
@Mock
|
||||
private ChildAssociationRef primaryParentMock;
|
||||
@Mock
|
||||
private NodeService nodeServiceMock;
|
||||
@Mock
|
||||
private AuthorityService authorityServiceMock;
|
||||
@Mock
|
||||
private TaggingService taggingServiceMock;
|
||||
@Mock
|
||||
private Parameters parametersMock;
|
||||
@Mock
|
||||
private Paging pagingMock;
|
||||
@Mock
|
||||
private PagingResults<Pair<NodeRef, String>> pagingResultsMock;
|
||||
|
||||
@InjectMocks
|
||||
private TagsImpl objectUnderTest;
|
||||
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
given(authorityServiceMock.hasAdminAuthority()).willReturn(true);
|
||||
given(nodesMock.validateNode(STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID)).willReturn(TAG_NODE_REF);
|
||||
given(taggingServiceMock.getTagName(TAG_NODE_REF)).willReturn(TAG_NAME);
|
||||
given(nodeServiceMock.getPrimaryParent(TAG_NODE_REF)).willReturn(primaryParentMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTags()
|
||||
{
|
||||
given(parametersMock.getPaging()).willReturn(pagingMock);
|
||||
given(taggingServiceMock.getTags(any(StoreRef.class), any(PagingRequest.class), any(), any())).willReturn(pagingResultsMock);
|
||||
given(pagingResultsMock.getTotalResultCount()).willReturn(new Pair<>(Integer.MAX_VALUE, 0));
|
||||
given(pagingResultsMock.getPage()).willReturn(List.of(new Pair<>(TAG_NODE_REF, TAG_NAME)));
|
||||
|
||||
final CollectionWithPagingInfo<Tag> actualTags = objectUnderTest.getTags(STORE_REF_WORKSPACE_SPACESSTORE, parametersMock);
|
||||
|
||||
then(taggingServiceMock).should().getTags(eq(STORE_REF_WORKSPACE_SPACESSTORE), any(PagingRequest.class), isNull(), isNull());
|
||||
then(taggingServiceMock).shouldHaveNoMoreInteractions();
|
||||
final List<Tag> expectedTags = createTagsWithNodeRefs(List.of(TAG_NAME)).stream().peek(tag -> tag.setCount(0)).collect(Collectors.toList());
|
||||
assertEquals(expectedTags, actualTags.getCollection());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTags_verifyIfCountIsZero()
|
||||
{
|
||||
given(parametersMock.getPaging()).willReturn(pagingMock);
|
||||
given(taggingServiceMock.getTags(any(StoreRef.class), any(PagingRequest.class), any(), any())).willReturn(pagingResultsMock);
|
||||
given(pagingResultsMock.getTotalResultCount()).willReturn(new Pair<>(Integer.MAX_VALUE, 0));
|
||||
given(pagingResultsMock.getPage()).willReturn(List.of(new Pair<>(TAG_NODE_REF, TAG_NAME)));
|
||||
given(parametersMock.getInclude()).willReturn(List.of("count"));
|
||||
|
||||
final CollectionWithPagingInfo<Tag> actualTags = objectUnderTest.getTags(STORE_REF_WORKSPACE_SPACESSTORE, parametersMock);
|
||||
|
||||
then(taggingServiceMock).should().findTaggedNodesAndCountByTagName(STORE_REF_WORKSPACE_SPACESSTORE);
|
||||
final List<Tag> expectedTags = createTagsWithNodeRefs(List.of(TAG_NAME)).stream()
|
||||
.peek(tag -> tag.setCount(0))
|
||||
.collect(Collectors.toList());
|
||||
assertEquals(expectedTags, actualTags.getCollection());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTags_withEqualsClauseWhereQuery()
|
||||
{
|
||||
given(parametersMock.getPaging()).willReturn(pagingMock);
|
||||
given(parametersMock.getQuery()).willReturn(queryExtractor.getWhereClause("(tag=expectedName)"));
|
||||
given(taggingServiceMock.getTags(any(StoreRef.class), any(PagingRequest.class), any(), any())).willReturn(pagingResultsMock);
|
||||
given(pagingResultsMock.getTotalResultCount()).willReturn(new Pair<>(Integer.MAX_VALUE, 0));
|
||||
|
||||
//when
|
||||
final CollectionWithPagingInfo<Tag> actualTags = objectUnderTest.getTags(STORE_REF_WORKSPACE_SPACESSTORE, parametersMock);
|
||||
|
||||
then(taggingServiceMock).should().getTags(eq(STORE_REF_WORKSPACE_SPACESSTORE), any(PagingRequest.class), eq(Set.of("expectedname")), isNull());
|
||||
then(taggingServiceMock).shouldHaveNoMoreInteractions();
|
||||
assertThat(actualTags).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTags_withInClauseWhereQuery()
|
||||
{
|
||||
given(parametersMock.getPaging()).willReturn(pagingMock);
|
||||
given(parametersMock.getQuery()).willReturn(queryExtractor.getWhereClause("(tag IN (expectedName1, expectedName2))"));
|
||||
given(taggingServiceMock.getTags(any(StoreRef.class), any(PagingRequest.class), any(), any())).willReturn(pagingResultsMock);
|
||||
given(pagingResultsMock.getTotalResultCount()).willReturn(new Pair<>(Integer.MAX_VALUE, 0));
|
||||
|
||||
//when
|
||||
final CollectionWithPagingInfo<Tag> actualTags = objectUnderTest.getTags(STORE_REF_WORKSPACE_SPACESSTORE, parametersMock);
|
||||
|
||||
then(taggingServiceMock).should().getTags(eq(STORE_REF_WORKSPACE_SPACESSTORE), any(PagingRequest.class), eq(Set.of("expectedname1", "expectedname2")), isNull());
|
||||
then(taggingServiceMock).shouldHaveNoMoreInteractions();
|
||||
assertThat(actualTags).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTags_withMatchesClauseWhereQuery()
|
||||
{
|
||||
given(parametersMock.getPaging()).willReturn(pagingMock);
|
||||
given(parametersMock.getQuery()).willReturn(queryExtractor.getWhereClause("(tag MATCHES ('expectedName*'))"));
|
||||
given(taggingServiceMock.getTags(any(StoreRef.class), any(PagingRequest.class), any(), any())).willReturn(pagingResultsMock);
|
||||
given(pagingResultsMock.getTotalResultCount()).willReturn(new Pair<>(Integer.MAX_VALUE, 0));
|
||||
|
||||
//when
|
||||
final CollectionWithPagingInfo<Tag> actualTags = objectUnderTest.getTags(STORE_REF_WORKSPACE_SPACESSTORE, parametersMock);
|
||||
|
||||
then(taggingServiceMock).should().getTags(eq(STORE_REF_WORKSPACE_SPACESSTORE), any(PagingRequest.class), isNull(), eq(Set.of("expectedname*")));
|
||||
then(taggingServiceMock).shouldHaveNoMoreInteractions();
|
||||
assertThat(actualTags).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTags_withBothInAndEqualsClausesInSingleWhereQuery()
|
||||
{
|
||||
given(parametersMock.getPaging()).willReturn(pagingMock);
|
||||
given(parametersMock.getQuery()).willReturn(queryExtractor.getWhereClause("(tag=expectedName AND tag IN (expectedName1, expectedName2))"));
|
||||
|
||||
//when
|
||||
final Throwable actualException = catchThrowable(() -> objectUnderTest.getTags(STORE_REF_WORKSPACE_SPACESSTORE, parametersMock));
|
||||
|
||||
then(taggingServiceMock).shouldHaveNoInteractions();
|
||||
assertThat(actualException).isInstanceOf(InvalidQueryException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTags_withOtherClauseInWhereQuery()
|
||||
{
|
||||
given(parametersMock.getPaging()).willReturn(pagingMock);
|
||||
given(parametersMock.getQuery()).willReturn(queryExtractor.getWhereClause("(tag BETWEEN ('expectedName', 'expectedName2'))"));
|
||||
|
||||
//when
|
||||
final Throwable actualException = catchThrowable(() -> objectUnderTest.getTags(STORE_REF_WORKSPACE_SPACESSTORE, parametersMock));
|
||||
|
||||
then(taggingServiceMock).shouldHaveNoInteractions();
|
||||
assertThat(actualException).isInstanceOf(InvalidQueryException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTags_withNotEqualsClauseInWhereQuery()
|
||||
{
|
||||
given(parametersMock.getPaging()).willReturn(pagingMock);
|
||||
given(parametersMock.getQuery()).willReturn(queryExtractor.getWhereClause("(NOT tag=expectedName)"));
|
||||
|
||||
//when
|
||||
final Throwable actualException = catchThrowable(() -> objectUnderTest.getTags(STORE_REF_WORKSPACE_SPACESSTORE, parametersMock));
|
||||
|
||||
then(taggingServiceMock).shouldHaveNoInteractions();
|
||||
assertThat(actualException).isInstanceOf(InvalidQueryException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteTagById()
|
||||
{
|
||||
//when
|
||||
given(primaryParentMock.getParentRef()).willReturn(TAG_PARENT_NODE_REF);
|
||||
objectUnderTest.deleteTagById(STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID);
|
||||
|
||||
then(authorityServiceMock).should().hasAdminAuthority();
|
||||
then(authorityServiceMock).shouldHaveNoMoreInteractions();
|
||||
|
||||
then(nodesMock).should().validateNode(STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID);
|
||||
then(nodesMock).shouldHaveNoMoreInteractions();
|
||||
|
||||
then(taggingServiceMock).should().getTagName(TAG_NODE_REF);
|
||||
then(taggingServiceMock).should().deleteTag(STORE_REF_WORKSPACE_SPACESSTORE, TAG_NAME);
|
||||
then(taggingServiceMock).shouldHaveNoMoreInteractions();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteTagById_asNonAdminUser()
|
||||
{
|
||||
given(authorityServiceMock.hasAdminAuthority()).willReturn(false);
|
||||
|
||||
//when
|
||||
assertThrows(PermissionDeniedException.class, () -> objectUnderTest.deleteTagById(STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID));
|
||||
|
||||
then(authorityServiceMock).should().hasAdminAuthority();
|
||||
then(authorityServiceMock).shouldHaveNoMoreInteractions();
|
||||
|
||||
then(nodesMock).shouldHaveNoInteractions();
|
||||
|
||||
then(taggingServiceMock).shouldHaveNoInteractions();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteTagById_nonExistentTag()
|
||||
{
|
||||
//when
|
||||
assertThrows(EntityNotFoundException.class, () -> objectUnderTest.deleteTagById(STORE_REF_WORKSPACE_SPACESSTORE, "dummy-id"));
|
||||
|
||||
then(authorityServiceMock).should().hasAdminAuthority();
|
||||
then(authorityServiceMock).shouldHaveNoMoreInteractions();
|
||||
|
||||
then(nodesMock).should().validateNode(STORE_REF_WORKSPACE_SPACESSTORE, "dummy-id");
|
||||
then(nodesMock).shouldHaveNoMoreInteractions();
|
||||
|
||||
then(taggingServiceMock).shouldHaveNoInteractions();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateTags()
|
||||
{
|
||||
final List<String> tagNames = List.of("tag1", "99gat");
|
||||
final List<Tag> tagsToCreate = createTags(tagNames);
|
||||
given(taggingServiceMock.createTags(any(), any())).willAnswer(invocation -> createTagAndNodeRefPairs(invocation.getArgument(1)));
|
||||
|
||||
//when
|
||||
final List<Tag> actualCreatedTags = objectUnderTest.createTags(tagsToCreate, parametersMock);
|
||||
|
||||
then(authorityServiceMock).should().hasAdminAuthority();
|
||||
then(authorityServiceMock).shouldHaveNoMoreInteractions();
|
||||
then(taggingServiceMock).should().createTags(STORE_REF_WORKSPACE_SPACESSTORE, tagNames);
|
||||
then(taggingServiceMock).shouldHaveNoMoreInteractions();
|
||||
final List<Tag> expectedTags = createTagsWithNodeRefs(tagNames);
|
||||
assertThat(actualCreatedTags)
|
||||
.isNotNull().usingRecursiveComparison()
|
||||
.isEqualTo(expectedTags);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateTags_withoutPermission()
|
||||
{
|
||||
given(authorityServiceMock.hasAdminAuthority()).willReturn(false);
|
||||
|
||||
//when
|
||||
final Throwable actualException = catchThrowable(() -> objectUnderTest.createTags(List.of(createTag(TAG_NAME)), parametersMock));
|
||||
|
||||
then(authorityServiceMock).should().hasAdminAuthority();
|
||||
then(authorityServiceMock).shouldHaveNoMoreInteractions();
|
||||
then(taggingServiceMock).shouldHaveNoInteractions();
|
||||
assertThat(actualException)
|
||||
.isInstanceOf(PermissionDeniedException.class)
|
||||
.hasMessageContaining(NO_PERMISSION_TO_MANAGE_A_TAG);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateTags_passingNullInsteadList()
|
||||
{
|
||||
//when
|
||||
final Throwable actualException = catchThrowable(() -> objectUnderTest.createTags(null, parametersMock));
|
||||
|
||||
then(taggingServiceMock).shouldHaveNoInteractions();
|
||||
assertThat(actualException)
|
||||
.isInstanceOf(InvalidArgumentException.class)
|
||||
.hasMessageContaining(NOT_A_VALID_TAG);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateTags_passingEmptyList()
|
||||
{
|
||||
//when
|
||||
final Throwable actualException = catchThrowable(() -> objectUnderTest.createTags(Collections.emptyList(), parametersMock));
|
||||
|
||||
then(taggingServiceMock).shouldHaveNoInteractions();
|
||||
assertThat(actualException)
|
||||
.isInstanceOf(InvalidArgumentException.class)
|
||||
.hasMessageContaining(NOT_A_VALID_TAG);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateTags_passingListOfNulls()
|
||||
{
|
||||
//when
|
||||
final Throwable actualException = catchThrowable(() -> objectUnderTest.createTags(Collections.singletonList(null), parametersMock));
|
||||
|
||||
then(taggingServiceMock).shouldHaveNoInteractions();
|
||||
assertThat(actualException)
|
||||
.isInstanceOf(InvalidArgumentException.class)
|
||||
.hasMessageContaining(NOT_A_VALID_TAG);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateTags_whileTagAlreadyExists()
|
||||
{
|
||||
given(taggingServiceMock.createTags(any(), any())).willThrow(new DuplicateChildNodeNameException(null, null, TAG_NAME, null));
|
||||
|
||||
//when
|
||||
final Throwable actualException = catchThrowable(() -> objectUnderTest.createTags(List.of(createTag(TAG_NAME)), parametersMock));
|
||||
|
||||
then(taggingServiceMock).should().createTags(STORE_REF_WORKSPACE_SPACESSTORE, List.of(TAG_NAME));
|
||||
then(taggingServiceMock).shouldHaveNoMoreInteractions();
|
||||
assertThat(actualException).isInstanceOf(DuplicateChildNodeNameException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateTags_withRepeatedTagName()
|
||||
{
|
||||
final List<String> tagNames = List.of(TAG_NAME, TAG_NAME);
|
||||
final List<Tag> tagsToCreate = createTags(tagNames);
|
||||
given(taggingServiceMock.createTags(any(), any())).willAnswer(invocation -> createTagAndNodeRefPairs(invocation.getArgument(1)));
|
||||
|
||||
//when
|
||||
final List<Tag> actualCreatedTags = objectUnderTest.createTags(tagsToCreate, parametersMock);
|
||||
|
||||
then(taggingServiceMock).should().createTags(STORE_REF_WORKSPACE_SPACESSTORE, List.of(TAG_NAME));
|
||||
final List<Tag> expectedTags = List.of(createTagWithNodeRef(TAG_NAME));
|
||||
assertThat(actualCreatedTags)
|
||||
.isNotNull()
|
||||
.isEqualTo(expectedTags);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateTags_includingCount()
|
||||
{
|
||||
final List<String> tagNames = List.of("tag1", "99gat");
|
||||
final List<Tag> tagsToCreate = createTags(tagNames);
|
||||
given(taggingServiceMock.createTags(any(), any())).willAnswer(invocation -> createTagAndNodeRefPairs(invocation.getArgument(1)));
|
||||
given(parametersMock.getInclude()).willReturn(List.of("count"));
|
||||
|
||||
//when
|
||||
final List<Tag> actualCreatedTags = objectUnderTest.createTags(tagsToCreate, parametersMock);
|
||||
|
||||
final List<Tag> expectedTags = createTagsWithNodeRefs(tagNames).stream()
|
||||
.peek(tag -> tag.setCount(0))
|
||||
.collect(Collectors.toList());
|
||||
assertThat(actualCreatedTags)
|
||||
.isNotNull()
|
||||
.isEqualTo(expectedTags);
|
||||
}
|
||||
|
||||
@Test(expected = EntityNotFoundException.class)
|
||||
public void testGetTagByIdNotFoundValidation()
|
||||
{
|
||||
given(primaryParentMock.getParentRef()).willReturn(TAG_NODE_REF);
|
||||
objectUnderTest.getTag(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE,TAG_ID);
|
||||
then(nodeServiceMock).shouldHaveNoInteractions();
|
||||
then(nodesMock).should().validateNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID);
|
||||
then(nodesMock).shouldHaveNoMoreInteractions();
|
||||
then(taggingServiceMock).shouldHaveNoInteractions();
|
||||
}
|
||||
|
||||
private static List<Pair<String, NodeRef>> createTagAndNodeRefPairs(final List<String> tagNames)
|
||||
{
|
||||
return tagNames.stream()
|
||||
.map(tagName -> createPair(tagName, new NodeRef(STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID.concat("-").concat(tagName))))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static Pair<String, NodeRef> createPair(final String tagName, final NodeRef nodeRef)
|
||||
{
|
||||
return new Pair<>(tagName, nodeRef);
|
||||
}
|
||||
|
||||
private static List<Tag> createTags(final List<String> tagNames)
|
||||
{
|
||||
return tagNames.stream().map(TagsImplTest::createTag).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static List<Tag> createTagsWithNodeRefs(final List<String> tagNames)
|
||||
{
|
||||
return tagNames.stream().map(TagsImplTest::createTagWithNodeRef).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static Tag createTag(final String tagName)
|
||||
{
|
||||
return Tag.builder()
|
||||
.tag(tagName)
|
||||
.create();
|
||||
}
|
||||
|
||||
private static Tag createTagWithNodeRef(final String tagName)
|
||||
{
|
||||
return Tag.builder()
|
||||
.nodeRef(new NodeRef(STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID.concat("-").concat(tagName)))
|
||||
.tag(tagName)
|
||||
.create();
|
||||
}
|
||||
}
|
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2023 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.tags;
|
||||
|
||||
import static org.alfresco.service.cmr.repository.StoreRef.STORE_REF_WORKSPACE_SPACESSTORE;
|
||||
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.rest.api.Tags;
|
||||
import org.alfresco.rest.api.model.Tag;
|
||||
import org.alfresco.rest.framework.resource.parameters.Parameters;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class TagsEntityResourceTest
|
||||
{
|
||||
private static final String TAG_ID = "tag-dummy-id";
|
||||
private static final String TAG_NAME = "tag-dummy-name";
|
||||
private static final NodeRef TAG_NODE_REF = new NodeRef(STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID);
|
||||
|
||||
@Mock
|
||||
private Parameters parametersMock;
|
||||
@Mock
|
||||
private Tags tagsMock;
|
||||
|
||||
@InjectMocks
|
||||
private TagsEntityResource tagsEntityResource;
|
||||
|
||||
@Test
|
||||
public void testCreate()
|
||||
{
|
||||
final Tag tag = Tag.builder().tag(TAG_NAME).create();
|
||||
final List<Tag> tags = List.of(tag);
|
||||
given(tagsMock.createTags(any(), any())).willCallRealMethod();
|
||||
given(tagsMock.createTags(any(), any(), any())).willReturn(List.of(Tag.builder().nodeRef(TAG_NODE_REF).tag(TAG_NAME).create()));
|
||||
|
||||
//when
|
||||
final List<Tag> actualCreatedTags = tagsEntityResource.create(tags, parametersMock);
|
||||
|
||||
then(tagsMock).should().createTags(STORE_REF_WORKSPACE_SPACESSTORE, tags, parametersMock);
|
||||
final List<Tag> expectedTags = List.of(Tag.builder().nodeRef(TAG_NODE_REF).tag(TAG_NAME).create());
|
||||
assertThat(actualCreatedTags)
|
||||
.isNotEmpty()
|
||||
.isEqualTo(expectedTags);
|
||||
}
|
||||
}
|
@@ -0,0 +1,666 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2023 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.framework.resource.parameters.where;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.catchThrowable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.rest.antlr.WhereClauseParser;
|
||||
import org.alfresco.rest.framework.tools.RecognizedParamsExtractor;
|
||||
import org.alfresco.rest.workflow.api.impl.MapBasedQueryWalker;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests verifying {@link QueryHelper.QueryResolver} functionality based on {@link BasicQueryWalker}.
|
||||
*/
|
||||
public class QueryResolverTest
|
||||
{
|
||||
private final RecognizedParamsExtractor queryExtractor = new RecognizedParamsExtractor() {};
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_equals()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(propName=testValue)");
|
||||
|
||||
//when
|
||||
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||
|
||||
assertThat(property.containsType(WhereClauseParser.EQUALS, false)).isTrue();
|
||||
assertThat(property.containsType(WhereClauseParser.GREATERTHAN, false)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.LESSTHAN, false)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.GREATERTHANOREQUALS, false)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.LESSTHANOREQUALS, false)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.IN, false)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.MATCHES, false)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.BETWEEN, false)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.EXISTS, false)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.EQUALS, true)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.GREATERTHAN, true)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.LESSTHAN, true)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.GREATERTHANOREQUALS, true)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.LESSTHANOREQUALS, true)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.IN, true)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.MATCHES, true)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.BETWEEN, true)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.EXISTS, true)).isFalse();
|
||||
assertThat(property.getExpectedValuesFor(WhereClauseParser.EQUALS, false)).containsOnly("testValue");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_greaterThan()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(propName > testValue)");
|
||||
|
||||
//when
|
||||
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||
|
||||
assertThat(property.containsType(WhereClauseParser.GREATERTHAN, false)).isTrue();
|
||||
assertThat(property.getExpectedValuesFor(WhereClauseParser.GREATERTHAN, false)).containsOnly("testValue");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_greaterThanOrEquals()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(propName >= testValue)");
|
||||
|
||||
//when
|
||||
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||
|
||||
assertThat(property.containsType(WhereClauseParser.GREATERTHANOREQUALS, false)).isTrue();
|
||||
assertThat(property.getExpectedValuesFor(WhereClauseParser.GREATERTHANOREQUALS, false)).containsOnly("testValue");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_lessThan()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(propName < testValue)");
|
||||
|
||||
//when
|
||||
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||
|
||||
assertThat(property.containsType(WhereClauseParser.LESSTHAN, false)).isTrue();
|
||||
assertThat(property.getExpectedValuesFor(WhereClauseParser.LESSTHAN, false)).containsOnly("testValue");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_lessThanOrEquals()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(propName <= testValue)");
|
||||
|
||||
//when
|
||||
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||
|
||||
assertThat(property.containsType(WhereClauseParser.LESSTHANOREQUALS, false)).isTrue();
|
||||
assertThat(property.getExpectedValuesFor(WhereClauseParser.LESSTHANOREQUALS, false)).containsOnly("testValue");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_between()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(propName BETWEEN (testValue, testValue2))");
|
||||
|
||||
//when
|
||||
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||
|
||||
assertThat(property.containsType(WhereClauseParser.BETWEEN, false)).isTrue();
|
||||
assertThat(property.getExpectedValuesFor(WhereClauseParser.BETWEEN, false)).containsOnly("testValue", "testValue2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_in()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(propName IN (testValue, testValue2))");
|
||||
|
||||
//when
|
||||
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||
|
||||
assertThat(property.containsType(WhereClauseParser.IN, false)).isTrue();
|
||||
assertThat(property.getExpectedValuesFor(WhereClauseParser.IN, false)).containsOnly("testValue", "testValue2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_matches()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(propName MATCHES ('*Value'))");
|
||||
|
||||
//when
|
||||
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||
|
||||
assertThat(property.containsType(WhereClauseParser.MATCHES, false)).isTrue();
|
||||
assertThat(property.getExpectedValuesFor(WhereClauseParser.MATCHES, false)).containsOnly("*Value");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_exists()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(EXISTS (propName))");
|
||||
|
||||
//when
|
||||
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||
|
||||
assertThat(property.containsType(WhereClauseParser.EXISTS, false)).isTrue();
|
||||
assertThat(property.getExpectedValuesFor(WhereClauseParser.EXISTS, false)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_notEquals()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(NOT propName=testValue)");
|
||||
|
||||
//when
|
||||
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||
|
||||
assertThat(property.containsType(WhereClauseParser.EQUALS, true)).isTrue();
|
||||
assertThat(property.containsType(WhereClauseParser.EQUALS, false)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.GREATERTHAN, false)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.LESSTHAN, false)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.GREATERTHANOREQUALS, false)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.LESSTHANOREQUALS, false)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.IN, false)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.MATCHES, false)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.BETWEEN, false)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.EXISTS, false)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.GREATERTHAN, true)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.LESSTHAN, true)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.GREATERTHANOREQUALS, true)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.LESSTHANOREQUALS, true)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.IN, true)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.MATCHES, true)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.BETWEEN, true)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.EXISTS, true)).isFalse();
|
||||
assertThat(property.getExpectedValuesFor(WhereClauseParser.EQUALS, true)).containsOnly("testValue");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_notGreaterThan()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(NOT propName > testValue)");
|
||||
|
||||
//when
|
||||
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||
|
||||
assertThat(property.containsType(WhereClauseParser.GREATERTHAN, true)).isTrue();
|
||||
assertThat(property.getExpectedValuesFor(WhereClauseParser.GREATERTHAN, true)).containsOnly("testValue");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_notGreaterThanOrEquals()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(NOT propName >= testValue)");
|
||||
|
||||
//when
|
||||
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||
|
||||
assertThat(property.containsType(WhereClauseParser.GREATERTHANOREQUALS, true)).isTrue();
|
||||
assertThat(property.getExpectedValuesFor(WhereClauseParser.GREATERTHANOREQUALS, true)).containsOnly("testValue");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_notLessThan()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(NOT propName < testValue)");
|
||||
|
||||
//when
|
||||
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||
|
||||
assertThat(property.containsType(WhereClauseParser.LESSTHAN, true)).isTrue();
|
||||
assertThat(property.getExpectedValuesFor(WhereClauseParser.LESSTHAN, true)).containsOnly("testValue");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_notLessThanOrEquals()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(NOT propName <= testValue)");
|
||||
|
||||
//when
|
||||
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||
|
||||
assertThat(property.containsType(WhereClauseParser.LESSTHANOREQUALS, true)).isTrue();
|
||||
assertThat(property.getExpectedValuesFor(WhereClauseParser.LESSTHANOREQUALS, true)).containsOnly("testValue");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_notBetween()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(NOT propName BETWEEN (testValue, testValue2))");
|
||||
|
||||
//when
|
||||
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||
|
||||
assertThat(property.containsType(WhereClauseParser.BETWEEN, true)).isTrue();
|
||||
assertThat(property.getExpectedValuesFor(WhereClauseParser.BETWEEN, true)).containsOnly("testValue", "testValue2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_notIn()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(NOT propName IN (testValue, testValue2))");
|
||||
|
||||
//when
|
||||
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||
|
||||
assertThat(property.containsType(WhereClauseParser.IN, true)).isTrue();
|
||||
assertThat(property.getExpectedValuesFor(WhereClauseParser.IN, true)).containsOnly("testValue", "testValue2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_notMatches()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(NOT propName MATCHES ('*Value'))");
|
||||
|
||||
//when
|
||||
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||
|
||||
assertThat(property.containsType(WhereClauseParser.MATCHES, true)).isTrue();
|
||||
assertThat(property.getExpectedValuesFor(WhereClauseParser.MATCHES, true)).containsOnly("*Value");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_notExists()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(NOT EXISTS (propName))");
|
||||
|
||||
//when
|
||||
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||
|
||||
assertThat(property.containsType(WhereClauseParser.EXISTS, true)).isTrue();
|
||||
assertThat(property.getExpectedValuesFor(WhereClauseParser.EXISTS, true)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_propertyNotExpected()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(propName=testValue AND differentName>18)");
|
||||
|
||||
//when
|
||||
final Throwable actualException = catchThrowable(() -> QueryHelper.resolve(query).getProperty("differentName"));
|
||||
|
||||
assertThat(actualException).isInstanceOf(InvalidQueryException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_propertyNotExpectedUsingLenientApproach()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(propName=testValue AND differentName>18)");
|
||||
|
||||
//when
|
||||
final WhereProperty property = QueryHelper.resolve(query).leniently().getProperty("differentName");
|
||||
|
||||
assertThat(property.containsType(WhereClauseParser.EQUALS, false)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.EQUALS, true)).isFalse();
|
||||
assertThat(property.getExpectedValuesFor(WhereClauseParser.EQUALS, false)).isNull();
|
||||
assertThat(property.getExpectedValuesFor(WhereClauseParser.EQUALS, true)).isNull();
|
||||
assertThat(property.containsType(WhereClauseParser.GREATERTHAN, false)).isTrue();
|
||||
assertThat(property.getExpectedValuesFor(WhereClauseParser.GREATERTHAN, false)).containsOnly("18");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_propertyNotPresentUsingLenientApproach()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(propName=testValue)");
|
||||
|
||||
//when
|
||||
final Throwable actualException = catchThrowable(() -> QueryHelper.resolve(query).getProperty("differentName"));
|
||||
|
||||
assertThat(actualException).isInstanceOf(InvalidQueryException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_slashInPropertyName()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(EXISTS (prop/name/with/slashes))");
|
||||
|
||||
//when
|
||||
final WhereProperty property = QueryHelper.resolve(query).getProperty("prop/name/with/slashes");
|
||||
|
||||
assertThat(property.containsType(WhereClauseParser.EXISTS, false)).isTrue();
|
||||
assertThat(property.getExpectedValuesFor(WhereClauseParser.EXISTS, false)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_propertyBetweenDates()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(propName BETWEEN ('2012-01-01', '2012-12-31'))");
|
||||
|
||||
//when
|
||||
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||
|
||||
assertThat(property.containsType(WhereClauseParser.BETWEEN, false)).isTrue();
|
||||
assertThat(property.getExpectedValuesFor(WhereClauseParser.BETWEEN, false)).containsOnly("2012-01-01", "2012-12-31");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_singlePropertyGreaterThanOrEqualsAndLessThan()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(propName >= 18 AND propName < 65)");
|
||||
|
||||
//when
|
||||
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||
|
||||
assertThat(property.containsType(WhereClauseParser.GREATERTHANOREQUALS, false)).isTrue();
|
||||
assertThat(property.getExpectedValuesFor(WhereClauseParser.GREATERTHANOREQUALS, false)).containsOnly("18");
|
||||
assertThat(property.containsType(WhereClauseParser.LESSTHAN, false)).isTrue();
|
||||
assertThat(property.getExpectedValuesFor(WhereClauseParser.LESSTHAN, false)).containsOnly("65");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_onePropertyGreaterThanAndSecondPropertyNotMatches()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(propName1 > 20 AND NOT propName2 MATCHES ('external*'))");
|
||||
|
||||
//when
|
||||
final List<WhereProperty> property = QueryHelper.resolve(query).getProperties("propName1", "propName2");
|
||||
|
||||
assertThat(property.get(0).containsType(WhereClauseParser.GREATERTHAN, false)).isTrue();
|
||||
assertThat(property.get(0).getExpectedValuesFor(WhereClauseParser.GREATERTHAN, false)).containsOnly("20");
|
||||
assertThat(property.get(1).containsType(WhereClauseParser.MATCHES, true)).isTrue();
|
||||
assertThat(property.get(1).getExpectedValuesFor(WhereClauseParser.MATCHES, true)).containsOnly("external*");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_negationsForbidden()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(NOT propName=testValue)");
|
||||
|
||||
//when
|
||||
final Throwable actualException = catchThrowable(() -> QueryHelper.resolve(query).withoutNegations().getProperty("propName"));
|
||||
|
||||
assertThat(actualException).isInstanceOf(InvalidQueryException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_withoutNegations()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(propName=testValue)");
|
||||
|
||||
//when
|
||||
final WhereProperty actualProperty = QueryHelper.resolve(query).withoutNegations().getProperty("propName");
|
||||
|
||||
assertThat(actualProperty.containsType(WhereClauseParser.EQUALS, false)).isTrue();
|
||||
assertThat(actualProperty.containsType(WhereClauseParser.EQUALS, true)).isFalse();
|
||||
assertThat(actualProperty.getExpectedValuesFor(WhereClauseParser.EQUALS).onlyNegated()).isNull();
|
||||
assertThat(actualProperty.getExpectedValuesFor(WhereClauseParser.EQUALS).skipNegated()).containsOnly("testValue");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_orNotAllowed()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(propName=testValue OR propName BETWEEN (testValue2, testValue3))");
|
||||
|
||||
//when
|
||||
final Throwable actualException = catchThrowable(() -> QueryHelper.resolve(query).getProperty("propName"));
|
||||
|
||||
assertThat(actualException).isInstanceOf(InvalidQueryException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_orAllowedInFavorOfAnd()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(propName=testValue OR propName=testValue2)");
|
||||
|
||||
//when
|
||||
final WhereProperty property = QueryHelper
|
||||
.resolve(query)
|
||||
.usingOrOperator()
|
||||
.getProperty("propName");
|
||||
|
||||
assertThat(property.containsType(WhereClauseParser.EQUALS, false)).isTrue();
|
||||
assertThat(property.getExpectedValuesFor(WhereClauseParser.EQUALS, false)).containsOnly("testValue", "testValue2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_usingCustomQueryWalker()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(propName=testValue)");
|
||||
|
||||
//when
|
||||
final Collection<String> propertyValues = QueryHelper
|
||||
.resolve(query)
|
||||
.usingWalker(new MapBasedQueryWalker(Set.of("propName"), null))
|
||||
.getProperty("propName", WhereClauseParser.EQUALS, false);
|
||||
|
||||
assertThat(propertyValues).containsOnly("testValue");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_usingCustomBasicQueryWalkerExtension()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(propName=testValue OR propName=testValue2)");
|
||||
|
||||
//when
|
||||
final WhereProperty property = QueryHelper
|
||||
.resolve(query)
|
||||
.usingWalker(new BasicQueryWalker("propName")
|
||||
{
|
||||
@Override
|
||||
public void or() {}
|
||||
@Override
|
||||
public void and() {throw UNSUPPORTED;}
|
||||
})
|
||||
.withoutNegations()
|
||||
.getProperty("propName");
|
||||
|
||||
assertThat(property.containsType(WhereClauseParser.EQUALS, false)).isTrue();
|
||||
assertThat(property.getExpectedValuesFor(WhereClauseParser.EQUALS, false)).containsOnly("testValue", "testValue2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_equalsAndInNotAllowedTogether()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(propName=testValue AND propName IN (testValue2, testValue3))");
|
||||
|
||||
//when
|
||||
final Throwable actualException = catchThrowable(() -> QueryHelper.resolve(query).getProperty("propName"));
|
||||
|
||||
assertThat(actualException).isInstanceOf(InvalidQueryException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_equalsOrInAllowedTogether()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(propName=testValue OR propName IN (testValue2, testValue3))");
|
||||
|
||||
//when
|
||||
final WhereProperty whereProperty = QueryHelper.resolve(query).usingOrOperator().getProperty("propName");
|
||||
|
||||
assertThat(whereProperty).isNotNull();
|
||||
assertThat(whereProperty.getExpectedValuesForAllOf(WhereClauseParser.EQUALS, WhereClauseParser.IN).skipNegated())
|
||||
.isEqualTo(Map.of(WhereClauseParser.EQUALS, Set.of("testValue"), WhereClauseParser.IN, Set.of("testValue2", "testValue3")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_equalsAndInAllowedTogetherWithDifferentProperties()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(propName=testValue AND propName2 IN (testValue2, testValue3))");
|
||||
|
||||
//when
|
||||
final List<WhereProperty> properties = QueryHelper
|
||||
.resolve(query)
|
||||
.getProperties("propName", "propName2");
|
||||
|
||||
assertThat(properties.get(0).containsType(WhereClauseParser.EQUALS, false)).isTrue();
|
||||
assertThat(properties.get(0).containsType(WhereClauseParser.IN, false)).isFalse();
|
||||
assertThat(properties.get(0).getExpectedValuesForAnyOf(WhereClauseParser.EQUALS, WhereClauseParser.IN).skipNegated().get(WhereClauseParser.EQUALS)).containsOnly("testValue");
|
||||
assertThat(properties.get(0).getExpectedValuesForAnyOf(WhereClauseParser.EQUALS, WhereClauseParser.IN).skipNegated().containsKey(WhereClauseParser.IN)).isFalse();
|
||||
assertThat(properties.get(1).containsType(WhereClauseParser.EQUALS, false)).isFalse();
|
||||
assertThat(properties.get(1).containsType(WhereClauseParser.IN, false)).isTrue();
|
||||
assertThat(properties.get(1).getExpectedValuesForAnyOf(WhereClauseParser.EQUALS, WhereClauseParser.IN).skipNegated().containsKey(WhereClauseParser.EQUALS)).isFalse();
|
||||
assertThat(properties.get(1).getExpectedValuesForAnyOf(WhereClauseParser.EQUALS, WhereClauseParser.IN).skipNegated().get(WhereClauseParser.IN)).containsOnly("testValue2", "testValue3");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_equalsAndInAllowedAlternately_equals()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(propName=testValue)");
|
||||
|
||||
//when
|
||||
final WhereProperty property = QueryHelper
|
||||
.resolve(query)
|
||||
.getProperty("propName");
|
||||
|
||||
assertThat(property.containsType(WhereClauseParser.EQUALS, false)).isTrue();
|
||||
assertThat(property.containsType(WhereClauseParser.IN, false)).isFalse();
|
||||
assertThat(property.getExpectedValuesForAnyOf(WhereClauseParser.EQUALS, WhereClauseParser.IN).skipNegated().get(WhereClauseParser.EQUALS)).containsOnly("testValue");
|
||||
assertThat(property.getExpectedValuesForAnyOf(WhereClauseParser.EQUALS, WhereClauseParser.IN).skipNegated().containsKey(WhereClauseParser.IN)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_equalsAndInAllowedAlternately_in()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(propName IN (testValue))");
|
||||
|
||||
//when
|
||||
final WhereProperty property = QueryHelper
|
||||
.resolve(query)
|
||||
.getProperty("propName");
|
||||
|
||||
assertThat(property.containsType(WhereClauseParser.EQUALS, false)).isFalse();
|
||||
assertThat(property.containsType(WhereClauseParser.IN, false)).isTrue();
|
||||
assertThat(property.getExpectedValuesForAnyOf(WhereClauseParser.EQUALS, WhereClauseParser.IN).skipNegated().containsKey(WhereClauseParser.EQUALS)).isFalse();
|
||||
assertThat(property.getExpectedValuesForAnyOf(WhereClauseParser.EQUALS, WhereClauseParser.IN).skipNegated().get(WhereClauseParser.IN)).containsOnly("testValue");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_missingEqualsClauseType()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(propName MATCHES (testValue))");
|
||||
|
||||
//when
|
||||
final WhereProperty property = QueryHelper
|
||||
.resolve(query)
|
||||
.getProperty("propName");
|
||||
|
||||
assertThatExceptionOfType(InvalidQueryException.class)
|
||||
.isThrownBy(() -> property.getExpectedValuesForAllOf(WhereClauseParser.EQUALS, WhereClauseParser.MATCHES));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_ignoreUnexpectedClauseType()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(propName=testValue AND propName MATCHES (testValue))");
|
||||
|
||||
//when
|
||||
final WhereProperty property = QueryHelper
|
||||
.resolve(query)
|
||||
.getProperty("propName");
|
||||
|
||||
assertThat(property.getExpectedValuesForAllOf(WhereClauseParser.EQUALS).skipNegated(WhereClauseParser.EQUALS)).containsOnly("testValue");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_complexAndQuery()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(a=v1 AND b>18 AND b<=65 AND NOT c BETWEEN ('2012-01-01','2012-12-31') AND d IN (v1, v2) AND e MATCHES ('*@mail.com') AND EXISTS (f/g))");
|
||||
|
||||
//when
|
||||
final List<WhereProperty> properties = QueryHelper
|
||||
.resolve(query)
|
||||
.getProperties("a", "b", "c", "d", "e", "f/g");
|
||||
|
||||
assertThat(properties).hasSize(6);
|
||||
assertThat(properties.get(0).getExpectedValuesFor(WhereProperty.ClauseType.EQUALS)).containsOnly("v1");
|
||||
assertThat(properties.get(1).containsAllTypes(WhereProperty.ClauseType.GREATER_THAN, WhereProperty.ClauseType.LESS_THAN_OR_EQUALS)).isTrue();
|
||||
assertThat(properties.get(1).getExpectedValuesFor(WhereProperty.ClauseType.GREATER_THAN)).containsOnly("18");
|
||||
assertThat(properties.get(1).getExpectedValuesFor(WhereProperty.ClauseType.LESS_THAN_OR_EQUALS)).containsOnly("65");
|
||||
assertThat(properties.get(2).getExpectedValuesFor(WhereProperty.ClauseType.NOT_BETWEEN)).containsOnly("2012-01-01", "2012-12-31");
|
||||
assertThat(properties.get(3).getExpectedValuesFor(WhereProperty.ClauseType.IN)).containsOnly("v1", "v2");
|
||||
assertThat(properties.get(4).getExpectedValuesFor(WhereProperty.ClauseType.MATCHES)).containsOnly("*@mail.com");
|
||||
assertThat(properties.get(5).containsType(WhereProperty.ClauseType.EXISTS)).isTrue();
|
||||
assertThat(properties.get(5).getExpectedValuesFor(WhereProperty.ClauseType.EXISTS)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_complexOrQuery()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(a=v1 OR b>18 OR b<=65 OR NOT c BETWEEN ('2012-01-01','2012-12-31') OR d IN (v1, v2) OR e MATCHES ('*@mail.com') OR EXISTS (f/g))");
|
||||
|
||||
//when
|
||||
final List<WhereProperty> properties = QueryHelper
|
||||
.resolve(query)
|
||||
.usingOrOperator()
|
||||
.getProperties("a", "b", "c", "d", "e", "f/g");
|
||||
|
||||
assertThat(properties).hasSize(6);
|
||||
assertThat(properties.get(0).getExpectedValuesFor(WhereProperty.ClauseType.EQUALS)).containsOnly("v1");
|
||||
assertThat(properties.get(1).containsAllTypes(WhereProperty.ClauseType.GREATER_THAN, WhereProperty.ClauseType.LESS_THAN_OR_EQUALS)).isTrue();
|
||||
assertThat(properties.get(1).getExpectedValuesFor(WhereProperty.ClauseType.GREATER_THAN)).containsOnly("18");
|
||||
assertThat(properties.get(1).getExpectedValuesFor(WhereProperty.ClauseType.LESS_THAN_OR_EQUALS)).containsOnly("65");
|
||||
assertThat(properties.get(2).getExpectedValuesFor(WhereProperty.ClauseType.NOT_BETWEEN)).containsOnly("2012-01-01", "2012-12-31");
|
||||
assertThat(properties.get(3).getExpectedValuesFor(WhereProperty.ClauseType.IN)).containsOnly("v1", "v2");
|
||||
assertThat(properties.get(4).getExpectedValuesFor(WhereProperty.ClauseType.MATCHES)).containsOnly("*@mail.com");
|
||||
assertThat(properties.get(5).containsType(WhereProperty.ClauseType.EXISTS)).isTrue();
|
||||
assertThat(properties.get(5).getExpectedValuesFor(WhereProperty.ClauseType.EXISTS)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_clauseTypeOptional()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(propName MATCHES (testValue))");
|
||||
|
||||
//when
|
||||
final WhereProperty property = QueryHelper
|
||||
.resolve(query)
|
||||
.getProperty("propName");
|
||||
|
||||
assertThat(property.getExpectedValuesForAnyOf(WhereClauseParser.EQUALS, WhereClauseParser.MATCHES).skipNegated(WhereClauseParser.MATCHES)).containsOnly("testValue");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_optionalClauseTypesNotPresent()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(propName=testValue AND propName MATCHES (testValue))");
|
||||
|
||||
//when
|
||||
final WhereProperty property = QueryHelper
|
||||
.resolve(query)
|
||||
.getProperty("propName");
|
||||
|
||||
assertThatExceptionOfType(InvalidQueryException.class)
|
||||
.isThrownBy(() -> property.getExpectedValuesForAnyOf(WhereClauseParser.IN));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveQuery_matchesOrMatchesAllowed()
|
||||
{
|
||||
final Query query = queryExtractor.getWhereClause("(propName MATCHES ('test*') OR propName MATCHES ('*value*'))");
|
||||
|
||||
//when
|
||||
final Collection<String> expectedValues = QueryHelper
|
||||
.resolve(query)
|
||||
.usingOrOperator()
|
||||
.getProperty("propName")
|
||||
.getExpectedValuesFor(WhereClauseParser.MATCHES)
|
||||
.skipNegated();
|
||||
|
||||
assertThat(expectedValues).containsOnly("test*", "*value*");
|
||||
}
|
||||
}
|
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo</artifactId>
|
||||
<version>20.78</version>
|
||||
<version>20.112</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
@@ -119,6 +119,10 @@
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.jayway.jsonpath</groupId>
|
||||
<artifactId>json-path</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.ibm.icu</groupId>
|
||||
<artifactId>icu4j</artifactId>
|
||||
@@ -387,14 +391,11 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-core</artifactId>
|
||||
<version>${dependency.spring-security.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-expression</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
<artifactId>spring-security-crypto</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-oauth2-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.quartz-scheduler</groupId>
|
||||
@@ -557,17 +558,6 @@
|
||||
</dependency>
|
||||
|
||||
<!-- Keycloak dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-authz-client</artifactId>
|
||||
<version>${dependency.keycloak.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>*</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
|
@@ -1,28 +1,28 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* 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%
|
||||
*/
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* 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.download;
|
||||
|
||||
import java.io.File;
|
||||
@@ -31,6 +31,8 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.util.Date;
|
||||
import java.util.Deque;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
@@ -85,6 +87,8 @@ public class ZipDownloadExporter extends BaseExporter
|
||||
private String currentName;
|
||||
|
||||
private OutputStream outputStream;
|
||||
private Date zipTimestampCreated;
|
||||
private Date zipTimestampModified;
|
||||
|
||||
/**
|
||||
* Construct
|
||||
@@ -137,6 +141,8 @@ public class ZipDownloadExporter extends BaseExporter
|
||||
public void startNode(NodeRef nodeRef)
|
||||
{
|
||||
this.currentName = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_NAME);
|
||||
this.zipTimestampCreated = (Date)nodeService.getProperty(nodeRef, ContentModel.PROP_CREATED);
|
||||
this.zipTimestampModified = (Date)nodeService.getProperty(nodeRef, ContentModel.PROP_MODIFIED);
|
||||
path.push(new Pair<String, NodeRef>(currentName, nodeRef));
|
||||
if (dictionaryService.isSubClass(nodeService.getType(nodeRef), ContentModel.TYPE_FOLDER))
|
||||
{
|
||||
@@ -144,6 +150,9 @@ public class ZipDownloadExporter extends BaseExporter
|
||||
ZipArchiveEntry archiveEntry = new ZipArchiveEntry(path);
|
||||
try
|
||||
{
|
||||
archiveEntry.setTime(zipTimestampCreated.getTime());
|
||||
archiveEntry.setCreationTime(FileTime.fromMillis(zipTimestampCreated.getTime()));
|
||||
archiveEntry.setLastModifiedTime(FileTime.fromMillis(zipTimestampModified.getTime()));
|
||||
zipStream.putArchiveEntry(archiveEntry);
|
||||
zipStream.closeArchiveEntry();
|
||||
}
|
||||
@@ -167,6 +176,9 @@ public class ZipDownloadExporter extends BaseExporter
|
||||
{
|
||||
// ALF-2016
|
||||
ZipArchiveEntry zipEntry=new ZipArchiveEntry(getPath());
|
||||
zipEntry.setTime(zipTimestampCreated.getTime());
|
||||
zipEntry.setCreationTime(FileTime.fromMillis(zipTimestampCreated.getTime()));
|
||||
zipEntry.setLastModifiedTime(FileTime.fromMillis(zipTimestampModified.getTime()));
|
||||
zipStream.putArchiveEntry(zipEntry);
|
||||
|
||||
// copy export stream to zip
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,119 +0,0 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2018 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.security.authentication.identityservice;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.keycloak.adapters.HttpClientBuilder;
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.Configuration;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
*
|
||||
* Creates an instance of {@link AuthzClient}. <br>
|
||||
* The creation of {@link AuthzClient} requires connection to a Keycloak server, disable this factory if Keycloak cannot be reached. <br>
|
||||
* This factory can return a null if it is disabled.
|
||||
*
|
||||
*/
|
||||
public class AuthenticatorAuthzClientFactoryBean implements FactoryBean<AuthzClient>
|
||||
{
|
||||
|
||||
private static Log logger = LogFactory.getLog(AuthenticatorAuthzClientFactoryBean.class);
|
||||
private IdentityServiceConfig identityServiceConfig;
|
||||
private boolean enabled;
|
||||
|
||||
public void setEnabled(boolean enabled)
|
||||
{
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public void setIdentityServiceConfig(IdentityServiceConfig identityServiceConfig)
|
||||
{
|
||||
this.identityServiceConfig = identityServiceConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthzClient getObject() throws Exception
|
||||
{
|
||||
// The creation of the client can be disabled for testing or when the username/password authentication is not required,
|
||||
// for instance when Keycloak is configured for 'bearer only' authentication or Direct Access Grants are disabled.
|
||||
if (!enabled)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Build default http client using the keycloak client builder.
|
||||
int conTimeout = identityServiceConfig.getClientConnectionTimeout();
|
||||
int socTimeout = identityServiceConfig.getClientSocketTimeout();
|
||||
HttpClient client = new HttpClientBuilder()
|
||||
.establishConnectionTimeout(conTimeout, TimeUnit.MILLISECONDS)
|
||||
.socketTimeout(socTimeout, TimeUnit.MILLISECONDS)
|
||||
.build(this.identityServiceConfig);
|
||||
|
||||
// Add secret to credentials if needed.
|
||||
// AuthzClient configuration needs credentials with a secret even if the client in Keycloak is configured as public.
|
||||
Map<String, Object> credentials = identityServiceConfig.getCredentials();
|
||||
if (credentials == null || !credentials.containsKey("secret"))
|
||||
{
|
||||
credentials = credentials == null ? new HashMap<>() : new HashMap<>(credentials);
|
||||
credentials.put("secret", "");
|
||||
}
|
||||
|
||||
// Create default AuthzClient for authenticating users against keycloak
|
||||
String authServerUrl = identityServiceConfig.getAuthServerUrl();
|
||||
String realm = identityServiceConfig.getRealm();
|
||||
String resource = identityServiceConfig.getResource();
|
||||
Configuration authzConfig = new Configuration(authServerUrl, realm, resource, credentials, client);
|
||||
AuthzClient authzClient = AuthzClient.create(authzConfig);
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug(" Created Keycloak AuthzClient");
|
||||
logger.debug(" Keycloak AuthzClient server URL: " + authzClient.getConfiguration().getAuthServerUrl());
|
||||
logger.debug(" Keycloak AuthzClient realm: " + authzClient.getConfiguration().getRealm());
|
||||
logger.debug(" Keycloak AuthzClient resource: " + authzClient.getConfiguration().getResource());
|
||||
}
|
||||
return authzClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType()
|
||||
{
|
||||
return AuthenticatorAuthzClientFactoryBean.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSingleton()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -25,38 +25,35 @@
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.identityservice;
|
||||
|
||||
import java.net.ConnectException;
|
||||
|
||||
import org.alfresco.error.ExceptionStackUtil;
|
||||
import org.alfresco.repo.management.subsystems.ActivateableBean;
|
||||
import org.alfresco.repo.security.authentication.AbstractAuthenticationComponent;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationException;
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceAuthenticationComponent.OAuth2Client.CredentialsVerificationException;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.util.HttpResponseException;
|
||||
|
||||
/**
|
||||
*
|
||||
* Authenticates a user against Keycloak.
|
||||
* Keycloak's {@link AuthzClient} is used to retrieve an access token for the provided user credentials,
|
||||
* user is set as the current user if the user's access token can be obtained.
|
||||
* Authenticates a user against Identity Service (Keycloak).
|
||||
* {@link OAuth2Client} is used to verify provided user credentials. User is set as the current user if the user
|
||||
* credentials are valid.
|
||||
* <br>
|
||||
* The AuthzClient can be null in which case this authenticator will just fall through to the next one in the chain.
|
||||
* The {@link IdentityServiceAuthenticationComponent#oAuth2Client} can be null in which case this authenticator will
|
||||
* just fall through to the next one in the chain.
|
||||
*
|
||||
*/
|
||||
public class IdentityServiceAuthenticationComponent extends AbstractAuthenticationComponent implements ActivateableBean
|
||||
{
|
||||
private final Log logger = LogFactory.getLog(IdentityServiceAuthenticationComponent.class);
|
||||
/** client used to authenticate user credentials against Keycloak **/
|
||||
private AuthzClient authzClient;
|
||||
private final Log LOGGER = LogFactory.getLog(IdentityServiceAuthenticationComponent.class);
|
||||
/** client used to authenticate user credentials against Authorization Server **/
|
||||
private OAuth2Client oAuth2Client;
|
||||
/** enabled flag for the identity service subsystem**/
|
||||
private boolean active;
|
||||
private boolean allowGuestLogin;
|
||||
|
||||
public void setAuthenticatorAuthzClient(AuthzClient authenticatorAuthzClient)
|
||||
public void setOAuth2Client(OAuth2Client oAuth2Client)
|
||||
{
|
||||
this.authzClient = authenticatorAuthzClient;
|
||||
this.oAuth2Client = oAuth2Client;
|
||||
}
|
||||
|
||||
public void setAllowGuestLogin(boolean allowGuestLogin)
|
||||
@@ -67,49 +64,31 @@ public class IdentityServiceAuthenticationComponent extends AbstractAuthenticati
|
||||
public void authenticateImpl(String userName, char[] password) throws AuthenticationException
|
||||
{
|
||||
|
||||
if (authzClient == null)
|
||||
if (oAuth2Client == null)
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
if (LOGGER.isDebugEnabled())
|
||||
{
|
||||
logger.debug("AuthzClient was not set, possibly due to the 'identity-service.authentication.enable-username-password-authentication=false' property. ");
|
||||
LOGGER.debug("OAuth2Client was not set, possibly due to the 'identity-service.authentication.enable-username-password-authentication=false' property.");
|
||||
}
|
||||
|
||||
throw new AuthenticationException("User not authenticated because AuthzClient was not set.");
|
||||
throw new AuthenticationException("User not authenticated because OAuth2Client was not set.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Attempt to get an access token using the user credentials
|
||||
authzClient.obtainAccessToken(userName, new String(password));
|
||||
// Attempt to verify user credentials
|
||||
oAuth2Client.verifyCredentials(userName, new String(password));
|
||||
|
||||
// Successfully obtained access token so treat as authenticated user
|
||||
// Verification was successful so treat as authenticated user
|
||||
setCurrentUser(userName);
|
||||
}
|
||||
catch (HttpResponseException e)
|
||||
catch (CredentialsVerificationException e)
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Failed to authenticate user against Keycloak. Status: " + e.getStatusCode() + " Reason: "+ e.getReasonPhrase());
|
||||
}
|
||||
|
||||
throw new AuthenticationException("Failed to authenticate user against Keycloak.", e);
|
||||
throw new AuthenticationException("Failed to verify user credentials against the OAuth2 Authorization Server.", e);
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
Throwable cause = ExceptionStackUtil.getCause(e, ConnectException.class);
|
||||
if (cause != null)
|
||||
{
|
||||
if (logger.isWarnEnabled())
|
||||
{
|
||||
logger.warn("Couldn't connect to Keycloak server to authenticate user. Reason: " + cause.getMessage());
|
||||
}
|
||||
throw new AuthenticationException("Couldn't connect to Keycloak server to authenticate user.", cause);
|
||||
}
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Error occurred while authenticating user against Keycloak. Reason: " + e.getMessage());
|
||||
}
|
||||
throw new AuthenticationException("Error occurred while authenticating user against Keycloak.", e);
|
||||
throw new AuthenticationException("Failed to verify user credentials.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,4 +108,32 @@ public class IdentityServiceAuthenticationComponent extends AbstractAuthenticati
|
||||
{
|
||||
return allowGuestLogin;
|
||||
}
|
||||
|
||||
/**
|
||||
* An abstraction for acting as an OAuth2 Client
|
||||
*/
|
||||
interface OAuth2Client
|
||||
{
|
||||
/**
|
||||
* The OAuth2's Client role is only used to verify the user credentials (Resource Owner Password
|
||||
* Credentials Flow) this is why there is an explicit method for verifying these.
|
||||
* @param userName user's name
|
||||
* @param password user's password
|
||||
* @throws CredentialsVerificationException when the verification failed or couldn't be performed
|
||||
*/
|
||||
void verifyCredentials(String userName, String password);
|
||||
|
||||
class CredentialsVerificationException extends RuntimeException
|
||||
{
|
||||
CredentialsVerificationException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
CredentialsVerificationException(String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -26,6 +26,7 @@
|
||||
package org.alfresco.repo.security.authentication.identityservice;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.TreeMap;
|
||||
|
||||
@@ -33,6 +34,7 @@ import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* Class to hold configuration for the Identity Service.
|
||||
@@ -41,8 +43,9 @@ import org.springframework.beans.factory.InitializingBean;
|
||||
*/
|
||||
public class IdentityServiceConfig extends AdapterConfig implements InitializingBean
|
||||
{
|
||||
private static Log logger = LogFactory.getLog(IdentityServiceConfig.class);
|
||||
|
||||
private static final Log LOGGER = LogFactory.getLog(IdentityServiceConfig.class);
|
||||
private static final String REALMS = "realms";
|
||||
private static final String SECRET = "secret";
|
||||
private static final String CREDENTIALS_SECRET = "identity-service.credentials.secret";
|
||||
private static final String CREDENTIALS_PROVIDER = "identity-service.credentials.provider";
|
||||
|
||||
@@ -95,13 +98,13 @@ public class IdentityServiceConfig extends AdapterConfig implements Initializing
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception
|
||||
{
|
||||
// programatically build the more complex objects i.e. credentials
|
||||
// programmatically build the more complex objects i.e. credentials
|
||||
Map<String, Object> credentials = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
|
||||
String secret = this.globalProperties.getProperty(CREDENTIALS_SECRET);
|
||||
if (secret != null && !secret.isEmpty())
|
||||
{
|
||||
credentials.put("secret", secret);
|
||||
credentials.put(SECRET, secret);
|
||||
}
|
||||
|
||||
String provider = this.globalProperties.getProperty(CREDENTIALS_PROVIDER);
|
||||
@@ -116,10 +119,27 @@ public class IdentityServiceConfig extends AdapterConfig implements Initializing
|
||||
{
|
||||
this.setCredentials(credentials);
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
if (LOGGER.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Created credentials map from config: " + credentials);
|
||||
LOGGER.debug("Created credentials map from config: " + credentials);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String getIssuerUrl()
|
||||
{
|
||||
return UriComponentsBuilder.fromUriString(getAuthServerUrl())
|
||||
.pathSegment(REALMS, getRealm())
|
||||
.build()
|
||||
.toString();
|
||||
}
|
||||
|
||||
public String getClientSecret()
|
||||
{
|
||||
return Optional.ofNullable(getCredentials())
|
||||
.map(c -> c.get(SECRET))
|
||||
.filter(String.class::isInstance)
|
||||
.map(String.class::cast)
|
||||
.orElse("");
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,270 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2023 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.security.authentication.identityservice;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceAuthenticationComponent.OAuth2Client;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.http.converter.FormHttpMessageConverter;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizationContext;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder.PasswordGrantBuilder;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
|
||||
import org.springframework.security.oauth2.client.endpoint.DefaultPasswordTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrations;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
*
|
||||
* Creates an instance of {@link OAuth2Client}. <br>
|
||||
* The creation of {@link OAuth2Client} requires connection to the Identity Service (Keycloak), disable this factory if
|
||||
* the server cannot be reached. <br>
|
||||
* This factory can return a null if it is disabled.
|
||||
*
|
||||
*/
|
||||
public class OAuth2ClientFactoryBean implements FactoryBean<OAuth2Client>
|
||||
{
|
||||
|
||||
private static final Log LOGGER = LogFactory.getLog(OAuth2ClientFactoryBean.class);
|
||||
private IdentityServiceConfig identityServiceConfig;
|
||||
private boolean enabled;
|
||||
|
||||
public void setEnabled(boolean enabled)
|
||||
{
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public void setIdentityServiceConfig(IdentityServiceConfig identityServiceConfig)
|
||||
{
|
||||
this.identityServiceConfig = identityServiceConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2Client getObject() throws Exception
|
||||
{
|
||||
// The creation of the client can be disabled for testing or when the username/password authentication is not required,
|
||||
// for instance when Keycloak is configured for 'bearer only' authentication or Direct Access Grants are disabled.
|
||||
if (!enabled)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// The OAuth2AuthorizedClientManager isn't created upfront to make the code resilient to Identity Service being down.
|
||||
// If it's down the Application Context will start and when it's back online it can be used.
|
||||
return new SpringOAuth2Client(this::createOAuth2AuthorizedClientManager);
|
||||
}
|
||||
|
||||
private OAuth2AuthorizedClientManager createOAuth2AuthorizedClientManager()
|
||||
{
|
||||
//Here we preserve the behaviour of previously used Keycloak Adapter
|
||||
// * Client is authenticating itself using basic auth
|
||||
// * Resource Owner Password Credentials Flow is used to authenticate Resource Owner
|
||||
// * There is no caching of authenticated clients (NoStoredAuthorizedClient)
|
||||
// * There is only one Authorization Server/Client pair (SingleClientRegistration)
|
||||
|
||||
final ClientRegistration clientRegistration = ClientRegistrations
|
||||
.fromIssuerLocation(identityServiceConfig.getIssuerUrl())
|
||||
.clientId(identityServiceConfig.getResource())
|
||||
.clientSecret(identityServiceConfig.getClientSecret())
|
||||
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
|
||||
.registrationId(SpringOAuth2Client.CLIENT_REGISTRATION_ID)
|
||||
.build();
|
||||
|
||||
final AuthorizedClientServiceOAuth2AuthorizedClientManager oauth2 =
|
||||
new AuthorizedClientServiceOAuth2AuthorizedClientManager(
|
||||
new SingleClientRegistration(clientRegistration),
|
||||
new NoStoredAuthorizedClient());
|
||||
oauth2.setContextAttributesMapper(OAuth2AuthorizeRequest::getAttributes);
|
||||
oauth2.setAuthorizedClientProvider(OAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.password(this::configureTimeouts)
|
||||
.build());
|
||||
|
||||
if (LOGGER.isDebugEnabled())
|
||||
{
|
||||
LOGGER.debug(" Created OAuth2 Client");
|
||||
LOGGER.debug(" OAuth2 Issuer URL: " + clientRegistration.getProviderDetails().getIssuerUri());
|
||||
LOGGER.debug(" OAuth2 ClientId: " + clientRegistration.getClientId());
|
||||
}
|
||||
|
||||
return oauth2;
|
||||
}
|
||||
|
||||
private void configureTimeouts(PasswordGrantBuilder builder)
|
||||
{
|
||||
final SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
|
||||
requestFactory.setConnectTimeout(identityServiceConfig.getClientConnectionTimeout());
|
||||
requestFactory.setReadTimeout(identityServiceConfig.getClientSocketTimeout());
|
||||
|
||||
final RestTemplate restTemplate = new RestTemplate(Arrays.asList(new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter()));
|
||||
restTemplate.setRequestFactory(requestFactory);
|
||||
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
|
||||
|
||||
final DefaultPasswordTokenResponseClient client = new DefaultPasswordTokenResponseClient();
|
||||
client.setRestOperations(restTemplate);
|
||||
|
||||
builder.accessTokenResponseClient(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType()
|
||||
{
|
||||
return OAuth2Client.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSingleton()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static class SpringOAuth2Client implements OAuth2Client
|
||||
{
|
||||
private static final String CLIENT_REGISTRATION_ID = "ids";
|
||||
private final Supplier<OAuth2AuthorizedClientManager> authorizedClientManagerSupplier;
|
||||
private final AtomicReference<OAuth2AuthorizedClientManager> authorizedClientManager = new AtomicReference<>();
|
||||
|
||||
public SpringOAuth2Client(Supplier<OAuth2AuthorizedClientManager> authorizedClientManagerSupplier)
|
||||
{
|
||||
this.authorizedClientManagerSupplier = Objects.requireNonNull(authorizedClientManagerSupplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verifyCredentials(String userName, String password)
|
||||
{
|
||||
final OAuth2AuthorizedClientManager clientManager;
|
||||
try
|
||||
{
|
||||
clientManager = getAuthorizedClientManager();
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
LOGGER.warn("Failed to instantiate OAuth2AuthorizedClientManager.", e);
|
||||
throw new CredentialsVerificationException("Unable to use the Authorization Server.", e);
|
||||
}
|
||||
|
||||
final OAuth2AuthorizedClient authorizedClient;
|
||||
try
|
||||
{
|
||||
final OAuth2AuthorizeRequest authRequest = createPasswordCredentialsRequest(userName, password);
|
||||
authorizedClient = clientManager.authorize(authRequest);
|
||||
}
|
||||
catch (OAuth2AuthorizationException e)
|
||||
{
|
||||
LOGGER.debug("Failed to authorize against Authorization Server. Reason: " + e.getError() + ".");
|
||||
throw new CredentialsVerificationException("Authorization against the Authorization Server failed with " + e.getError() + ".", e);
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
LOGGER.warn("Failed to authorize against Authorization Server. Reason: " + e.getMessage());
|
||||
throw new CredentialsVerificationException("Failed to authorize against Authorization Server.", e);
|
||||
}
|
||||
|
||||
if (authorizedClient == null || authorizedClient.getAccessToken() == null)
|
||||
{
|
||||
throw new CredentialsVerificationException("Resource Owner Password Credentials is not supported by the Authorization Server.");
|
||||
}
|
||||
}
|
||||
|
||||
private OAuth2AuthorizedClientManager getAuthorizedClientManager()
|
||||
{
|
||||
final OAuth2AuthorizedClientManager current = authorizedClientManager.get();
|
||||
if (current != null)
|
||||
{
|
||||
return current;
|
||||
}
|
||||
return authorizedClientManager
|
||||
.updateAndGet(prev -> prev != null ? prev : authorizedClientManagerSupplier.get());
|
||||
}
|
||||
|
||||
private OAuth2AuthorizeRequest createPasswordCredentialsRequest(String userName, String password)
|
||||
{
|
||||
return OAuth2AuthorizeRequest
|
||||
.withClientRegistrationId(CLIENT_REGISTRATION_ID)
|
||||
.principal(userName)
|
||||
.attribute(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, userName)
|
||||
.attribute(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
private static class NoStoredAuthorizedClient implements OAuth2AuthorizedClientService
|
||||
{
|
||||
|
||||
@Override
|
||||
public <T extends OAuth2AuthorizedClient> T loadAuthorizedClient(String clientRegistrationId, String principalName)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication principal)
|
||||
{
|
||||
//do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAuthorizedClient(String clientRegistrationId, String principalName)
|
||||
{
|
||||
//do nothing
|
||||
}
|
||||
}
|
||||
|
||||
private static class SingleClientRegistration implements ClientRegistrationRepository
|
||||
{
|
||||
private final ClientRegistration clientRegistration;
|
||||
|
||||
private SingleClientRegistration(ClientRegistration clientRegistration)
|
||||
{
|
||||
this.clientRegistration = Objects.requireNonNull(clientRegistration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientRegistration findByRegistrationId(String registrationId)
|
||||
{
|
||||
return Objects.equals(registrationId, clientRegistration.getRegistrationId()) ? clientRegistration : null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2020 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -26,7 +26,6 @@
|
||||
package org.alfresco.repo.tagging;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Serializable;
|
||||
@@ -41,9 +40,11 @@ import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.query.EmptyPagingResults;
|
||||
import org.alfresco.query.PagingRequest;
|
||||
@@ -64,11 +65,13 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
|
||||
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
|
||||
import org.alfresco.repo.transaction.TransactionListener;
|
||||
import org.alfresco.service.Experimental;
|
||||
import org.alfresco.service.cmr.action.Action;
|
||||
import org.alfresco.service.cmr.action.ActionService;
|
||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||
import org.alfresco.service.cmr.repository.ContentReader;
|
||||
import org.alfresco.service.cmr.repository.ContentService;
|
||||
import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.cmr.repository.Path;
|
||||
@@ -912,51 +915,23 @@ public class TaggingServiceImpl implements TaggingService,
|
||||
return new EmptyPagingResults<Pair<NodeRef, String>>();
|
||||
}
|
||||
|
||||
public PagingResults<Pair<NodeRef, String>> getTags(StoreRef storeRef, PagingRequest pagingRequest)
|
||||
{
|
||||
return getTags(storeRef, pagingRequest, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.alfresco.service.cmr.tagging.TaggingService#getTags(org.alfresco.service.cmr.repository.StoreRef, org.alfresco.query.PagingRequest)
|
||||
*/
|
||||
public PagingResults<Pair<NodeRef, String>> getTags(StoreRef storeRef, PagingRequest pagingRequest)
|
||||
public PagingResults<Pair<NodeRef, String>> getTags(StoreRef storeRef, PagingRequest pagingRequest, Collection<String> exactNamesFilter, Collection<String> alikeNamesFilter)
|
||||
{
|
||||
ParameterCheck.mandatory("storeRef", storeRef);
|
||||
|
||||
PagingResults<ChildAssociationRef> rootCategories = this.categoryService.getRootCategories(storeRef, ContentModel.ASPECT_TAGGABLE, pagingRequest, true);
|
||||
final List<Pair<NodeRef, String>> result = new ArrayList<Pair<NodeRef, String>>(rootCategories.getPage().size());
|
||||
for (ChildAssociationRef rootCategory : rootCategories.getPage())
|
||||
{
|
||||
String name = (String)this.nodeService.getProperty(rootCategory.getChildRef(), ContentModel.PROP_NAME);
|
||||
result.add(new Pair<NodeRef, String>(rootCategory.getChildRef(), name));
|
||||
}
|
||||
final boolean hasMoreItems = rootCategories.hasMoreItems();
|
||||
final Pair<Integer, Integer> totalResultCount = rootCategories.getTotalResultCount();
|
||||
final String queryExecutionId = rootCategories.getQueryExecutionId();
|
||||
rootCategories = null;
|
||||
PagingResults<ChildAssociationRef> rootCategories = categoryService.getRootCategories(storeRef, ContentModel.ASPECT_TAGGABLE, pagingRequest, true,
|
||||
exactNamesFilter, alikeNamesFilter);
|
||||
|
||||
return new PagingResults<Pair<NodeRef, String>>()
|
||||
{
|
||||
@Override
|
||||
public List<Pair<NodeRef, String>> getPage()
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMoreItems()
|
||||
{
|
||||
return hasMoreItems;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<Integer, Integer> getTotalResultCount()
|
||||
{
|
||||
return totalResultCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQueryExecutionId()
|
||||
{
|
||||
return queryExecutionId;
|
||||
}
|
||||
};
|
||||
return mapPagingResult(rootCategories,
|
||||
(childAssociation) -> new Pair<>(childAssociation.getChildRef(), childAssociation.getQName().getLocalName()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1575,4 +1550,59 @@ public class TaggingServiceImpl implements TaggingService,
|
||||
}
|
||||
}
|
||||
|
||||
@Experimental
|
||||
@Override
|
||||
public List<Pair<String, NodeRef>> createTags(final StoreRef storeRef, final List<String> tagNames)
|
||||
{
|
||||
updateTagBehaviour.disable();
|
||||
createTagBehaviour.disable();
|
||||
try
|
||||
{
|
||||
return tagNames.stream()
|
||||
.map(String::toLowerCase)
|
||||
.peek(tagName -> categoryService.getRootCategories(storeRef, ContentModel.ASPECT_TAGGABLE, tagName, false).stream()
|
||||
.filter(association -> Objects.nonNull(association.getChildRef()))
|
||||
.findAny()
|
||||
.ifPresent(association -> { throw new DuplicateChildNodeNameException(association.getParentRef(), association.getTypeQName(), tagName, null); }))
|
||||
.map(tagName -> new Pair<>(tagName, getTagNodeRef(storeRef, tagName, true)))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
finally
|
||||
{
|
||||
updateTagBehaviour.enable();
|
||||
createTagBehaviour.enable();
|
||||
}
|
||||
}
|
||||
|
||||
private <T, R> PagingResults<R> mapPagingResult(final PagingResults<T> pagingResults, final Function<T, R> mapper)
|
||||
{
|
||||
return new PagingResults<R>()
|
||||
{
|
||||
@Override
|
||||
public List<R> getPage()
|
||||
{
|
||||
return pagingResults.getPage().stream()
|
||||
.map(mapper)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMoreItems()
|
||||
{
|
||||
return pagingResults.hasMoreItems();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<Integer, Integer> getTotalResultCount()
|
||||
{
|
||||
return pagingResults.getTotalResultCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQueryExecutionId()
|
||||
{
|
||||
return pagingResults.getQueryExecutionId();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -1,140 +0,0 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* 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.urlshortening;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.service.cmr.urlshortening.UrlShortener;
|
||||
import org.apache.commons.httpclient.HostConfiguration;
|
||||
import org.apache.commons.httpclient.HttpClient;
|
||||
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
|
||||
import org.apache.commons.httpclient.NameValuePair;
|
||||
import org.apache.commons.httpclient.methods.GetMethod;
|
||||
import org.apache.commons.httpclient.protocol.Protocol;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* @deprecated as it is no longer used in the core repository code.
|
||||
*/
|
||||
@Deprecated
|
||||
public class BitlyUrlShortenerImpl implements UrlShortener
|
||||
{
|
||||
private static final Log log = LogFactory.getLog(BitlyUrlShortenerImpl.class);
|
||||
|
||||
private int urlLength = 20;
|
||||
private String username;
|
||||
private String apiKey = "R_ca15c6c89e9b25ccd170bafd209a0d4f";
|
||||
private HttpClient httpClient;
|
||||
|
||||
public BitlyUrlShortenerImpl()
|
||||
{
|
||||
httpClient = new HttpClient();
|
||||
httpClient.setHttpConnectionManager(new MultiThreadedHttpConnectionManager());
|
||||
HostConfiguration hostConfiguration = new HostConfiguration();
|
||||
hostConfiguration.setHost("api-ssl.bitly.com", 443, Protocol.getProtocol("https"));
|
||||
httpClient.setHostConfiguration(hostConfiguration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String shortenUrl(String longUrl)
|
||||
{
|
||||
if (log.isDebugEnabled())
|
||||
{
|
||||
log.debug("Shortening URL: " + longUrl);
|
||||
}
|
||||
String shortUrl = longUrl;
|
||||
if (longUrl.length() > urlLength)
|
||||
{
|
||||
GetMethod getMethod = new GetMethod();
|
||||
getMethod.setPath("/v3/shorten");
|
||||
|
||||
List<NameValuePair> args = new ArrayList<NameValuePair>();
|
||||
args.add(new NameValuePair("login", username));
|
||||
args.add(new NameValuePair("apiKey", apiKey));
|
||||
args.add(new NameValuePair("longUrl", longUrl));
|
||||
args.add(new NameValuePair("format", "txt"));
|
||||
getMethod.setQueryString(args.toArray(new NameValuePair[args.size()]));
|
||||
|
||||
try
|
||||
{
|
||||
int resultCode = httpClient.executeMethod(getMethod);
|
||||
if (resultCode == 200)
|
||||
{
|
||||
shortUrl = getMethod.getResponseBodyAsString();
|
||||
}
|
||||
else
|
||||
{
|
||||
log.warn("Failed to shorten URL " + longUrl + " - response code == " + resultCode);
|
||||
log.warn(getMethod.getResponseBodyAsString());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.error("Failed to shorten URL " + longUrl, ex);
|
||||
}
|
||||
if (log.isDebugEnabled())
|
||||
{
|
||||
log.debug("URL " + longUrl + " has been shortened to " + shortUrl);
|
||||
}
|
||||
}
|
||||
return shortUrl.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int getUrlLength()
|
||||
{
|
||||
return urlLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param urlLength the urlLength to set
|
||||
*/
|
||||
public void setUrlLength(int urlLength)
|
||||
{
|
||||
this.urlLength = urlLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param username the username to set
|
||||
*/
|
||||
public void setUsername(String username)
|
||||
{
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param apiKey the apiKey to set
|
||||
*/
|
||||
public void setApiKey(String apiKey)
|
||||
{
|
||||
this.apiKey = apiKey;
|
||||
}
|
||||
}
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -30,6 +30,7 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.alfresco.api.AlfrescoPublicApi;
|
||||
import org.alfresco.query.EmptyPagingResults;
|
||||
import org.alfresco.query.PagingRequest;
|
||||
import org.alfresco.query.PagingResults;
|
||||
import org.alfresco.service.Auditable;
|
||||
@@ -136,6 +137,24 @@ public interface CategoryService
|
||||
@Auditable(parameters = {"storeRef", "aspectName", "pagingRequest", "sortByName", "filter"})
|
||||
PagingResults<ChildAssociationRef> getRootCategories(StoreRef storeRef, QName aspectName, PagingRequest pagingRequest, boolean sortByName, String filter);
|
||||
|
||||
/**
|
||||
* Get a paged list of the root categories for an aspect/classification supporting multiple name filters.
|
||||
*
|
||||
* @param storeRef
|
||||
* @param aspectName
|
||||
* @param pagingRequest
|
||||
* @param sortByName
|
||||
* @param exactNamesFilter
|
||||
* @param alikeNamesFilter
|
||||
* @return
|
||||
*/
|
||||
@Auditable(parameters = {"storeRef", "aspectName", "pagingRequest", "sortByName", "exactNamesFilter", "alikeNamesFilter"})
|
||||
default PagingResults<ChildAssociationRef> getRootCategories(StoreRef storeRef, QName aspectName, PagingRequest pagingRequest, boolean sortByName,
|
||||
Collection<String> exactNamesFilter, Collection<String> alikeNamesFilter)
|
||||
{
|
||||
return new EmptyPagingResults<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the root categories for an aspect/classification with names that start with filter
|
||||
*
|
||||
|
@@ -1,36 +1,40 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* 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%
|
||||
*/
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2023 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.tagging;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.api.AlfrescoPublicApi;
|
||||
import org.alfresco.api.AlfrescoPublicApi;
|
||||
import org.alfresco.query.EmptyPagingResults;
|
||||
import org.alfresco.query.PagingRequest;
|
||||
import org.alfresco.query.PagingResults;
|
||||
import org.alfresco.service.Auditable;
|
||||
import org.alfresco.service.Experimental;
|
||||
import org.alfresco.service.NotAuditable;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.StoreRef;
|
||||
@@ -73,6 +77,21 @@ public interface TaggingService
|
||||
*/
|
||||
@NotAuditable
|
||||
PagingResults<Pair<NodeRef, String>> getTags(StoreRef storeRef, PagingRequest pagingRequest);
|
||||
|
||||
/**
|
||||
* Get a paged list of tags filtered by name
|
||||
*
|
||||
* @param storeRef StoreRef
|
||||
* @param pagingRequest PagingRequest
|
||||
* @param exactNamesFilter PagingRequest
|
||||
* @param alikeNamesFilter PagingRequest
|
||||
* @return PagingResults
|
||||
*/
|
||||
@NotAuditable
|
||||
default PagingResults<Pair<NodeRef, String>> getTags(StoreRef storeRef, PagingRequest pagingRequest, Collection<String> exactNamesFilter, Collection<String> alikeNamesFilter)
|
||||
{
|
||||
return new EmptyPagingResults<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the tags currently available that match the provided filter.
|
||||
@@ -306,17 +325,31 @@ public interface TaggingService
|
||||
*/
|
||||
@NotAuditable
|
||||
Pair<List<String>, Integer> getPagedTags(StoreRef storeRef, String filter, int fromTag, int pageSize);
|
||||
|
||||
|
||||
/**
|
||||
* Get tagged nodes and count of nodes group by tag name
|
||||
*
|
||||
* @param storeRef
|
||||
* @return
|
||||
*/
|
||||
@NotAuditable
|
||||
List<Pair<String, Integer>> findTaggedNodesAndCountByTagName(StoreRef storeRef);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get tagged nodes and count of nodes group by tag name
|
||||
*
|
||||
* @param storeRef
|
||||
* @return
|
||||
*/
|
||||
@NotAuditable
|
||||
List<Pair<String, Integer>> findTaggedNodesAndCountByTagName(StoreRef storeRef);
|
||||
|
||||
/**
|
||||
* Creates orphan tags. Tag names case will be lowered.
|
||||
*
|
||||
* @param storeRef Reference to node store.
|
||||
* @param tagNames List of tag names.
|
||||
* @return {@link List} of {@link Pair}s of tag names and node references.
|
||||
* @throws org.alfresco.service.cmr.repository.DuplicateChildNodeNameException if tag already exists.
|
||||
*/
|
||||
@Experimental
|
||||
@Auditable(parameters = {"tagNames"})
|
||||
default List<Pair<String, NodeRef>> createTags(StoreRef storeRef, List<String> tagNames)
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@@ -1159,7 +1159,7 @@
|
||||
on z.parent_node_id = #{parentNode.id}
|
||||
and z.child_node_id = a.parent_node_id
|
||||
where c.child_node_id = a.child_node_id
|
||||
);
|
||||
)
|
||||
</select>
|
||||
|
||||
<select id="select_ChildAssocsByPropertyValue" parameterType="ChildProperty" resultMap="result_ChildAssoc">
|
||||
|
@@ -813,13 +813,6 @@ solr6.store.mappings.value.solrMappingHistory.baseUrl=/solr/history
|
||||
solr6.store.mappings.value.solrMappingHistory.protocol=workspace
|
||||
solr6.store.mappings.value.solrMappingHistory.identifier=history
|
||||
|
||||
#
|
||||
# URL Shortening Properties
|
||||
#
|
||||
urlshortening.bitly.username=brianalfresco
|
||||
urlshortening.bitly.api.key=R_ca15c6c89e9b25ccd170bafd209a0d4f
|
||||
urlshortening.bitly.url.length=20
|
||||
|
||||
#
|
||||
# Bulk Filesystem Importer
|
||||
#
|
||||
@@ -1156,7 +1149,7 @@ smart.folders.config.type.templates.path=${spaces.dictionary.childname}/${spaces
|
||||
smart.folders.config.type.templates.qname.filter=none
|
||||
|
||||
# Preferred password encoding, md4, sha256, bcrypt10
|
||||
system.preferred.password.encoding=md4
|
||||
system.preferred.password.encoding=bcrypt10
|
||||
|
||||
# Upgrade Password Hash Job
|
||||
system.upgradePasswordHash.jobBatchSize=100
|
||||
|
@@ -21,12 +21,12 @@
|
||||
<property name="allowGuestLogin">
|
||||
<value>${identity-service.authentication.allowGuestLogin}</value>
|
||||
</property>
|
||||
<property name="authenticatorAuthzClient">
|
||||
<ref bean="authenticatorAuthzClient"/>
|
||||
<property name="oAuth2Client">
|
||||
<ref bean="oAuth2Client"/>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean name="authenticatorAuthzClient" class="org.alfresco.repo.security.authentication.identityservice.AuthenticatorAuthzClientFactoryBean">
|
||||
<bean name="oAuth2Client" class="org.alfresco.repo.security.authentication.identityservice.OAuth2ClientFactoryBean">
|
||||
<property name="identityServiceConfig">
|
||||
<ref bean="identityServiceConfig" />
|
||||
</property>
|
||||
|
@@ -84,7 +84,6 @@ import org.junit.runners.Suite;
|
||||
org.alfresco.repo.transfer.HttpClientTransmitterImplTest.class,
|
||||
org.alfresco.repo.transfer.manifest.TransferManifestTest.class,
|
||||
org.alfresco.repo.transfer.TransferVersionCheckerImplTest.class,
|
||||
org.alfresco.repo.urlshortening.BitlyUrlShortenerTest.class,
|
||||
org.alfresco.service.cmr.calendar.CalendarRecurrenceHelperTest.class,
|
||||
org.alfresco.service.cmr.calendar.CalendarTimezoneHelperTest.class,
|
||||
org.alfresco.tools.RenameUserTest.class,
|
||||
@@ -137,6 +136,7 @@ import org.junit.runners.Suite;
|
||||
org.alfresco.repo.search.impl.solr.facet.FacetQNameUtilsTest.class,
|
||||
org.alfresco.util.BeanExtenderUnitTest.class,
|
||||
org.alfresco.repo.solr.SOLRTrackingComponentUnitTest.class,
|
||||
org.alfresco.repo.security.authentication.identityservice.SpringOAuth2ClientUnitTest.class,
|
||||
org.alfresco.repo.security.authentication.CompositePasswordEncoderTest.class,
|
||||
org.alfresco.repo.security.authentication.PasswordHashingTest.class,
|
||||
org.alfresco.repo.security.authority.script.ScriptAuthorityService_RegExTest.class,
|
||||
@@ -244,7 +244,8 @@ import org.junit.runners.Suite;
|
||||
|
||||
org.alfresco.repo.event2.RepoEvent2UnitSuite.class,
|
||||
|
||||
org.alfresco.util.schemacomp.SchemaDifferenceHelperUnitTest.class
|
||||
org.alfresco.util.schemacomp.SchemaDifferenceHelperUnitTest.class,
|
||||
org.alfresco.repo.tagging.TaggingServiceImplUnitTest.class
|
||||
})
|
||||
public class AllUnitTestsSuite
|
||||
{
|
||||
|
@@ -26,15 +26,12 @@
|
||||
package org.alfresco.repo.security.authentication;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.transaction.Status;
|
||||
import javax.transaction.UserTransaction;
|
||||
|
||||
@@ -48,7 +45,6 @@ import net.sf.acegisecurity.DisabledException;
|
||||
import net.sf.acegisecurity.LockedException;
|
||||
import net.sf.acegisecurity.UserDetails;
|
||||
import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.admin.SysAdminParamsImpl;
|
||||
@@ -519,50 +515,48 @@ public class AuthenticationTest extends TestCase
|
||||
assertTrue("The user should exist", dao.userExists(userName));
|
||||
}
|
||||
|
||||
public void testCreateAndyUserAndOtherCRUD() throws NoSuchAlgorithmException, UnsupportedEncodingException
|
||||
public void testCreateAndyUserAndUpdatePassword()
|
||||
{
|
||||
RepositoryAuthenticationDao dao = createRepositoryAuthenticationDao();
|
||||
|
||||
dao.createUser("Andy", "cabbage".toCharArray());
|
||||
assertNotNull(dao.getUserOrNull("Andy"));
|
||||
|
||||
UserDetails AndyDetails = (UserDetails) dao.loadUserByUsername("Andy");
|
||||
assertNotNull(AndyDetails);
|
||||
assertEquals("Andy", AndyDetails.getUsername());
|
||||
// assertNotNull(dao.getSalt(AndyDetails));
|
||||
assertTrue(AndyDetails.isAccountNonExpired());
|
||||
assertTrue(AndyDetails.isAccountNonLocked());
|
||||
assertTrue(AndyDetails.isCredentialsNonExpired());
|
||||
assertTrue(AndyDetails.isEnabled());
|
||||
assertNotSame("cabbage", AndyDetails.getPassword());
|
||||
assertTrue(compositePasswordEncoder.matches(compositePasswordEncoder.getPreferredEncoding(),"cabbage", AndyDetails.getPassword(), null));
|
||||
assertEquals(1, AndyDetails.getAuthorities().length);
|
||||
RepositoryAuthenticatedUser andyDetails = (RepositoryAuthenticatedUser) dao.loadUserByUsername("Andy");
|
||||
assertNotNull("User unexpectedly null", andyDetails);
|
||||
assertEquals("Unexpected username", "Andy", andyDetails.getUsername());
|
||||
Object originalSalt = andyDetails.getSalt();
|
||||
assertNotNull("Salt was not generated", originalSalt);
|
||||
assertTrue("Account unexpectedly expired", andyDetails.isAccountNonExpired());
|
||||
assertTrue("Account unexpectedly locked", andyDetails.isAccountNonLocked());
|
||||
assertTrue("Credentials unexpectedly expired", andyDetails.isCredentialsNonExpired());
|
||||
assertTrue("User unexpectedly disabled", andyDetails.isEnabled());
|
||||
assertNotSame("Password was not hashed", "cabbage", andyDetails.getPassword());
|
||||
assertTrue("Failed to recalculate same password hash", compositePasswordEncoder.matches(compositePasswordEncoder.getPreferredEncoding(),"cabbage", andyDetails.getPassword(), originalSalt));
|
||||
assertEquals("User does not have a single authority", 1, andyDetails.getAuthorities().length);
|
||||
|
||||
// Object oldSalt = dao.getSalt(AndyDetails);
|
||||
dao.updateUser("Andy", "carrot".toCharArray());
|
||||
UserDetails newDetails = (UserDetails) dao.loadUserByUsername("Andy");
|
||||
assertNotNull(newDetails);
|
||||
assertEquals("Andy", newDetails.getUsername());
|
||||
// assertNotNull(dao.getSalt(newDetails));
|
||||
assertTrue(newDetails.isAccountNonExpired());
|
||||
assertTrue(newDetails.isAccountNonLocked());
|
||||
assertTrue(newDetails.isCredentialsNonExpired());
|
||||
assertTrue(newDetails.isEnabled());
|
||||
assertNotSame("carrot", newDetails.getPassword());
|
||||
assertEquals(1, newDetails.getAuthorities().length);
|
||||
RepositoryAuthenticatedUser newDetails = (RepositoryAuthenticatedUser) dao.loadUserByUsername("Andy");
|
||||
assertNotNull("New details were null", newDetails);
|
||||
assertEquals("New details contain wrong username", "Andy", newDetails.getUsername());
|
||||
Object updatedSalt = newDetails.getSalt();
|
||||
assertNotNull("New details contain null salt", updatedSalt);
|
||||
assertTrue("Updated account is expired", newDetails.isAccountNonExpired());
|
||||
assertTrue("Updated account is locked", newDetails.isAccountNonLocked());
|
||||
assertTrue("Updated account has expired credentials", newDetails.isCredentialsNonExpired());
|
||||
assertTrue("Updated account is not enabled", newDetails.isEnabled());
|
||||
assertNotSame("Updated account contains unhashed password", "carrot", newDetails.getPassword());
|
||||
assertEquals("Updated account should have a single authority", 1, newDetails.getAuthorities().length);
|
||||
assertTrue("Failed to validate updated password hash", compositePasswordEncoder.matches(compositePasswordEncoder.getPreferredEncoding(),"carrot", newDetails.getPassword(), updatedSalt));
|
||||
assertNotSame("Expected salt to be replaced when password was updated", originalSalt, updatedSalt);
|
||||
|
||||
assertNotSame(AndyDetails.getPassword(), newDetails.getPassword());
|
||||
RepositoryAuthenticatedUser rau = (RepositoryAuthenticatedUser) newDetails;
|
||||
assertTrue(compositePasswordEncoder.matchesPassword("carrot", newDetails.getPassword(), null, rau.getHashIndicator()));
|
||||
// assertNotSame(oldSalt, dao.getSalt(newDetails));
|
||||
|
||||
//Update again
|
||||
dao.updateUser("Andy", "potato".toCharArray());
|
||||
newDetails = (UserDetails) dao.loadUserByUsername("Andy");
|
||||
assertNotNull(newDetails);
|
||||
assertEquals("Andy", newDetails.getUsername());
|
||||
rau = (RepositoryAuthenticatedUser) newDetails;
|
||||
assertTrue(compositePasswordEncoder.matchesPassword("potato", newDetails.getPassword(), null, rau.getHashIndicator()));
|
||||
// Update back to first password again.
|
||||
dao.updateUser("Andy", "cabbage".toCharArray());
|
||||
RepositoryAuthenticatedUser thirdDetails = (RepositoryAuthenticatedUser) dao.loadUserByUsername("Andy");
|
||||
Object thirdSalt = thirdDetails.getSalt();
|
||||
assertNotSame("New salt should not match original salt", thirdSalt, originalSalt);
|
||||
assertNotSame("New salt should not match previous salt", thirdSalt, updatedSalt);
|
||||
assertTrue("New password hash was not reproducible", compositePasswordEncoder.matches(compositePasswordEncoder.getPreferredEncoding(), "cabbage", thirdDetails.getPassword(), thirdSalt));
|
||||
|
||||
dao.deleteUser("Andy");
|
||||
assertFalse("Should not be a cache entry for 'Andy'.", authenticationCache.contains("Andy"));
|
||||
@@ -1989,131 +1983,142 @@ public class AuthenticationTest extends TestCase
|
||||
* Tests the scenario where a user logs in after the system has been upgraded.
|
||||
* Their password should get re-hashed using the preferred encoding.
|
||||
*/
|
||||
public void testRehashedPasswordOnAuthentication() throws Exception
|
||||
public void testRehashedPasswordOnAuthentication()
|
||||
{
|
||||
// create the Andy authentication
|
||||
assertNull(authenticationComponent.getCurrentAuthentication());
|
||||
authenticationComponent.setSystemUserAsCurrentUser();
|
||||
pubAuthenticationService.createAuthentication("Andy", "auth1".toCharArray());
|
||||
|
||||
// find the node representing the Andy user and it's properties
|
||||
NodeRef andyUserNodeRef = getRepositoryAuthenticationDao(). getUserOrNull("Andy");
|
||||
assertNotNull(andyUserNodeRef);
|
||||
|
||||
// ensure the properties are in the state we're expecting
|
||||
Map<QName, Serializable> userProps = nodeService.getProperties(andyUserNodeRef);
|
||||
String passwordProp = (String)userProps.get(ContentModel.PROP_PASSWORD);
|
||||
assertNull("Expected the password property to be null", passwordProp);
|
||||
String password2Prop = (String)userProps.get(ContentModel.PROP_PASSWORD_SHA256);
|
||||
assertNull("Expected the password2 property to be null", password2Prop);
|
||||
String passwordHashProp = (String)userProps.get(ContentModel.PROP_PASSWORD_HASH);
|
||||
assertNotNull("Expected the passwordHash property to be populated", passwordHashProp);
|
||||
List<String> hashIndicatorProp = (List<String>)userProps.get(ContentModel.PROP_HASH_INDICATOR);
|
||||
assertNotNull("Expected the hashIndicator property to be populated", hashIndicatorProp);
|
||||
|
||||
// re-generate an md4 hashed password
|
||||
MD4PasswordEncoderImpl md4PasswordEncoder = new MD4PasswordEncoderImpl();
|
||||
String md4Password = md4PasswordEncoder.encodePassword("auth1", null);
|
||||
|
||||
// re-generate a sha256 hashed password
|
||||
String salt = (String)userProps.get(ContentModel.PROP_SALT);
|
||||
ShaPasswordEncoderImpl sha256PasswordEncoder = new ShaPasswordEncoderImpl(256);
|
||||
String sha256Password = sha256PasswordEncoder.encodePassword("auth1", salt);
|
||||
|
||||
// change the underlying user object to represent state in previous release
|
||||
userProps.put(ContentModel.PROP_PASSWORD, md4Password);
|
||||
userProps.put(ContentModel.PROP_PASSWORD_SHA256, sha256Password);
|
||||
userProps.remove(ContentModel.PROP_PASSWORD_HASH);
|
||||
userProps.remove(ContentModel.PROP_HASH_INDICATOR);
|
||||
nodeService.setProperties(andyUserNodeRef, userProps);
|
||||
|
||||
// make sure the changes took effect
|
||||
Map<QName, Serializable> updatedProps = nodeService.getProperties(andyUserNodeRef);
|
||||
String usernameProp = (String)updatedProps.get(ContentModel.PROP_USER_USERNAME);
|
||||
assertEquals("Expected the username property to be 'Andy'", "Andy", usernameProp);
|
||||
passwordProp = (String)updatedProps.get(ContentModel.PROP_PASSWORD);
|
||||
assertNotNull("Expected the password property to be populated", passwordProp);
|
||||
password2Prop = (String)updatedProps.get(ContentModel.PROP_PASSWORD_SHA256);
|
||||
assertNotNull("Expected the password2 property to be populated", password2Prop);
|
||||
passwordHashProp = (String)updatedProps.get(ContentModel.PROP_PASSWORD_HASH);
|
||||
assertNull("Expected the passwordHash property to be null", passwordHashProp);
|
||||
hashIndicatorProp = (List<String>)updatedProps.get(ContentModel.PROP_HASH_INDICATOR);
|
||||
assertNull("Expected the hashIndicator property to be null", hashIndicatorProp);
|
||||
|
||||
// authenticate the user
|
||||
authenticationComponent.clearCurrentSecurityContext();
|
||||
pubAuthenticationService.authenticate("Andy", "auth1".toCharArray());
|
||||
assertEquals("Andy", authenticationService.getCurrentUserName());
|
||||
|
||||
// commit the transaction to invoke the password hashing of the user
|
||||
userTransaction.commit();
|
||||
|
||||
// start another transaction and change to system user
|
||||
userTransaction = transactionService.getUserTransaction();
|
||||
userTransaction.begin();
|
||||
authenticationComponent.setSystemUserAsCurrentUser();
|
||||
|
||||
// verify that the new properties are populated and the old ones are cleaned up
|
||||
Map<QName, Serializable> upgradedProps = nodeService.getProperties(andyUserNodeRef);
|
||||
passwordProp = (String)upgradedProps.get(ContentModel.PROP_PASSWORD);
|
||||
assertNull("Expected the password property to be null", passwordProp);
|
||||
password2Prop = (String)upgradedProps.get(ContentModel.PROP_PASSWORD_SHA256);
|
||||
assertNull("Expected the password2 property to be null", password2Prop);
|
||||
passwordHashProp = (String)upgradedProps.get(ContentModel.PROP_PASSWORD_HASH);
|
||||
assertNotNull("Expected the passwordHash property to be populated", passwordHashProp);
|
||||
hashIndicatorProp = (List<String>)upgradedProps.get(ContentModel.PROP_HASH_INDICATOR);
|
||||
assertNotNull("Expected the hashIndicator property to be populated", hashIndicatorProp);
|
||||
assertTrue("Expected there to be a single hash indicator entry", (hashIndicatorProp.size() == 1));
|
||||
String preferredEncoding = compositePasswordEncoder.getPreferredEncoding();
|
||||
String hashEncoding = (String)hashIndicatorProp.get(0);
|
||||
assertEquals("Expected hash indicator to be '" + preferredEncoding + "' but it was: " + hashEncoding,
|
||||
// This test requires upgrading from md4 to sha256 hashing.
|
||||
String defaultPreferredEncoding = compositePasswordEncoder.getPreferredEncoding();
|
||||
compositePasswordEncoder.setPreferredEncoding("md4");
|
||||
|
||||
try
|
||||
{
|
||||
// create the Andy authentication
|
||||
assertNull(authenticationComponent.getCurrentAuthentication());
|
||||
authenticationComponent.setSystemUserAsCurrentUser();
|
||||
pubAuthenticationService.createAuthentication("Andy", "auth1".toCharArray());
|
||||
|
||||
// find the node representing the Andy user and its properties
|
||||
NodeRef andyUserNodeRef = getRepositoryAuthenticationDao().getUserOrNull("Andy");
|
||||
assertNotNull(andyUserNodeRef);
|
||||
|
||||
// ensure the properties are in the state we're expecting
|
||||
Map<QName, Serializable> userProps = nodeService.getProperties(andyUserNodeRef);
|
||||
String passwordProp = (String) userProps.get(ContentModel.PROP_PASSWORD);
|
||||
assertNull("Expected the password property to be null", passwordProp);
|
||||
String password2Prop = (String) userProps.get(ContentModel.PROP_PASSWORD_SHA256);
|
||||
assertNull("Expected the password2 property to be null", password2Prop);
|
||||
String passwordHashProp = (String) userProps.get(ContentModel.PROP_PASSWORD_HASH);
|
||||
assertNotNull("Expected the passwordHash property to be populated", passwordHashProp);
|
||||
List<String> hashIndicatorProp = (List<String>) userProps.get(ContentModel.PROP_HASH_INDICATOR);
|
||||
assertNotNull("Expected the hashIndicator property to be populated", hashIndicatorProp);
|
||||
|
||||
// re-generate an md4 hashed password
|
||||
MD4PasswordEncoderImpl md4PasswordEncoder = new MD4PasswordEncoderImpl();
|
||||
String md4Password = md4PasswordEncoder.encodePassword("auth1", null);
|
||||
|
||||
// re-generate a sha256 hashed password
|
||||
String salt = (String) userProps.get(ContentModel.PROP_SALT);
|
||||
ShaPasswordEncoderImpl sha256PasswordEncoder = new ShaPasswordEncoderImpl(256);
|
||||
String sha256Password = sha256PasswordEncoder.encodePassword("auth1", salt);
|
||||
|
||||
// change the underlying user object to represent state in previous release
|
||||
userProps.put(ContentModel.PROP_PASSWORD, md4Password);
|
||||
userProps.put(ContentModel.PROP_PASSWORD_SHA256, sha256Password);
|
||||
userProps.remove(ContentModel.PROP_PASSWORD_HASH);
|
||||
userProps.remove(ContentModel.PROP_HASH_INDICATOR);
|
||||
nodeService.setProperties(andyUserNodeRef, userProps);
|
||||
|
||||
// make sure the changes took effect
|
||||
Map<QName, Serializable> updatedProps = nodeService.getProperties(andyUserNodeRef);
|
||||
String usernameProp = (String) updatedProps.get(ContentModel.PROP_USER_USERNAME);
|
||||
assertEquals("Expected the username property to be 'Andy'", "Andy", usernameProp);
|
||||
passwordProp = (String) updatedProps.get(ContentModel.PROP_PASSWORD);
|
||||
assertNotNull("Expected the password property to be populated", passwordProp);
|
||||
password2Prop = (String) updatedProps.get(ContentModel.PROP_PASSWORD_SHA256);
|
||||
assertNotNull("Expected the password2 property to be populated", password2Prop);
|
||||
passwordHashProp = (String) updatedProps.get(ContentModel.PROP_PASSWORD_HASH);
|
||||
assertNull("Expected the passwordHash property to be null", passwordHashProp);
|
||||
hashIndicatorProp = (List<String>) updatedProps.get(ContentModel.PROP_HASH_INDICATOR);
|
||||
assertNull("Expected the hashIndicator property to be null", hashIndicatorProp);
|
||||
|
||||
// authenticate the user
|
||||
authenticationComponent.clearCurrentSecurityContext();
|
||||
pubAuthenticationService.authenticate("Andy", "auth1".toCharArray());
|
||||
assertEquals("Andy", authenticationService.getCurrentUserName());
|
||||
|
||||
// commit the transaction to invoke the password hashing of the user
|
||||
userTransaction.commit();
|
||||
|
||||
// start another transaction and change to system user
|
||||
userTransaction = transactionService.getUserTransaction();
|
||||
userTransaction.begin();
|
||||
authenticationComponent.setSystemUserAsCurrentUser();
|
||||
|
||||
// verify that the new properties are populated and the old ones are cleaned up
|
||||
Map<QName, Serializable> upgradedProps = nodeService.getProperties(andyUserNodeRef);
|
||||
passwordProp = (String) upgradedProps.get(ContentModel.PROP_PASSWORD);
|
||||
assertNull("Expected the password property to be null", passwordProp);
|
||||
password2Prop = (String) upgradedProps.get(ContentModel.PROP_PASSWORD_SHA256);
|
||||
assertNull("Expected the password2 property to be null", password2Prop);
|
||||
passwordHashProp = (String) upgradedProps.get(ContentModel.PROP_PASSWORD_HASH);
|
||||
assertNotNull("Expected the passwordHash property to be populated", passwordHashProp);
|
||||
hashIndicatorProp = (List<String>) upgradedProps.get(ContentModel.PROP_HASH_INDICATOR);
|
||||
assertNotNull("Expected the hashIndicator property to be populated", hashIndicatorProp);
|
||||
assertTrue("Expected there to be a single hash indicator entry", (hashIndicatorProp.size() == 1));
|
||||
String preferredEncoding = compositePasswordEncoder.getPreferredEncoding();
|
||||
String hashEncoding = hashIndicatorProp.get(0);
|
||||
assertEquals("Expected hash indicator to be '" + preferredEncoding + "' but it was: " + hashEncoding,
|
||||
preferredEncoding, hashEncoding);
|
||||
|
||||
// delete the user and clear the security context
|
||||
this.deleteAndy();
|
||||
authenticationComponent.clearCurrentSecurityContext();
|
||||
|
||||
// delete the user and clear the security context
|
||||
this.deleteAndy();
|
||||
authenticationComponent.clearCurrentSecurityContext();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
compositePasswordEncoder.setPreferredEncoding(defaultPreferredEncoding);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For on premise the default is MD4, for cloud BCRYPT10
|
||||
*
|
||||
* @throws Exception
|
||||
* Test password encoding with MD4 without a salt.
|
||||
*/
|
||||
public void testDefaultEncodingIsMD4() throws Exception
|
||||
public void testGetsMD4Password()
|
||||
{
|
||||
assertNotNull(compositePasswordEncoder);
|
||||
assertEquals("md4", compositePasswordEncoder.getPreferredEncoding());
|
||||
}
|
||||
String defaultPreferredEncoding = compositePasswordEncoder.getPreferredEncoding();
|
||||
compositePasswordEncoder.setPreferredEncoding("md4");
|
||||
|
||||
/**
|
||||
* For on premise the default is MD4, get it
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public void testGetsMD4Password() throws Exception
|
||||
{
|
||||
String user = "mduzer";
|
||||
String rawPass = "roarPazzw0rd";
|
||||
assertEquals("md4", compositePasswordEncoder.getPreferredEncoding());
|
||||
dao.createUser(user, null, rawPass.toCharArray());
|
||||
NodeRef userNodeRef = getRepositoryAuthenticationDao().getUserOrNull(user);
|
||||
assertNotNull(userNodeRef);
|
||||
String pass = dao.getMD4HashedPassword(user);
|
||||
assertNotNull(pass);
|
||||
assertTrue(compositePasswordEncoder.matches("md4", rawPass, pass, null));
|
||||
try
|
||||
{
|
||||
String user = "mduzer";
|
||||
String rawPass = "roarPazzw0rd";
|
||||
dao.createUser(user, null, rawPass.toCharArray());
|
||||
NodeRef userNodeRef = getRepositoryAuthenticationDao().getUserOrNull(user);
|
||||
assertNotNull(userNodeRef);
|
||||
String pass = dao.getMD4HashedPassword(user);
|
||||
assertNotNull(pass);
|
||||
assertTrue(compositePasswordEncoder.matches("md4", rawPass, pass, null));
|
||||
|
||||
Map<QName, Serializable> properties = nodeService.getProperties(userNodeRef);
|
||||
properties.remove(ContentModel.PROP_PASSWORD_HASH);
|
||||
properties.remove(ContentModel.PROP_HASH_INDICATOR);
|
||||
properties.remove(ContentModel.PROP_PASSWORD);
|
||||
properties.remove(ContentModel.PROP_PASSWORD_SHA256);
|
||||
String encoded = compositePasswordEncoder.encode("md4",new String(rawPass), null);
|
||||
properties.put(ContentModel.PROP_PASSWORD, encoded);
|
||||
nodeService.setProperties(userNodeRef, properties);
|
||||
pass = dao.getMD4HashedPassword(user);
|
||||
assertNotNull(pass);
|
||||
assertEquals(encoded, pass);
|
||||
dao.deleteUser(user);
|
||||
Map<QName, Serializable> properties = nodeService.getProperties(userNodeRef);
|
||||
properties.remove(ContentModel.PROP_PASSWORD_HASH);
|
||||
properties.remove(ContentModel.PROP_HASH_INDICATOR);
|
||||
properties.remove(ContentModel.PROP_PASSWORD);
|
||||
properties.remove(ContentModel.PROP_PASSWORD_SHA256);
|
||||
String encoded = compositePasswordEncoder.encodePassword("md4", rawPass, List.of("md4"));
|
||||
properties.put(ContentModel.PROP_PASSWORD, encoded);
|
||||
nodeService.setProperties(userNodeRef, properties);
|
||||
pass = dao.getMD4HashedPassword(user);
|
||||
assertNotNull(pass);
|
||||
assertEquals(encoded, pass);
|
||||
dao.deleteUser(user);
|
||||
}
|
||||
finally
|
||||
{
|
||||
compositePasswordEncoder.setPreferredEncoding(defaultPreferredEncoding);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -25,14 +25,17 @@
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.identityservice;
|
||||
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.net.ConnectException;
|
||||
|
||||
import org.alfresco.error.ExceptionStackUtil;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationContext;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationException;
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceAuthenticationComponent.OAuth2Client;
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceAuthenticationComponent.OAuth2Client.CredentialsVerificationException;
|
||||
import org.alfresco.repo.security.sync.UserRegistrySynchronizer;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.cmr.security.PersonService;
|
||||
@@ -41,9 +44,6 @@ import org.alfresco.util.BaseSpringTest;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.util.HttpResponseException;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
public class IdentityServiceAuthenticationComponentTest extends BaseSpringTest
|
||||
@@ -65,7 +65,7 @@ public class IdentityServiceAuthenticationComponentTest extends BaseSpringTest
|
||||
@Autowired
|
||||
private PersonService personService;
|
||||
|
||||
private AuthzClient mockAuthzClient;
|
||||
private OAuth2Client mockOAuth2Client;
|
||||
|
||||
@Before
|
||||
public void setUp()
|
||||
@@ -76,8 +76,8 @@ public class IdentityServiceAuthenticationComponentTest extends BaseSpringTest
|
||||
authComponent.setNodeService(nodeService);
|
||||
authComponent.setPersonService(personService);
|
||||
|
||||
mockAuthzClient = mock(AuthzClient.class);
|
||||
authComponent.setAuthenticatorAuthzClient(mockAuthzClient);
|
||||
mockOAuth2Client = mock(OAuth2Client.class);
|
||||
authComponent.setOAuth2Client(mockOAuth2Client);
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -89,8 +89,9 @@ public class IdentityServiceAuthenticationComponentTest extends BaseSpringTest
|
||||
@Test (expected=AuthenticationException.class)
|
||||
public void testAuthenticationFail()
|
||||
{
|
||||
when(mockAuthzClient.obtainAccessToken("username", "password"))
|
||||
.thenThrow(new HttpResponseException("Unauthorized", 401, "Unauthorized", null));
|
||||
doThrow(new CredentialsVerificationException("Failed"))
|
||||
.when(mockOAuth2Client)
|
||||
.verifyCredentials("username", "password");
|
||||
|
||||
authComponent.authenticateImpl("username", "password".toCharArray());
|
||||
}
|
||||
@@ -98,8 +99,9 @@ public class IdentityServiceAuthenticationComponentTest extends BaseSpringTest
|
||||
@Test(expected = AuthenticationException.class)
|
||||
public void testAuthenticationFail_connectionException()
|
||||
{
|
||||
when(mockAuthzClient.obtainAccessToken("username", "password")).thenThrow(
|
||||
new RuntimeException("Couldn't connect to server", new ConnectException("ConnectionRefused")));
|
||||
doThrow(new CredentialsVerificationException("Couldn't connect to server", new ConnectException("ConnectionRefused")))
|
||||
.when(mockOAuth2Client)
|
||||
.verifyCredentials("username", "password");
|
||||
|
||||
try
|
||||
{
|
||||
@@ -116,8 +118,9 @@ public class IdentityServiceAuthenticationComponentTest extends BaseSpringTest
|
||||
@Test (expected=AuthenticationException.class)
|
||||
public void testAuthenticationFail_otherException()
|
||||
{
|
||||
when(mockAuthzClient.obtainAccessToken("username", "password"))
|
||||
.thenThrow(new RuntimeException("Some other errors!"));
|
||||
doThrow(new RuntimeException("Some other errors!"))
|
||||
.when(mockOAuth2Client)
|
||||
.verifyCredentials("username", "password");
|
||||
|
||||
authComponent.authenticateImpl("username", "password".toCharArray());
|
||||
}
|
||||
@@ -125,8 +128,7 @@ public class IdentityServiceAuthenticationComponentTest extends BaseSpringTest
|
||||
@Test
|
||||
public void testAuthenticationPass()
|
||||
{
|
||||
when(mockAuthzClient.obtainAccessToken("username", "password"))
|
||||
.thenReturn(new AccessTokenResponse());
|
||||
doNothing().when(mockOAuth2Client).verifyCredentials("username", "password");
|
||||
|
||||
authComponent.authenticateImpl("username", "password".toCharArray());
|
||||
|
||||
@@ -135,9 +137,9 @@ public class IdentityServiceAuthenticationComponentTest extends BaseSpringTest
|
||||
}
|
||||
|
||||
@Test (expected= AuthenticationException.class)
|
||||
public void testFallthroughWhenAuthzClientIsNull()
|
||||
public void testFallthroughWhenOAuth2ClientIsNull()
|
||||
{
|
||||
authComponent.setAuthenticatorAuthzClient(null);
|
||||
authComponent.setOAuth2Client(null);
|
||||
authComponent.authenticateImpl("username", "password".toCharArray());
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2023 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.security.authentication.identityservice;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceAuthenticationComponent.OAuth2Client.CredentialsVerificationException;
|
||||
import org.alfresco.repo.security.authentication.identityservice.OAuth2ClientFactoryBean.SpringOAuth2Client;
|
||||
import org.junit.Test;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
|
||||
public class SpringOAuth2ClientUnitTest
|
||||
{
|
||||
private static final String USER_NAME = "user";
|
||||
private static final String PASSWORD = "password";
|
||||
|
||||
@Test
|
||||
public void shouldRecoverFromInitialAuthorizationServerUnavailability()
|
||||
{
|
||||
final OAuth2AuthorizedClient authorizedClient = mock(OAuth2AuthorizedClient.class);
|
||||
when(authorizedClient.getAccessToken()).thenReturn(mock(OAuth2AccessToken.class));
|
||||
final OAuth2AuthorizedClientManager authClientManager = mock(OAuth2AuthorizedClientManager.class);
|
||||
when(authClientManager.authorize(any())).thenReturn(authorizedClient);
|
||||
|
||||
final SpringOAuth2Client client = new SpringOAuth2Client(faultySupplier(3, authClientManager));
|
||||
|
||||
assertThatExceptionOfType(CredentialsVerificationException.class)
|
||||
.isThrownBy(() -> client.verifyCredentials(USER_NAME, PASSWORD))
|
||||
.havingCause().withNoCause().withMessage("Expected failure #1");
|
||||
verifyNoInteractions(authClientManager);
|
||||
|
||||
assertThatExceptionOfType(CredentialsVerificationException.class)
|
||||
.isThrownBy(() -> client.verifyCredentials(USER_NAME, PASSWORD))
|
||||
.havingCause().withNoCause().withMessage("Expected failure #2");
|
||||
verifyNoInteractions(authClientManager);
|
||||
|
||||
assertThatExceptionOfType(CredentialsVerificationException.class)
|
||||
.isThrownBy(() -> client.verifyCredentials(USER_NAME, PASSWORD))
|
||||
.havingCause().withNoCause().withMessage("Expected failure #3");
|
||||
verifyNoInteractions(authClientManager);
|
||||
|
||||
client.verifyCredentials(USER_NAME, PASSWORD);
|
||||
verify(authClientManager).authorize(argThat(r -> r.getPrincipal() != null && USER_NAME.equals(r.getPrincipal().getPrincipal())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldThrowVerificationExceptionOnFailure()
|
||||
{
|
||||
final OAuth2AuthorizedClientManager authClientManager = mock(OAuth2AuthorizedClientManager.class);
|
||||
when(authClientManager.authorize(any())).thenThrow(new RuntimeException("Expected"));
|
||||
|
||||
final SpringOAuth2Client client = new SpringOAuth2Client(() -> authClientManager);
|
||||
|
||||
assertThatExceptionOfType(CredentialsVerificationException.class)
|
||||
.isThrownBy(() -> client.verifyCredentials(USER_NAME, PASSWORD))
|
||||
.havingCause().withNoCause().withMessage("Expected");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldAvoidCreatingMultipleInstanceOfOAuth2AuthorizedClientManager()
|
||||
{
|
||||
final OAuth2AuthorizedClient authorizedClient = mock(OAuth2AuthorizedClient.class);
|
||||
when(authorizedClient.getAccessToken()).thenReturn(mock(OAuth2AccessToken.class));
|
||||
final OAuth2AuthorizedClientManager authClientManager = mock(OAuth2AuthorizedClientManager.class);
|
||||
when(authClientManager.authorize(any())).thenReturn(authorizedClient);
|
||||
final Supplier<OAuth2AuthorizedClientManager> supplier = mock(Supplier.class);
|
||||
when(supplier.get()).thenReturn(authClientManager);
|
||||
|
||||
final SpringOAuth2Client client = new SpringOAuth2Client(supplier);
|
||||
|
||||
client.verifyCredentials(USER_NAME, PASSWORD);
|
||||
client.verifyCredentials(USER_NAME, PASSWORD);
|
||||
client.verifyCredentials(USER_NAME, PASSWORD);
|
||||
verify(supplier, times(1)).get();
|
||||
verify(authClientManager, times(3)).authorize(any());
|
||||
}
|
||||
|
||||
private Supplier<OAuth2AuthorizedClientManager> faultySupplier(int numberOfInitialFailures, OAuth2AuthorizedClientManager authClientManager)
|
||||
{
|
||||
final int[] counter = new int[]{0};
|
||||
return () -> {
|
||||
if (counter[0]++ < numberOfInitialFailures)
|
||||
{
|
||||
throw new RuntimeException("Expected failure #" + counter[0]);
|
||||
}
|
||||
return authClientManager;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2023 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.tagging;
|
||||
|
||||
import static org.alfresco.service.cmr.repository.StoreRef.STORE_REF_WORKSPACE_SPACESSTORE;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.catchThrowable;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.policy.PolicyComponent;
|
||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||
import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.StoreRef;
|
||||
import org.alfresco.service.cmr.search.CategoryService;
|
||||
import org.alfresco.util.Pair;
|
||||
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;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class TaggingServiceImplUnitTest
|
||||
{
|
||||
private static final String TAG_ID = "tag-node-id";
|
||||
private static final String TAG_NAME = "tag-dummy-name";
|
||||
private static final NodeRef TAG_NODE_REF = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID);
|
||||
|
||||
@Mock
|
||||
private CategoryService categoryServiceMock;
|
||||
@Mock
|
||||
private PolicyComponent policyComponentMock;
|
||||
|
||||
@InjectMocks
|
||||
private TaggingServiceImpl taggingService;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
taggingService.init();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateTags()
|
||||
{
|
||||
final ChildAssociationRef tagAssociationMock = mock(ChildAssociationRef.class);
|
||||
given(categoryServiceMock.getRootCategories(any(), any(), any(String.class), eq(true))).willReturn(List.of(tagAssociationMock));
|
||||
given(tagAssociationMock.getChildRef()).willReturn(TAG_NODE_REF);
|
||||
|
||||
//when
|
||||
final List<Pair<String, NodeRef>> actualTagPairs = taggingService.createTags(STORE_REF_WORKSPACE_SPACESSTORE, List.of(TAG_NAME));
|
||||
|
||||
then(categoryServiceMock).should().getRootCategories(STORE_REF_WORKSPACE_SPACESSTORE, ContentModel.ASPECT_TAGGABLE, TAG_NAME, false);
|
||||
then(categoryServiceMock).should().getRootCategories(STORE_REF_WORKSPACE_SPACESSTORE, ContentModel.ASPECT_TAGGABLE, TAG_NAME, true);
|
||||
then(categoryServiceMock).shouldHaveNoMoreInteractions();
|
||||
List<Pair<String, NodeRef>> expectedTagPairs = List.of(new Pair<>(TAG_NAME, TAG_NODE_REF));
|
||||
assertThat(actualTagPairs)
|
||||
.isNotNull()
|
||||
.isEqualTo(expectedTagPairs);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateTags_whileTagAlreadyExists()
|
||||
{
|
||||
given(categoryServiceMock.getRootCategories(any(), any(), any(String.class), eq(false))).willThrow(new DuplicateChildNodeNameException(null, null, null, null));
|
||||
|
||||
//when
|
||||
final Throwable actualException = catchThrowable(() -> taggingService.createTags(STORE_REF_WORKSPACE_SPACESSTORE, List.of(TAG_NAME)));
|
||||
|
||||
then(categoryServiceMock).should().getRootCategories(STORE_REF_WORKSPACE_SPACESSTORE, ContentModel.ASPECT_TAGGABLE, TAG_NAME, false);
|
||||
then(categoryServiceMock).shouldHaveNoMoreInteractions();
|
||||
assertThat(actualException).isInstanceOf(DuplicateChildNodeNameException.class);
|
||||
}
|
||||
}
|
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* 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.urlshortening;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* @deprecated as BitlyUrlShortenerImpl is no longer used in the core repository code.
|
||||
*/
|
||||
@Deprecated
|
||||
public class BitlyUrlShortenerTest extends TestCase
|
||||
{
|
||||
private BitlyUrlShortenerImpl shortener;
|
||||
|
||||
public void testShorten()
|
||||
{
|
||||
String url = "http://www.alfresco.com/";
|
||||
String shortUrl = shortener.shortenUrl(url);
|
||||
assertNotNull(shortUrl);
|
||||
assertFalse(shortUrl.isEmpty());
|
||||
assertFalse(url.equals(shortUrl));
|
||||
assertTrue(shortUrl.length() < url.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected void setUp() throws Exception
|
||||
{
|
||||
this.shortener = new BitlyUrlShortenerImpl();;
|
||||
shortener.setApiKey("R_ca15c6c89e9b25ccd170bafd209a0d4f");
|
||||
shortener.setUrlLength(20);
|
||||
shortener.setUsername("brianalfresco");
|
||||
}
|
||||
}
|
@@ -66,3 +66,5 @@ encryption.cipherAlgorithm=DESede/CBC/PKCS5Padding
|
||||
encryption.keystore.type=JCEKS
|
||||
encryption.keystore.backup.type=JCEKS
|
||||
|
||||
# For CI override the default hashing algorithm for password storage to save build time.
|
||||
system.preferred.password.encoding=sha256
|
||||
|
21
scripts/prepare_buildx.sh
Executable file
21
scripts/prepare_buildx.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
BUILDER_NAME="${1}"
|
||||
TARGET_REGISTRY="${2}"
|
||||
TARGET_IMAGE="${3}"
|
||||
IMAGE_TAG="${4}"
|
||||
|
||||
#Create a `docker-container` builder with host networking and required flags (quay.io)
|
||||
docker --config target/docker/"${TARGET_REGISTRY}"/"${TARGET_IMAGE}"/"${IMAGE_TAG}"/docker \
|
||||
buildx create --use --name "${BUILDER_NAME}" --driver-opt network=host \
|
||||
--buildkitd-flags '--allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host'
|
||||
|
||||
#Create a `docker-container` builder with host networking and required flags (docker.io)
|
||||
docker --config target/docker/"${TARGET_IMAGE}"/"${IMAGE_TAG}"/docker \
|
||||
buildx create --use --name "${BUILDER_NAME}" --driver-opt network=host \
|
||||
--buildkitd-flags '--allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host'
|
||||
|
||||
#Create a `docker-container` builder with host networking and required flags (local registry)
|
||||
docker --config target/docker/127.0.0.1/5000/"${TARGET_IMAGE}"/"${IMAGE_TAG}"/docker \
|
||||
buildx create --use --name "${BUILDER_NAME}" --driver-opt network=host \
|
||||
--buildkitd-flags '--allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host'
|
Reference in New Issue
Block a user