Compare commits

..
Author SHA1 Message Date
dcerbo 01e224902b Revert "SEARCH-2878 Update the supported version for SearchService to 2.0.2 (#1421)" [ci skip]
This reverts commit e798790b
2021-07-15 14:57:11 +02:00
Davide e798790b90 SEARCH-2878 Update the supported version for SearchService to 2.0.2 (#1421) 2021-07-15 09:56:58 +02:00
alandavis a064a54630 Update upstream version to 9.29 2021-07-15 01:14:11 +01:00
alandavis 1fb5591ff3 Create HF branch for 7.0.0 [skip ci] 2021-07-14 16:34:58 +01:00
Travis CI User c704d0e333 [maven-release-plugin][skip ci] prepare for next development iteration 2021-07-14 13:58:22 +00:00
Travis CI User 0710c26a7a [maven-release-plugin][skip ci] prepare release 7.0.1-A7 2021-07-14 13:58:16 +00:00
alandavis ede6928445 [release][publish] 7.0.1-A7
[skip tests] already run for A6
2021-07-14 14:21:37 +01:00
Travis CI User be9bd05211 [maven-release-plugin][skip ci] prepare for next development iteration 2021-07-12 17:14:17 +00:00
Travis CI User 2398ae671f [maven-release-plugin][skip ci] prepare release 7.0.1-A6 2021-07-12 17:14:12 +00:00
Alex Mukha 322ebbe08f [release] 7.0.1-A6 (second attempt) 2021-07-12 17:36:42 +01:00
Alex Mukha 94f1f8343a Bump community repo to 9.28 2021-07-12 17:08:37 +01:00
Alex Mukha 7a112f8c89 [release] 7.0.1-A6 2021-07-12 15:16:35 +01:00
Davide d99d399ef1 SEARCH-2878 Update the SearchService version to 2.0.2-RC2(#1417) 2021-07-09 22:15:34 +01:00
alandavis 4215b634ba Dev env was not referencing the correct parent [skip ci] 2021-07-09 18:09:33 +01:00
alahariyaandalahariya 9aaa936bdf bump-share-7.0.1 (#1416)
Co-authored-by: alahariya <Anjali.Lahariya@contractors.onbase.com>
2021-07-09 20:48:43 +05:30
Travis CI User 293a25f76e [maven-release-plugin][skip ci] prepare for next development iteration 2021-06-29 15:54:13 +00:00
Travis CI User 4bfd2e145c [maven-release-plugin][skip ci] prepare release 7.0.1-A5 2021-06-29 15:54:07 +00:00
alandavis 39b923bbd1 [release] 7.0.1-A5 2021-06-29 15:50:27 +01:00
alandavis 78baf18136 [release] 7.0.1-A5 2021-06-29 15:49:45 +01:00
Travis CI User 4aca5c0ec0 [maven-release-plugin][skip ci] prepare for next development iteration 2021-06-25 23:54:09 +00:00
Travis CI User 13f9df4bcc [maven-release-plugin][skip ci] prepare release 7.0.1-A4 2021-06-25 23:54:03 +00:00
alandavis 44f1b9789d [release] 7.0.1-A4 2021-06-25 23:47:25 +01:00
alandavis fecee74661 Update upstream version to 9.22 2021-06-25 23:19:50 +01:00
alahariyaandalahariya a7cd016d2c bump-share-7.0.1-A4 (#1400)
Co-authored-by: alahariya <Anjali.Lahariya@contractors.onbase.com>
2021-06-25 19:19:17 +05:30
Travis CI User b671931cb8 [maven-release-plugin][skip ci] prepare for next development iteration 2021-06-12 10:07:38 +00:00
Travis CI User ed3cdc0c28 [maven-release-plugin][skip ci] prepare release 7.0.1-A1 2021-06-12 10:07:33 +00:00
alandavis 03eb064759 [release] 7.0.1-A1 2021-06-12 10:03:03 +01:00
alandavis 6030c9a0d4 Update upstream version to 9.16 2021-06-11 17:48:58 +01:00
alandavis 45b70b3271 Fix ATS versions [skip ci] 2021-06-11 17:42:27 +01:00
alandavis f0fd98873a Update upstream version to 9.8 2021-05-11 15:26:05 +01:00
alandavis 46911f54b6 Prep the RELEASE_VERSION for the first release [skip ci] 2021-05-11 13:56:41 +01:00
alandavis c7dd3e5b0a Create release/7.0.N branch for 7.0.1
* Still need to cherry pick commits already merged to 6.2.N
2021-05-10 18:41:53 +01:00
alandavis 66022dad03 Update upstream version to 8.424 (the version used in enterprise ACS 7.0.0.1)
[skip ci]
2021-04-12 14:49:23 +01:00
Cezar.Leahu 59299d54d4 [publish] [skip tests] Identify release build number in the publish script 2021-03-17 01:22:59 +02:00
Cezar.Leahu 7832d7b5be [publish] [skip tests] Identify release build number in the publish script 2021-03-17 01:10:03 +02:00
Cezar.Leahu 08fee7cfd9 [publish] [skip tests] Allow the CI *Publish* step on any branch
(cherry picked from commit f5ac695ca6)
2021-03-16 16:19:53 +02:00
Travis CI User 071cc8c2a4 [maven-release-plugin][skip ci] prepare for next development iteration 2021-03-11 14:26:44 +00:00
Travis CI User dc5e1dd83d [maven-release-plugin][skip ci] prepare release 7.0.0 2021-03-11 14:26:40 +00:00
alandavis bc94a0ae7c [release][publish] 7.0.0 2021-03-11 13:15:50 +00:00
126 changed files with 1352 additions and 12337 deletions
+8 -11
View File
@@ -1,11 +1,8 @@
# .* eol=crlf
# *.html eol=crlf
# *.java eol=crlf
# *.txt eol=crlf
# *.css eol=crlf
# *.xml eol=crlf
# *.js eol=crlf
# *.properties eol=crlf
# Shell scripts require LF
*.sh text eol=lf
Dockerfile text eol=lf
# .* eol=crlf
# *.html eol=crlf
# *.java eol=crlf
# *.txt eol=crlf
# *.css eol=crlf
# *.xml eol=crlf
# *.js eol=crlf
# *.properties eol=crlf
-52
View File
@@ -1,52 +0,0 @@
version: 2
registries:
maven-repository-artifacts-alfresco-com-nexus-content-groups-int:
type: maven-repository
url: https://artifacts.alfresco.com/nexus/content/groups/internal
username: ${{secrets.NEXUS_USERNAME}}
password: ${{secrets.NEXUS_PASSWORD}}
updates:
- package-ecosystem: maven
directory: "/"
schedule:
interval: daily
time: "22:00"
timezone: "Europe/London"
# disabling versions updates, when set to 0 only security updates will open
open-pull-requests-limit: 0
ignore:
# Upstream alfresco-community-repo artifacts
- dependency-name: org.alfresco:alfresco-community-repo
- dependency-name: org.alfresco:alfresco-core
- dependency-name: org.alfresco:alfresco-data-model
- dependency-name: org.alfresco:alfresco-repository
- dependency-name: org.alfresco:alfresco-remote-api
- dependency-name: org.alfresco:content-services-community
- dependency-name: org.alfresco:alfresco-community-repo-distribution
- dependency-name: org.alfresco.tas:alfresco-community-repo-webdav-test:tests
- dependency-name: org.alfresco.tas:alfresco-community-repo-restapi-test:tests
- dependency-name: org.alfresco.tas:alfresco-community-repo-integration-test:tests
- dependency-name: org.alfresco.tas:alfresco-community-repo-email-test:tests
- dependency-name: org.alfresco.tas:alfresco-community-repo-cmis-test:tests
# Upstream alfresco-enterprise-share artifacts
- dependency-name: org.alfresco:share:classes
- dependency-name: org.alfresco:alfresco-wcmqs-web:classes
- dependency-name: org.alfresco:alfresco-wcmqs-distribution
- dependency-name: org.alfresco:share
- dependency-name: org.alfresco:alfresco-content-services-share-distribution
- dependency-name: org.alfresco:alfresco-share-services
# Others
- dependency-name: org.apache.maven.plugins:maven-war-plugin
versions:
- ">= 3.a, < 4"
registries:
- maven-repository-artifacts-alfresco-com-nexus-content-groups-int
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
groups:
github-actions:
patterns:
- "*"
-350
View File
@@ -1,350 +0,0 @@
name: ACS Community Packaging CI
on:
pull_request:
branches:
- master
- release/**
- feature/**
- fix/**
push:
branches:
- master
- release/**
- feature/**
- fix/**
workflow_dispatch:
env:
JAVA_VERSION: "21"
GIT_USERNAME: ${{ secrets.BOT_GITHUB_USERNAME }}
GIT_EMAIL: ${{ secrets.BOT_GITHUB_EMAIL }}
GIT_PASSWORD: ${{ secrets.BOT_GITHUB_TOKEN }}
DOCKERHUB_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
DOCKERHUB_USERNAME: ${{ secrets.DOCKER_USERNAME }}
GITHUB_ACTIONS_DEPLOY_TIMEOUT: 60
MAVEN_PASSWORD: ${{ secrets.NEXUS_PASSWORD }}
MAVEN_USERNAME: ${{ secrets.NEXUS_USERNAME }}
QUAY_PASSWORD: ${{ secrets.QUAY_PASSWORD }}
QUAY_USERNAME: ${{ secrets.QUAY_USERNAME }}
TAS_ENVIRONMENT: ./tests/environment
TAS_SCRIPTS: ../alfresco-community-repo/packaging/tests/scripts
# Release version has to start with real version (23.2.0-....) for the docker image to build successfully.
RELEASE_VERSION: 26.2.0-A.25
DEVELOPMENT_VERSION: 26.2.0-A.26-SNAPSHOT
jobs:
precommit:
name: Pre-Commit checks
runs-on: ubuntu-latest
if: >
!contains(github.event.head_commit.message, '[skip tests]')
steps:
- uses: actions/checkout@v4
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
with:
java-version: ${{ env.JAVA_VERSION }}
- uses: Alfresco/alfresco-build-tools/.github/actions/pre-commit@v8.16.0
tas_tests:
name: ${{ matrix.testSuite }} TAS tests
runs-on: ubuntu-latest
if: ${{ !contains(github.event.head_commit.message, '[skip tas]') && !contains(github.event.head_commit.message, '[skip tests]') }}
strategy:
fail-fast: false
matrix:
include:
- testSuite: REST API part1
pom: tests/tas-restapi/pom.xml
profiles: all-tas-tests,run-restapi-part1
compose-file: docker-compose-minimal+transforms.yml
- testSuite: REST API part2
pom: tests/tas-restapi/pom.xml
profiles: all-tas-tests,run-restapi-part2
compose-file: docker-compose-minimal+transforms.yml
- testSuite: REST API part3
pom: tests/tas-restapi/pom.xml
profiles: all-tas-tests,run-restapi-part3
compose-file: docker-compose-minimal+transforms.yml
- testSuite: CMIS (BROWSER binding)
pom: tests/tas-cmis/pom.xml
profiles: all-tas-tests,run-cmis-browser
compose-file: docker-compose-minimal+transforms.yml
- testSuite: CMIS (ATOM binding)
pom: tests/tas-cmis/pom.xml
profiles: all-tas-tests,run-cmis-atom
compose-file: docker-compose-minimal+transforms.yml
- testSuite: CMIS (WEBSERVICES binding)
pom: tests/tas-cmis/pom.xml
profiles: all-tas-tests,run-cmis-webservices
compose-file: docker-compose-minimal+transforms.yml
- testSuite: Email
pom: tests/tas-email/pom.xml
profiles: all-tas-tests
compose-file: docker-compose-minimal.yml
- testSuite: WebDAV
pom: tests/tas-webdav/pom.xml
profiles: all-tas-tests
compose-file: docker-compose-minimal.yml
- testSuite: Integration
pom: tests/tas-integration/pom.xml
profiles: all-tas-tests
compose-file: docker-compose-minimal.yml
steps:
- uses: actions/checkout@v4
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
with:
java-version: ${{ env.JAVA_VERSION }}
- name: "Init"
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
run: |
bash ./scripts/ci/init.sh
bash ./scripts/ci/build.sh
- name: "Setup the environment"
run: |
${TAS_SCRIPTS}/start-compose.sh ${TAS_ENVIRONMENT}/${{ matrix.compose-file }}
${TAS_SCRIPTS}/wait-for-alfresco-start.sh "http://localhost:8082/alfresco"
- name: "Test"
run: mvn -B install -ntp -f ${{ matrix.pom }} -P${{ matrix.profiles }} -Denvironment=default -DrunBugs=false
- name: "Clean Maven cache"
run: bash ./scripts/ci/cleanup_cache.sh
tas_tests_search_api:
name: ${{ matrix.testSuite }} | TAS tests (Search API)
runs-on: ubuntu-latest
if: >
(github.ref_name == 'master' || startsWith(github.ref_name, 'release/') || github.event_name == 'pull_request') &&
!contains(github.event.head_commit.message, '[skip search]') &&
!contains(github.event.head_commit.message, '[skip tests]')
strategy:
fail-fast: false
matrix:
include:
- testSuite: Elasticsearch postgreSQL
profiles: all-tas-tests,elastic
search-engine-type: elasticsearch
db-type: postgresql
- testSuite: Elasticsearch MySQL
profiles: all-tas-tests,elastic
search-engine-type: elasticsearch
db-type: mysql
- testSuite: Elasticsearch Maria DB 11.8
profiles: all-tas-tests,elastic
search-engine-type: elasticsearch
db-type: mariadb
- testSuite: Opensearch
profiles: all-tas-tests,elastic
search-engine-type: opensearch
db-type: postgresql
- testSuite: Elasticsearch Basic Auth postgreSQL
profiles: all-tas-tests,elastic-basic-auth
search-engine-type: elasticsearch
db-type: postgresql
- testSuite: Elasticsearch Basic Auth MySQL
profiles: all-tas-tests,elastic-basic-auth
search-engine-type: elasticsearch
db-type: mysql
- testSuite: Elasticsearch Basic Auth MariaDB 11.8
profiles: all-tas-tests,elastic-basic-auth
search-engine-type: elasticsearch
db-type: mariadb
- testSuite: Opensearch Basic Auth
profiles: all-tas-tests,elastic-basic-auth
search-engine-type: opensearch
db-type: postgresql
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v12.4.3
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v12.4.3
with:
java-version: ${{ env.JAVA_VERSION }}
- name: "Build"
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
run: |
bash ./scripts/ci/init.sh
bash ./scripts/ci/build.sh
- name: "Run tests"
id: tests
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
run: |
set -a
source ${TAS_ENVIRONMENT}/.env
set +a
BATCH_INDEXING_TAG="${BATCH_INDEXING_TAG//[[:space:]]/}"
mvn -B -ntp -q install -N -f tests/pom.xml
mvn -B -ntp -q install -DskipTests -f tests/testcontainers-env/pom.xml
mvn -B install -ntp -f tests/tas-elasticsearch/pom.xml -P${{ matrix.profiles }} -Denvironment=default -DrunBugs=false "-Dsearch.engine.type=${{ matrix.search-engine-type }}" "-Ddatabase.type=${{ matrix.db-type }}" -Dindeximage="alfresco/alfresco-elasticsearch-batch-indexing:${BATCH_INDEXING_TAG}" -Drepoimage="alfresco-repository-databases:latest"
- name: "Dump all Docker containers logs"
uses: Alfresco/alfresco-build-tools/.github/actions/docker-dump-containers-logs@v12.4.3
if: failure() && steps.tests.outcome == 'failure'
- name: "Clean Maven cache"
run: bash ./scripts/ci/cleanup_cache.sh
community_zip_tests:
name: Community Distribution Zip content tests
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[skip tests]')"
steps:
- uses: actions/checkout@v4
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
with:
java-version: ${{ env.JAVA_VERSION }}
- name: "Init"
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
run: |
bash ./scripts/ci/init.sh
bash ./scripts/ci/build.sh
- name: "Build"
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
run: mvn -B -ntp -V clean install -Pags -DskipTests -Dmaven.javadoc.skip=true
- name: "Test"
run: mvn -B install -ntp -f tests/tas-distribution-zip/pom.xml -Prun-distribution-zip-contents-check -DrunBugs=false
- name: "Clean Maven cache"
run: bash ./scripts/ci/cleanup_cache.sh
community_test_tomcat_deployment:
name: "Test Tomcat deployment"
runs-on: ubuntu-latest
if: >
!contains(github.event.head_commit.message, '[skip tests]')
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
with:
java-version: ${{ env.JAVA_VERSION }}
- name: "Build"
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
run: |
bash ./scripts/ci/init.sh
bash ./scripts/ci/build.sh
- name: "Set up the environment"
run: |
${TAS_SCRIPTS}/start-compose.sh ./dev/docker-compose.yml
${TAS_SCRIPTS}/wait-for-alfresco-start.sh "http://localhost:8983/solr"
- name: "Run tomcat, verify it starts"
id: tests
run: |
(mvn clean install -Prun -rf dev &)
${TAS_SCRIPTS}/wait-for-alfresco-start.sh "http://localhost:8080/alfresco/"
echo "Test Alfresco Repository availability"
curl -Ls -o /dev/null --fail "http://localhost:8080/alfresco"
echo "Test Share availability"
curl -Ls -o /dev/null --fail "http://localhost:8080/share"
docker_latest:
name: Update latest Docker images
runs-on: ubuntu-latest
needs: [tas_tests, community_zip_tests]
if: >
!(failure() || cancelled()) &&
!contains(github.event.head_commit.message, '[skip docker_latest]') &&
(github.ref_name == 'master' || startsWith(github.ref_name, 'release/')) &&
github.event_name != 'pull_request'
services:
registry:
image: registry:2
ports:
- 5000:5000
steps:
- uses: actions/checkout@v4
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
with:
java-version: ${{ env.JAVA_VERSION }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: "Init"
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
run: |
bash ./scripts/ci/init.sh
bash ./scripts/ci/build.sh -m
- name: "Push Docker images"
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
run: mvn -B -ntp -V clean install -DskipTests -Dmaven.javadoc.skip=true -Pags -Ppush-docker-images
- name: "Clean Maven cache"
run: bash ./scripts/ci/cleanup_cache.sh
release:
name: Release and Copy to S3 Staging Bucket
runs-on: ubuntu-latest
needs: [docker_latest]
if: >
!(failure() || cancelled()) &&
contains(github.event.head_commit.message, '[release]') &&
(github.ref_name == 'master' || startsWith(github.ref_name, 'release/')) &&
github.event_name != 'pull_request'
services:
registry:
image: registry:2
ports:
- 5000:5000
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
with:
java-version: ${{ env.JAVA_VERSION }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: "Init"
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
run: |
bash ./scripts/ci/init.sh
bash ./scripts/ci/build.sh -m
- name: "Release"
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
run: |
bash scripts/ci/verify_release_tag.sh
bash scripts/ci/maven_release.sh
source scripts/ci/prepare_staging_deploy.sh
- name: "Clean Maven cache"
run: bash ./scripts/ci/cleanup_cache.sh
- name: "Configure AWS credentials"
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_S3_STAGING_ACCESS_KEY }}
aws-secret-access-key: ${{ secrets.AWS_S3_STAGING_SECRET_KEY }}
aws-region: eu-west-1
- name: "Deploy to S3 Staging Bucket"
run: |
aws s3 cp --acl private --recursive ./deploy_dir s3://alfresco-artefacts-staging/alfresco-content-services-community/release/${BRANCH_NAME}/${BUILD_NUMBER}
aws s3 cp --acl private --recursive ./deploy_dir_ags s3://alfresco-artefacts-staging/community/RM/${RELEASE_VERSION}
echo "Finished release and deployed to https://s3.console.aws.amazon.com/s3/buckets/alfresco-artefacts-staging/alfresco-content-services-community/release/${BRANCH_NAME}/${BUILD_NUMBER}"
publish:
name: Copy to S3 Release Bucket
runs-on: ubuntu-latest
needs: [release]
if: >
!(failure() || cancelled()) &&
contains(github.event.head_commit.message, '[publish]') &&
(github.ref_name == 'master' || startsWith(github.ref_name, 'release/')) &&
github.event_name != 'pull_request'
steps:
- uses: actions/checkout@v4
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
- name: "Configure AWS credentials"
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_S3_RELEASE_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_S3_RELEASE_SECRET_ACCESS_KEY }}
aws-region: eu-west-1
- name: "Deploy to S3 Release Bucket"
run: |
bash scripts/ci/copy_to_release_bucket.sh
bash scripts/ci/copy_ags_to_release_bucket.sh
-42
View File
@@ -1,42 +0,0 @@
name: Pre-Commit formatter
on:
push:
branches:
- precommit/**
env:
JAVA_VERSION: "21"
MAVEN_PASSWORD: ${{ secrets.NEXUS_PASSWORD }}
MAVEN_USERNAME: ${{ secrets.NEXUS_USERNAME }}
jobs:
format-code:
name: "Reformat code"
runs-on: ubuntu-latest
if: contains(github.event.head_commit.message, '[reformat code]')
steps:
- uses: actions/checkout@v4
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
with:
java-version: ${{ env.JAVA_VERSION }}
- name: Set up Python ${{ inputs.python-version }}
uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
with:
python-version: "3.9"
- uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
continue-on-error: true
with:
extra_args: --all-files
- name: Update secrets baseline
run: pip install detect-secrets && detect-secrets scan --baseline .secrets.baseline
- uses: Alfresco/alfresco-build-tools/.github/actions/git-commit-changes@v8.16.0
with:
username: ${{ secrets.BOT_GITHUB_USERNAME }}
add-options: -u
commit-message: "Apply Pre-Commit code formatting"
skip-if-no-changes: true
- name: Push changes
run: git push
+7 -7
View File
@@ -1,8 +1,5 @@
*.class
war/overlays
alfresco/*
# Eclipse
.classpath
.settings
@@ -13,7 +10,7 @@ alfresco/*
*.iml
*.iws
#VScode
#VSCode
/.vscode
# Mac
@@ -24,6 +21,7 @@ target
*.log
*.log.*
# Package Files #
*.jar
*.war
@@ -32,10 +30,12 @@ target
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
# Alfresco
alf_data/
helm/alfresco-content-services-community/charts/*
helm/alfresco-content-services-community/requirements.lock
helm/alfresco-content-services-community*.tgz
# Travis deployment folders
deploy_dir
deploy_dir_ags
# Travis deployment folder
deploy_dir
-14
View File
@@ -1,14 +0,0 @@
repos:
- repo: https://github.com/Yelp/detect-secrets
rev: v1.5.0
hooks:
- id: detect-secrets
args: ["--baseline", ".secrets.baseline"]
- repo: local
hooks:
- id: check-format-and-headers
name: Check format and headers and fix if necessary
entry: ./scripts/hooks/check-format-and-headers.sh
language: script
files: ".*.java"
pass_filenames: false
-292
View File
@@ -1,292 +0,0 @@
{
"version": "1.5.0",
"plugins_used": [
{
"name": "ArtifactoryDetector"
},
{
"name": "AWSKeyDetector"
},
{
"name": "AzureStorageKeyDetector"
},
{
"name": "Base64HighEntropyString",
"limit": 4.5
},
{
"name": "BasicAuthDetector"
},
{
"name": "CloudantDetector"
},
{
"name": "DiscordBotTokenDetector"
},
{
"name": "GitHubTokenDetector"
},
{
"name": "GitLabTokenDetector"
},
{
"name": "HexHighEntropyString",
"limit": 3.0
},
{
"name": "IbmCloudIamDetector"
},
{
"name": "IbmCosHmacDetector"
},
{
"name": "IPPublicDetector"
},
{
"name": "JwtTokenDetector"
},
{
"name": "KeywordDetector",
"keyword_exclude": ""
},
{
"name": "MailchimpDetector"
},
{
"name": "NpmDetector"
},
{
"name": "OpenAIDetector"
},
{
"name": "PrivateKeyDetector"
},
{
"name": "PypiTokenDetector"
},
{
"name": "SendGridDetector"
},
{
"name": "SlackDetector"
},
{
"name": "SoftlayerDetector"
},
{
"name": "SquareOAuthDetector"
},
{
"name": "StripeDetector"
},
{
"name": "TelegramBotTokenDetector"
},
{
"name": "TwilioKeyDetector"
}
],
"filters_used": [
{
"path": "detect_secrets.filters.allowlist.is_line_allowlisted"
},
{
"path": "detect_secrets.filters.common.is_baseline_file",
"filename": ".secrets.baseline"
},
{
"path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies",
"min_level": 2
},
{
"path": "detect_secrets.filters.heuristic.is_indirect_reference"
},
{
"path": "detect_secrets.filters.heuristic.is_likely_id_string"
},
{
"path": "detect_secrets.filters.heuristic.is_lock_file"
},
{
"path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string"
},
{
"path": "detect_secrets.filters.heuristic.is_potential_uuid"
},
{
"path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign"
},
{
"path": "detect_secrets.filters.heuristic.is_sequential_string"
},
{
"path": "detect_secrets.filters.heuristic.is_swagger_file"
},
{
"path": "detect_secrets.filters.heuristic.is_templated_secret"
}
],
"results": {
"dev/dev-tomcat/src/main/tomcat/shared/classes/alfresco-global.properties": [
{
"type": "Secret Keyword",
"filename": "dev/dev-tomcat/src/main/tomcat/shared/classes/alfresco-global.properties",
"hashed_secret": "a4a747bd4ba5e3a5049cad116881867c71fb625b",
"is_verified": false,
"line_number": 8
}
],
"dev/dev-tomcat/src/main/tomcat/tomcat-users.xml": [
{
"type": "Secret Keyword",
"filename": "dev/dev-tomcat/src/main/tomcat/tomcat-users.xml",
"hashed_secret": "2be88ca4242c76e8253ac62474851065032d6833",
"is_verified": false,
"line_number": 19
}
],
"dev/docker-compose.yml": [
{
"type": "Secret Keyword",
"filename": "dev/docker-compose.yml",
"hashed_secret": "d1ebbc78705ad98aa2c8fb357d2e30c8f001673a",
"is_verified": false,
"line_number": 41
}
],
"tests/environment/docker-compose-minimal+transforms.yml": [
{
"type": "Secret Keyword",
"filename": "tests/environment/docker-compose-minimal+transforms.yml",
"hashed_secret": "9e3d103f7aa5f4f778cf752087dfceeba15d4fef",
"is_verified": false,
"line_number": 13
},
{
"type": "Secret Keyword",
"filename": "tests/environment/docker-compose-minimal+transforms.yml",
"hashed_secret": "92cda941553ea9ba9b39716c79a46b08a740613c",
"is_verified": false,
"line_number": 23
},
{
"type": "Secret Keyword",
"filename": "tests/environment/docker-compose-minimal+transforms.yml",
"hashed_secret": "d1ebbc78705ad98aa2c8fb357d2e30c8f001673a",
"is_verified": false,
"line_number": 76
}
],
"tests/environment/docker-compose-minimal.yml": [
{
"type": "Secret Keyword",
"filename": "tests/environment/docker-compose-minimal.yml",
"hashed_secret": "9e3d103f7aa5f4f778cf752087dfceeba15d4fef",
"is_verified": false,
"line_number": 13
},
{
"type": "Secret Keyword",
"filename": "tests/environment/docker-compose-minimal.yml",
"hashed_secret": "92cda941553ea9ba9b39716c79a46b08a740613c",
"is_verified": false,
"line_number": 23
},
{
"type": "Secret Keyword",
"filename": "tests/environment/docker-compose-minimal.yml",
"hashed_secret": "d1ebbc78705ad98aa2c8fb357d2e30c8f001673a",
"is_verified": false,
"line_number": 79
}
],
"tests/tas-cmis/src/test/resources/default.properties": [
{
"type": "Secret Keyword",
"filename": "tests/tas-cmis/src/test/resources/default.properties",
"hashed_secret": "d033e22ae348aeb5660fc2140aec35850c4da997",
"is_verified": false,
"line_number": 8
},
{
"type": "Secret Keyword",
"filename": "tests/tas-cmis/src/test/resources/default.properties",
"hashed_secret": "a4a747bd4ba5e3a5049cad116881867c71fb625b",
"is_verified": false,
"line_number": 84
}
],
"tests/tas-email/src/test/resources/default.properties": [
{
"type": "Secret Keyword",
"filename": "tests/tas-email/src/test/resources/default.properties",
"hashed_secret": "d033e22ae348aeb5660fc2140aec35850c4da997",
"is_verified": false,
"line_number": 8
},
{
"type": "Secret Keyword",
"filename": "tests/tas-email/src/test/resources/default.properties",
"hashed_secret": "43b7ed74f99cf021109506832a48aa75435b817d",
"is_verified": false,
"line_number": 35
},
{
"type": "Secret Keyword",
"filename": "tests/tas-email/src/test/resources/default.properties",
"hashed_secret": "a4a747bd4ba5e3a5049cad116881867c71fb625b",
"is_verified": false,
"line_number": 97
}
],
"tests/tas-integration/src/test/resources/default.properties": [
{
"type": "Secret Keyword",
"filename": "tests/tas-integration/src/test/resources/default.properties",
"hashed_secret": "d033e22ae348aeb5660fc2140aec35850c4da997",
"is_verified": false,
"line_number": 8
},
{
"type": "Secret Keyword",
"filename": "tests/tas-integration/src/test/resources/default.properties",
"hashed_secret": "a4a747bd4ba5e3a5049cad116881867c71fb625b",
"is_verified": false,
"line_number": 118
}
],
"tests/tas-restapi/src/test/resources/default.properties": [
{
"type": "Secret Keyword",
"filename": "tests/tas-restapi/src/test/resources/default.properties",
"hashed_secret": "d033e22ae348aeb5660fc2140aec35850c4da997",
"is_verified": false,
"line_number": 21
},
{
"type": "Secret Keyword",
"filename": "tests/tas-restapi/src/test/resources/default.properties",
"hashed_secret": "a4a747bd4ba5e3a5049cad116881867c71fb625b",
"is_verified": false,
"line_number": 87
}
],
"tests/tas-webdav/src/test/resources/default.properties": [
{
"type": "Secret Keyword",
"filename": "tests/tas-webdav/src/test/resources/default.properties",
"hashed_secret": "d033e22ae348aeb5660fc2140aec35850c4da997",
"is_verified": false,
"line_number": 8
},
{
"type": "Secret Keyword",
"filename": "tests/tas-webdav/src/test/resources/default.properties",
"hashed_secret": "a4a747bd4ba5e3a5049cad116881867c71fb625b",
"is_verified": false,
"line_number": 73
}
]
},
"generated_at": "2025-03-18T15:20:48Z"
}
@@ -25,6 +25,10 @@
<url>https://artifacts.alfresco.com/nexus/content/groups/public</url>
</pluginRepository>
</pluginRepositories>
<properties>
<!-- WhiteSource token -->
<org.whitesource.orgToken>${env.WHITESOURCE_API_KEY}</org.whitesource.orgToken>
</properties>
</profile>
</profiles>
+152
View File
@@ -0,0 +1,152 @@
---
dist: xenial
sudo: required
language: java
jdk: openjdk11
services:
- docker
git:
depth: false
quiet: true
cache:
directories:
- ${HOME}/.m2/repository
# the cache can grow constantly
before_cache: bash scripts/travis/cleanup_cache.sh
branches:
only:
- master
- /release\/.*/
- /feature\/.*/
- /fix\/.*/
env:
global:
- TAS_SCRIPTS=../alfresco-community-repo/packaging/tests/scripts
- TAS_ENVIRONMENT=./tests/environment
# Release version has to start with real version (7.0.1-....) for the docker image to build successfully.
- RELEASE_VERSION=7.0.1.0
- DEVELOPMENT_VERSION=7.0.1-SNAPSHOT
stages:
- name: test
if: commit_message !~ /\[skip tests\]/
- name: docker_latest
if: fork = false AND type != pull_request AND branch = master
- name: release
if: commit_message =~ /\[release\]/ AND fork = false AND type != pull_request AND (branch = master OR branch =~ /release\/.*/)
- name: publish
if: commit_message =~ /\[publish\]/ AND fork = false AND type != pull_request AND (branch = master OR branch =~ /release\/.*/)
before_install: travis_retry bash scripts/travis/init.sh
install: travis_retry travis_wait 40 bash scripts/travis/build.sh
jobs:
include:
- name: "REST API TAS tests part1"
before_script:
- ${TAS_SCRIPTS}/start-compose.sh ${TAS_ENVIRONMENT}/docker-compose-minimal+transforms.yml
- ${TAS_SCRIPTS}/wait-for-alfresco-start.sh "http://localhost:8082/alfresco"
script:
- travis_wait 60 mvn -B install -f tests/tas-restapi/pom.xml -Pall-tas-tests,run-restapi-part1 -Denvironment=default -DrunBugs=false
- name: "REST API TAS tests part2"
before_script:
- ${TAS_SCRIPTS}/start-compose.sh ${TAS_ENVIRONMENT}/docker-compose-minimal+transforms.yml
- ${TAS_SCRIPTS}/wait-for-alfresco-start.sh "http://localhost:8082/alfresco"
script:
- travis_wait 60 mvn -B install -f tests/tas-restapi/pom.xml -Pall-tas-tests,run-restapi-part2 -Denvironment=default -DrunBugs=false
- name: "REST API TAS tests part3"
before_script:
- ${TAS_SCRIPTS}/start-compose.sh ${TAS_ENVIRONMENT}/docker-compose-minimal+transforms.yml
- ${TAS_SCRIPTS}/wait-for-alfresco-start.sh "http://localhost:8082/alfresco"
script:
- travis_wait 60 mvn -B install -f tests/tas-restapi/pom.xml -Pall-tas-tests,run-restapi-part3 -Denvironment=default -DrunBugs=false
- name: "CMIS TAS tests - BROWSER binding"
before_script:
- ${TAS_SCRIPTS}/start-compose.sh ${TAS_ENVIRONMENT}/docker-compose-minimal+transforms.yml
- ${TAS_SCRIPTS}/wait-for-alfresco-start.sh "http://localhost:8082/alfresco"
script:
- travis_wait 40 mvn -B install -f tests/tas-cmis/pom.xml -Pall-tas-tests,run-cmis-browser -Denvironment=default -DrunBugs=false
- name: "CMIS TAS tests - ATOM binding"
before_script:
- ${TAS_SCRIPTS}/start-compose.sh ${TAS_ENVIRONMENT}/docker-compose-minimal+transforms.yml
- ${TAS_SCRIPTS}/wait-for-alfresco-start.sh "http://localhost:8082/alfresco"
script:
- travis_wait 40 mvn -B install -f tests/tas-cmis/pom.xml -Pall-tas-tests,run-cmis-atom -Denvironment=default -DrunBugs=false
- name: "CMIS TAS tests - WEBSERVICES binding"
before_script:
- ${TAS_SCRIPTS}/start-compose.sh ${TAS_ENVIRONMENT}/docker-compose-minimal+transforms.yml
- ${TAS_SCRIPTS}/wait-for-alfresco-start.sh "http://localhost:8082/alfresco"
script:
- travis_wait 40 mvn -B install -f tests/tas-cmis/pom.xml -Pall-tas-tests,run-cmis-webservices -Denvironment=default -DrunBugs=false
- name: "Email TAS tests"
before_script:
- ${TAS_SCRIPTS}/start-compose.sh ${TAS_ENVIRONMENT}/docker-compose-minimal.yml
- ${TAS_SCRIPTS}/wait-for-alfresco-start.sh "http://localhost:8082/alfresco"
script:
- travis_wait 30 mvn -B install -f tests/tas-email/pom.xml -Pall-tas-tests -Denvironment=default -DrunBugs=false
- name: "WebDAV TAS tests"
before_script:
- ${TAS_SCRIPTS}/start-compose.sh ${TAS_ENVIRONMENT}/docker-compose-minimal.yml
- ${TAS_SCRIPTS}/wait-for-alfresco-start.sh "http://localhost:8082/alfresco"
script:
- travis_wait 20 mvn -B install -f tests/tas-webdav/pom.xml -Pall-tas-tests -Denvironment=default -DrunBugs=false
- name: "Integration TAS tests"
before_script:
- ${TAS_SCRIPTS}/start-compose.sh ${TAS_ENVIRONMENT}/docker-compose-minimal.yml
- ${TAS_SCRIPTS}/wait-for-alfresco-start.sh "http://localhost:8082/alfresco"
script:
- travis_wait 30 mvn -B install -f tests/tas-integration/pom.xml -Pall-tas-tests -Denvironment=default -DrunBugs=false
- name: "Update latest images in quay.io"
stage: docker_latest
script: travis_retry travis_wait 30 mvn -B -V clean install -DskipTests -Dmaven.javadoc.skip=true -Pinternal
- name: "Release and Copy to S3 Staging Bucket"
stage: release
before_script: bash scripts/travis/verify_release_tag.sh
script: travis_wait 60 bash scripts/travis/maven_release.sh
before_deploy:
# Move the final artifacts to a single folder (deploy_dir) to be copied to S3
- mkdir -p deploy_dir
- cp distribution/target/alfresco.war deploy_dir
- cp distribution/target/*-distribution*.zip deploy_dir
deploy:
- provider: s3
access_key_id: "${AWS_STAGING_ACCESS_KEY}"
secret_access_key: "${AWS_STAGING_SECRET_KEY}"
bucket: "alfresco-artefacts-staging"
region: "eu-west-1"
skip_cleanup: true
acl: private
local_dir: "deploy_dir"
upload_dir: "alfresco-content-services-community/release/${TRAVIS_BRANCH}/${TRAVIS_BUILD_NUMBER}"
on:
all_branches: true
after_deploy:
- echo "Finished release and deployed to https://s3.console.aws.amazon.com/s3/buckets/alfresco-artefacts-staging/alfresco-content-services/release/${TRAVIS_BRANCH}/${TRAVIS_BUILD_NUMBER}"
- name: "Copy to S3 Release Bucket"
stage: publish
# Nothing to build/install as we are just copying from S3 buckets
install: skip
script: skip
before_deploy: pip install awscli
deploy:
- provider: script
script: bash scripts/travis/copy_to_release_bucket.sh
on:
all_branches: true
+218
View File
@@ -0,0 +1,218 @@
<h1> Release Notes - Alfresco - Version Community Edition 201911 GA
</h1>
<h2> Bug
</h2>
<ul>
<li>[<a href='https://issues.alfresco.com/jira/browse/MNT-20436'>MNT-20436</a>] - “POST /nodes/{nodeId}/children” RestAPI does not create a node without having a mandatory value object, but it outputs the 201 successful response.
</li>
<li>[<a href='https://issues.alfresco.com/jira/browse/MNT-20714'>MNT-20714</a>] - [HotFix] /nodes/{nodeId}/content REST API fails for content created by a deleted user
</li>
<li>[<a href='https://issues.alfresco.com/jira/browse/MNT-20770'>MNT-20770</a>] - Share non responsive during direct download from S3 if content store selector is also configured
</li>
<li>[<a href='https://issues.alfresco.com/jira/browse/MNT-20863'>MNT-20863</a>] - Changing cm:name with REST API /nodes/{nodeId} does not update Primary Path
</li>
</ul>
<h1> Release Notes - Alfresco - Version Community Edition 201910 EA
</h1>
<h2>
New Features
</h2>
<ul>
<li>
<h4>Custom Transforms and Renditions</h4>
<p>Alfresco Content Services (ACS) provides a number of content
transforms, but also allows custom transforms to be added.
<p>It is now possible to create custom transforms that run in
separate processes known as T-Engines (short for Transformer
Engines). The same engines may be used in Community and
Enterprise Editions. They may be directly connected to the ACS
repository as Local Transforms, but in the Enterprise edition there
is the option to include them as part of the Transform Service
which provides more balanced throughput and better administration
capabilities.
<p>For more information see <a href='https://github.com/Alfresco/acs-packaging/blob/master/docs/custom-transforms-and-renditions.md'>Custom Transforms and Renditions</a>
</li>
<li>
<h4>Open-source Transformation Engines</h4>
<p>The core T-Engine images can now be used in Community
Edition.</p>
<p>T-Engines code has been Open-Sourced and is available on Github:</p>
<ul><a href='https://github.com/Alfresco/alfresco-transform-core'>alfresco/alfresco-transform-core</a></ul>
<p>Images are available on Docker Hub:</p>
<ul><a href='https://hub.docker.com/r/alfresco/alfresco-imagemagick'>alfresco/alfresco-imagemagick</a></ul>
<ul><a href='https://hub.docker.com/r/alfresco/alfresco-pdf-renderer'>alfresco/alfresco-pdf-renderer</a></ul>
<ul><a href='https://hub.docker.com/r/alfresco/alfresco-libreoffice'>alfresco/alfresco-libreoffice</a></ul>
<ul><a href='https://hub.docker.com/r/alfresco/alfresco-tika'>alfresco/alfresco-tika</a></ul>
<ul><a href='https://hub.docker.com/r/alfresco/alfresco-transform-misc'>alfresco/alfresco-transform-misc</a></ul>
</p>
</li>
<li>
<h4>Removal of external executables from docker image</h4>
<p>With the introduction of the new Local Transform Service
in Alfresco Community Edition, the capability of executing
remote transformations on T-Engines was enabled. Because of
this, the external executables (Alfresco-Pdf-renderer, Libreoffice
and Imagemagick) have been removed from the docker container to
facilitate the usage of out-of-process transformations.
</p>
</li>
</ul>
<h2> Bug
</h2>
<ul>
<li>[<a href='https://issues.alfresco.com/jira/browse/ALF-22013'>ALF-22013</a>] - Docker Image for Base Tomcat locale is POSIX
</li>
<li>[<a href='https://issues.alfresco.com/jira/browse/ALF-22060'>ALF-22060</a>] - Reader on the backing store is obtained twice in CachingContentStore
</li>
<li>[<a href='https://issues.alfresco.com/jira/browse/ALF-22056'>ALF-22056</a>] - onCopyCompleteBehaviour not called in order of copy-action
</li>
<li>[<a href='https://issues.alfresco.com/jira/browse/ALF-22073'>ALF-22073</a>] - MailActionExecutor doesn't consider email bodies with a HTML doctype as HTML
</li>
<li>[<a href='https://issues.alfresco.com/jira/browse/ALF-21988'>ALF-21988</a>] - Tab order for number ranges not ok
</li>
<li>[<a href='https://issues.alfresco.com/jira/browse/ALF-22097'>ALF-22097</a>] - T Engine - add source nodeId parameter
</li>
</ul>
<h2> Improvement
</h2>
<ul>
<li>[<a href='https://issues.alfresco.com/jira/browse/REPO-4318'>REPO-4318</a>] - [COMPLETE] Extraction of transformers and metadata extractors
</li>
</ul>
<h2>
<h1> Release Notes - Alfresco - Version Community Edition 201901 GA
</h1>
<h2>
New Features
</h2>
<ul>
<li>
<h4>ActiveMQ:</h4>
Alfresco ActiveMQ Docker images: <a href='https://github.com/Alfresco/alfresco-docker-activemq'>GitHub Repo</a> <a href='https://hub.docker.com/r/alfresco/alfresco-activemq/'>DockerHub Repo</a><p>
</li>
<li>
<h4>Alfresco Benchmark Framework:</h4>
<p>The benchmark framework project provides a way to run highly scalable, easy-to-run Java-based load and benchmark tests on an Alfresco instance.</p>
<p>It comprises the following: <a href='https://github.com/Alfresco/alfresco-bm-manager'>Alfresco BM Manager</a> and Alfresco BM Drivers.</p>
<p>The currently provided drivers are:</p>
<ul>
<li><a href='https://github.com/Alfresco/alfresco-bm-load-data'>Alfresco Benchmark Load Data</a></li>
<li><a href='https://github.com/Alfresco/alfresco-bm-rest-api'>Alfresco Benchmark Rest Api</a></li>
<li><a href='https://github.com/Alfresco/alfresco-bm-load-users'>Alfresco Benchmark Load Users</a></li>
</ul>
</li>
<li>
<h4>Java 11 support</h4>
<p>ACS is now runnable with OpenJDK 11.0.1. It still remains compatible with JDK 1.8.</p>
</li>
</ul>
<h2> Bug
</h2>
<ul>
<li>[<a href='https://issues.alfresco.com/jira/browse/ALF-22049'>ALF-22049</a>] - Alfresco does not start
</li>
<li>[<a href='https://issues.alfresco.com/jira/browse/ALF-22041'>ALF-22041</a>] - EKS deployment - SOLR_ALFRESCO_HOST set to wrong host name
</li>
<li>[<a href='https://issues.alfresco.com/jira/browse/ALF-22031'>ALF-22031</a>] - REST API calls silently rollback after the returning a success status
</li>
<li>[<a href='https://issues.alfresco.com/jira/browse/ALF-21963'>ALF-21963</a>] - Workflow - backslash in nodeRef properties url.
</li>
<li>[<a href='https://issues.alfresco.com/jira/browse/ALF-21803'>ALF-21803</a>] - Unable to add users to sites whose 'short name' is a substring of 'site'
</li>
<li>[<a href='https://issues.alfresco.com/jira/browse/ALF-21664'>ALF-21664</a>] - Exception on workflow image by REST API
</li>
<li>[<a href='https://issues.alfresco.com/jira/browse/ALF-20854'>ALF-20854</a>] - webdav error opening Spanish Accent files
</li>
</ul>
<h2> Improvement
</h2>
<ul>
<li>[<a href='https://issues.alfresco.com/jira/browse/REPO-3668'>REPO-3668</a>] - Renditions: Transform event consumer
</li>
<li>[<a href='https://issues.alfresco.com/jira/browse/REPO-7'>REPO-7</a>] - Embed ActiveMQ in the Platform
</li>
<li>[<a href='https://issues.alfresco.com/jira/browse/REPO-1957'>REPO-1957</a>] - Transformations Improvement Plan
</li>
</ul>
<h2>
Deprecations
</h2>
<ul>
<li>
TransformService and RenditionService: All Java APIs related to TransformService and RenditionService have been deprecated; the ability to perform arbitrary transformations will be phased out as the new DBP Transform Service takes effect. Renditions can be triggered using the existing repository REST API but will be processed asynchronously using the new services.<br/>
</li>
</ul>
<h2>
Known issues
</h2>
<ul>
<li>
Due to the changes to the RenditionService the Media Management AMP is not supported yet.<br/>
</li>
</ul>
<h2>
<h1> Release Notes - Alfresco - Version Community Edition 201810 EA
</h1>
<h2> Bug
</h2>
<ul>
<li>[<a href='https://issues.alfresco.com/jira/browse/ALF-21783'>ALF-21783</a>] - ScriptAuthorityService: No way to get more than 100 results with some methods
</li>
<li>[<a href='https://issues.alfresco.com/jira/browse/ALF-21917'>ALF-21917</a>] - Document list edit metadata incorrect url escaping
</li>
<li>[<a href='https://issues.alfresco.com/jira/browse/ALF-22001'>ALF-22001</a>] - Faceted search does not work in Japanese
</li>
<li>[<a href='https://issues.alfresco.com/jira/browse/ALF-22030'>ALF-22030</a>] - ADF UI freezes noticeably on a periodic basis during KeyCloak auth requests
</li>
</ul>
<h2> Improvement
</h2>
<ul>
<li>[<a href='https://issues.alfresco.com/jira/browse/REPO-2491'>REPO-2491</a>] - Renditions: Rendition Testing
</li>
<li>[<a href='https://issues.alfresco.com/jira/browse/REPO-3651'>REPO-3651</a>] - AWS Load Tests: Infrastructure and Revamp
</li>
<li>[<a href='https://issues.alfresco.com/jira/browse/REPO-3663'>REPO-3663</a>] - AWS Load Tests: Initial AWS Cost Estimation with BMF
</li>
<li>[<a href='https://issues.alfresco.com/jira/browse/REPO-3667'>REPO-3667</a>] - Renditions: Transform event producer
</li>
<li>[<a href='https://issues.alfresco.com/jira/browse/REPO-3677'>REPO-3677</a>] - AWS Services: Basic Deployment
</li>
<li>[<a href='https://issues.alfresco.com/jira/browse/REPO-3703'>REPO-3703</a>] - AWS Services: Native Services of ACS
</li>
</ul>
<h2>
<h1> Release Notes - Alfresco - Version Community Edition 201808 EA
</h1>
<h2> Bug
</h2>
<ul>
<li>[<a href='https://issues.alfresco.com/jira/browse/ALF-21992'>ALF-21992</a>] - BehaviourFilterImpl.isEnabled(NodeRef, QName) is checking wrong QName in case of subClass
</li>
<li>[<a href='https://issues.alfresco.com/jira/browse/ALF-22006'>ALF-22006</a>] - VersionServicePolicies cannot be disabled on a specific node
</li>
<li>[<a href='https://issues.alfresco.com/jira/browse/ALF-22007'>ALF-22007</a>] - TransactionListeners are executed in unpredictable order
</li>
</ul>
<h2> Improvement
</h2>
<ul>
<li>[<a href='https://issues.alfresco.com/jira/browse/ALF-22011'>ALF-22011</a>] - Upgrade to XMLBeans 3.0.0
</li>
</ul>
<h1> Release Notes - Alfresco - Version Community Edition 201806 GA
</h1>
<h2> Bug
</h2>
<ul>
<li>[<a href='https://issues.alfresco.com/jira/browse/ALF-22000'>ALF-22000</a>] - Docker: HTML link 693ce565f4c4:8080/share but name
</li>
<li>[<a href='https://issues.alfresco.com/jira/browse/ALF-22008'>ALF-22008</a>] - It is not possible to upload a document without versions using CMIS
</li>
</ul>
+16 -36
View File
@@ -1,56 +1,36 @@
# Alfresco Content Services Community Packaging
This project is producing packaging for Alfresco Content Services Community.
This project is producing packaging for [Alfresco Content Services Repository](https://community.alfresco.com/docs/DOC-6385-project-overview-repository).
The SNAPSHOT version of artifacts are **never** published.
The SNAPSHOT version of the artifacts is **never** published.
### Contributing guide
Please use [this guide](CONTRIBUTING.md) to make a contribution to the project.
This produces the docker images for alfresco-content-repository-community and the distribution zip for the entire Alfresco Content Services Community product
# General
This project is the Community equivalent of the [Enterprise Packaging Project](https://github.com/Alfresco/acs-packaging).
### Build:
* ```mvn clean install``` in the root of the project will build everything.
This project creates the `alfresco/alfresco-content-repository-community` docker image and the distribution zip
for the Alfresco Content Services Community product.
The `alfresco/alfresco-content-repository-community` image extends the `alfresco-community-repo-base` created by the
`alfresco-community-repo` project to add additional ACS components.
# Build:
For more detailed build instructions, see the [Development Tomcat Environment](https://github.com/Alfresco/acs-community-packaging/tree/master/dev/README.md)
page.
To build the project, including the distribution zip, but not the Docker images, issue the following commands:
```
$ # The comP alias includes the following:
$ cd acs-community-packaging
$ mvn clean install
$ cd ..
```
## Docker Alfresco
Releases are published to https://hub.docker.com/r/alfresco/alfresco-content-repository-community/tags/
On official releases, the image is published: https://hub.docker.com/r/alfresco/alfresco-content-repository-community/tags/
To build the Docker images, you will need to build the `alfresco-community-repo`, `alfresco-community-share` and
`acs-community-packaging` projects. The simplest way is to use the `comRD`, `comSD` and `comPD` aliases.
For more information, see [build aliases](dev/aliases). `latest` images are created locally.
```
comRD && comSD && comPD
```
## Docker-compose & Kubernetes
Use the https://github.com/Alfresco/acs-deployment project as the basis for your own docker-compose or helm chart deployments.
For testing locally:
1. Go to docker-alfresco folder
2. Run *mvn clean install* if you have not done so
3. Build the docker image: ```docker build . --tag acr-community:6.0.tag```
4. Check that the image has been created locally with your desired name/tag: ```docker images```
### Docker-compose & Kubernetes
Use the deployment project if you want the sample docker-compose or helm: https://github.com/Alfresco/acs-deployment
## Distribution zip
The distribution zip contains the war files, libraries, certificates and settings files you need, to deploy
Alfresco Content Services on the supported application servers.
In this folder the distribution zip is build. It contains all the war files, libraries, certificates and settings files you need to deploy Alfresco Content Services Community on the supported application servers.
## How to
# How to
* [Development Tomcat Environment](dev/README.md)
* [aliases](dev/aliases)
* [Create a custom Docker image](https://github.com/Alfresco/acs-packaging/blob/master/docs/create-custom-image.md)
* [Creating customized Docker images using an existing Docker image](https://github.com/Alfresco/acs-packaging/blob/master/docs/create-custom-image-using-existing-docker-image.md)
* [Verifying that AMPs have been applied](https://github.com/Alfresco/acs-packaging/blob/master/docs/verify-the-amp-has-been-applied.md)
+49 -86
View File
@@ -2,86 +2,33 @@
It is possible to use Docker containers to test your code, but it is normally more convenient to simply run the
repository webapp (`alfresco.war`) and Share webapp (`share.war`) in a tomcat instance. Options are also available to
apply selected AMPs
## Setting up your development environment
Although it is possible to work on individual github projects, we recommend working on
the `alfresco-community-repo`, `alfresco-community-share` and `acs-community-packaging`
in a single Intellij IDEA project. They depend on each other and typically you
will want to make changes to all of them if you are changing the repository code.
repository webapp (`alfresco.war`) in a tomcat instance.
## Build -repo projects
Build the `alfresco-community-repo` (if you have not done so already), so that your changes are in the enterprise alfresco.war file.
~~~
mkdir work
cd work
git clone git@github.com:Alfresco/alfresco-community-repo.git
git clone git@github.com:Alfresco/alfresco-community-share.git
git clone git@github.com:Alfresco/acs-community-packaging.git
~~~
## Aliases
There are a set of aliases to help with building. You may find them useful, as they will help you only build selected parts
of the code base and will save you lots of typing.
Aliases ending in `D` provide Maven commands for building local Docker images. The AMPS environment variable will be of
interest, if you wish to build AMPs included in the repo and share projects.
The `aliases` file includes a more detailed description.
~~~
source acs-community-packaging/dev/aliases
~~~
## Link the projects
Generally you will want to link the different projects together by modifying the top level
pom.xml files of each downstream project so that they reference the SNAPSHOT versions of the
upstream projects. To help do this see the `acs-community-packaging` project's `scripts/dev/linkPoms.sh` and
`scripts/dev/unlinkPoms.sh` scripts.
~~~
sh acs-community-packaging/scripts/dev/linkPoms.sh
~~~
## Build the alfresco-community-repo project
Build the `alfresco-community-repo` projects (if you have not
done so already), so that your changes are in the community alfresco.war file.
~~~
# The `comR` alias includes the following commands:
cd alfresco-community-repo
mvn clean install -DskipTests=true -Dversion.edition=Community
cd ..
~~~
## Build the Share project
Build the `alfresco-community-share` project (if you have not done so already), so that your
changes are in the community share.war file, which also depends on your `alfresco-community-repo` project version.
~~~
# The `entS` alias is the same as the following commands:
cd alfresco-community-share
mvn clean install -DskipTests -Dmaven.javadoc.skip=true
cd ..
$ # The `comR` alias includes the following commands:
$ cd alfresco-community-repo
$ mvn clean install -PcommunityDocker -DskipTests=true -Dversion.edition=Community
$ cd ..
~~~
## Docker test environment
The repository code will need to talk to other ACS components, such as a database, message queue and transformers.
The repository code will need to talk to other ACS components, such as a databases, message queue and transformers.
The simplest way to create these, is to use the `docker-compose.yml` file in the `dev` directory.
~~~
# The `envUp` alias is the same as the following commands. Run these in a new terminal session, or add a `-d` flag to
the `docker compose` command.
cd acs-community-packaging
docker compose -f dev/docker-compose.yml up
Creating dev_activemq_1 ... done
Creating dev_solr6_1 ... done
Creating dev_postgres_1 ... done
Creating dev_transform-core-aio_1 ... done
$ # The `envUp` alias is the same as the following commands:
$ cd acs-community-packaging
$ docker-compose -f dev/docker-compose.yml up -d
...
cd ..
$ cd ..
~~~
## Alfresco global properties and Log4j
## Alfresco Global Properties and Log4j
Set any alfresco-global.properties or log4j properties you may need in the following files. They will be copied
to the `dev/dev-acs-amps-overlay/target` directory. Other customisations may also be placed in the `extension` directory.
~~~
dev/dev-tomcat/src/main/tomcat/shared/classes/alfresco/extension/custom-log4j2.properties
dev/dev-tomcat/src/main/tomcat/shared/classes/alfresco/extension/custom-log4j.properties
dev/dev-tomcat/src/main/tomcat/shared/classes/alfresco-global.properties
~~~
@@ -90,55 +37,71 @@ Create the development tomcat environment, apply AMPs on top of the repository c
run tomcat. The `run` profile is what starts tomcat. The `withShare` applies
the Share services AMP and adds the `share.war` to tomcat.
Once started, you will be able to access Share on `http://localhost:8080/share` and various repository
endpoints via `http://localhost:8080/alfresco/`. `comT` is an alias for the
following command and `comTDebug` will allow a debugger to be attached.
endpoints via `http://localhost:8080/alfresco/`. `entT` is an alias for the
following command and `entTDebug` will allow a debugger to be attached.
~~~
# The alias comT is the same as the following commands:
cd acs-community-packaging
mvn clean install -Prun -rf dev
[INFO] Scanning for projects...
$ # The alias comT is the same as the following mvn command. comTDebug may also be used.
$ mvn clean install -Prun,withShare
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] Development Tomcat Environment [pom]
[INFO] Tomcat Configuration [pom]
[INFO] Repo WAR with amps [war]
[INFO] Share WAR with amps [war]
[INFO] Tomcat [war]
[INFO] Alfresco Content Services Community Packaging [pom]
[INFO] Alfresco Content Services Community Distribution zip [jar]
[INFO] Alfresco Content Services Public API Javadoc [pom]
[INFO] ACS Community Docker Image Builder for Alfresco Community [pom]
[INFO] Scanning for projects...
[INFO] --------------------------------[ pom ]---------------------------------
...
INFO: Starting ProtocolHandler ["http-bio-8080"]
cd ..
$ cd ..
~~~
If you kill the tomcat instance (^C) and wish to restart it, use the following command
or the `comO` alias, or `comODebug` to attach a debugger.
or the `comO` alias, or `comODebug` to attach a debuger.
~~~
mvn install -Prun,withShare -rf dev-acs-amps-overlay
$ mvn install -Prun,withShare -rf dev-acs-amps-overlay
~~~
## Clean up
When finished, kill the tomcat instance and stop the Docker instances. You will normally also
remove the Docker containers, as you will need a clean database if you are going to issue
another `mvn clean install` command. If you started `docker compose` in a separate terminal session,
simply use `^C` or if you used a `-d` flag, use `docker compose -f dev/docker-compose.yml stop`.
another `mvn clean install` command.
~~~
^C
$ ^C
... Stopped 'sysAdmin' subsystem, ID: [sysAdmin, default]
docker compose -f dev/docker-compose.yml rm
$ docker-compose -f dev/docker-compose.yml stop
Stopping dev_transform-core-aio_1 ... done
Stopping dev_transform-router_1 ... done
Stopping dev_solr6_1 ... done
Stopping dev_postgres_1 ... done
Stopping dev_activemq_1 ... done
Stopping dev_shared-file-store_1 ... done
$ docker-compose -f dev/docker-compose.yml rm
Going to remove dev_transform-core-aio_1, dev_transform-router_1, dev_solr6_1, dev_postgres_1, dev_activemq_1, dev_shared-file-store_1
Are you sure? [yN] y
Removing dev_transform-core-aio_1 ... done
Removing dev_transform-router_1 ... done
Removing dev_solr6_1 ... done
Removing dev_postgres_1 ... done
Removing dev_activemq_1 ... done
Removing dev_shared-file-store_1 ... done
~~~
If you have not removed the containers, it is possible to restart the tomcat instance with
a `mvn install` (no `clean`), but this may result in failures if there are incompatibilities
between the code, database and content in `dev/dev-acs-amps-overlay/target/dev-instance/runtime/alf_data`.
Any changes made to alfresco-global properties or log4j will not be picked up, unless you
directly edit `dev/dev-acs-amps-overlay/target/dev-instance/tomcat/shared/classes/alfresco/extension/custom-log4j2.properties`
directly edit `dev/dev-acs-amps-overlay/target/dev-instance/tomcat/shared/classes/alfresco/extension/custom-log4j.properties`
and `dev/dev-acs-amps-overlay/target/dev-instance/tomcat/shared/classes/alfresco-global.properties`, but they will be thrown away
on the next `mvn clean`.
## Aliases
You may also find the aliases specified in the following file useful, as they may save you some typing.
Aliases ending in `D` provide Maven commands for building local Docker images.
~~~
$ source acs-community-packaging/dev/aliases
~~~
+21 -34
View File
@@ -1,8 +1,8 @@
# The aliases in this script provide simple commands to build projects, start tomcat and combinations of these. This
# give the flexibility to only rebuild or repackage parts of the codebase that have changed.
#
# Once the repository component has been built, it will need to be combined with other ACS components such as Share
# in the acs_community_packaging projects.
# Once the repository component has been built, it will need to be combined with other ACS components in the
# acs_community_packaging projects.
#
# When developing code, you will normally not be creating repository docker images, or TAS test jar files, but will be
# creating war files and then running them in a local tomcat instance. To provide the environment needed to do this you
@@ -11,20 +11,15 @@
# If need to create Docker images or create TAS test jar files (extended in downstream projects), use the aliases that
# end in D for Docker.
#
# By default only the share-services AMP is built by these aliases. Additional AMPS may be included by adding their
# profile to the AMPS environment variable. For example: export AMPS="-Pags"
#
# Aliases to build a sequence of projects and start tomcat - remember to start a CLEAN docker-compose environment - see envUp
# com - builds alfresco-community-repo & acs-community-packaging
#
# Aliases to build projects WITHOUT creating docker images or TAS tests jars
# comR - alfresco-community-repo
# comS - alfresco-community-share
# comP - acs-community-packaging
#
# Aliases to build projects including DOCKER images and TAS tests jars
# comRD - alfresco-community-repo
# comSD - alfresco-community-share
# comPD - acs-community-packaging
# Aliases to start tomcat in a CLEAN environment - remember to start a docker-compose environment - see envUp
@@ -35,36 +30,28 @@
# comO - acs-community-packaging
# comODebug - acs-community-packaging (uses mvnDebug to allow a debugger to be attached)
# Set environment variables if not already set
if [ -z ${AMPS+x} ]
then
export AMPS=""
fi
export TAG=latest
alias comR='(cd alfresco-community-repo && mvn clean install $AMPS -DskipTests=true -Dversion.edition=Community)'
alias comS='(cd alfresco-community-share && mvn clean install $AMPS -DskipTests -Dmaven.javadoc.skip=true)'
alias comP='(cd acs-community-packaging && mvn clean install $AMPS -Dmaven.javadoc.skip=true)'
alias comR='(cd alfresco-community-repo && mvn clean install -DskipTests=true -Dversion.edition=Community)'
alias comP='(cd acs-community-packaging && mvn clean install -Dmaven.javadoc.skip=true)'
alias comRD='(cd alfresco-community-repo && mvn clean install $AMPS -DskipTests=true -Dversion.edition=Community -Pall-tas-tests -Pbuild-docker-images -Dimage.tag=latest)'
alias comSD='(cd alfresco-community-share && mvn clean install $AMPS -DskipTests=true -DskipTests -Dmaven.javadoc.skip=true -Pall-tas-tests -Pbuild-docker-images -Dimage.tag=latest -Drepo.image.tag=latest)'
alias comPD='(cd acs-community-packaging && mvn clean install $AMPS -DskipTests=true -Dmaven.javadoc.skip=true -Pall-tas-tests -Pbuild-docker-images -Dimage.tag=latest -Drepo.image.tag=latest -Dshare.image.tag=latest)'
alias comRD="(cd alfresco-community-repo && mvn clean install -PcommunityDocker -Pall-tas-tests -DskipTests=true -Dimage.tag=$TAG -Dversion.edition=Community)"
alias comPD="(cd acs-community-packaging && mvn clean install -PcommunityDocker -Pall-tas-tests -DskipTests=true -Dimage.tag=$TAG -Drepo.image.tag=$TAG -Dmaven.javadoc.skip=true)"
alias comT='(cd acs-community-packaging && mvn clean install -Prun $AMPS -rf dev)'
alias comTDebug='(cd acs-community-packaging && mvnDebug clean install -Prun $AMPS -rf dev)'
alias comO='(cd acs-community-packaging && mvn install -Prun $AMPS -rf dev/dev-repo-amps-overlay)'
alias comODebug='(cd acs-community-packaging && mvnDebug install -Prun $AMPS -rf dev/dev-repo-amps-overlay)'
alias comT=' (cd acs-community-packaging && mvn clean install -Prun,withShare)'
alias comTDebug='(cd acs-community-packaging && mvnDebug clean install -Prun,withShare)'
alias comO=' (cd acs-community-packaging && mvn install -Prun,withShare -rf dev/dev-acs-amps-overlay)'
alias comODebug='(cd acs-community-packaging && mvnDebug install -Prun,withShare -rf dev/dev-acs-amps-overlay)'
alias com='comR && comS && comP && comT'
alias comD='comRD && comSD && comPD'
alias comX='comR && comS && comP'
alias comDR='comRD && comRD && comPD'
alias comXR='comR && comR && comP'
alias comXS='comS && comP'
alias com='comR && comP && comT'
alias comD='comRD && comPD'
alias comX='comR && comP'
# Clean up of docker images and content stores (alf_data directories)
alias drm='docker rm -f $(docker ps -q)'
alias arm='rm -rf `find . -name alf_data`'
alias envUp='./acs-community-packaging/scripts/dev/start-compose.sh'
alias envStop='docker compose -f acs-community-packaging/dev/docker-compose.yml stop'
alias envKill='docker compose -f acs-community-packaging/dev/docker-compose.yml kill'
alias envRm='docker compose -f acs-community-packaging/dev/docker-compose.yml rm'
alias drm='docker rm -f $(docker ps -q)'
alias arm='rm -rf `find . -name alf_data`'
alias envUp='docker-compose -f acs-community-packaging/dev/docker-compose.yml up'
alias envStop='docker-compose -f acs-community-packaging/dev/docker-compose.yml stop'
alias envKill='docker-compose -f acs-community-packaging/dev/docker-compose.yml kill'
alias envRm='docker-compose -f acs-community-packaging/dev/docker-compose.yml rm'
+360 -272
View File
@@ -1,272 +1,360 @@
<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>dev-acs-community-amps-overlay</artifactId>
<name>Run Tomcat</name>
<packaging>war</packaging>
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-dev-community-tomcat-env</artifactId>
<version>26.2.0-A.25</version>
</parent>
<properties>
<dependency.sis.version>1.3</dependency.sis.version>
<tomcat.default.solr6.port>8983</tomcat.default.solr6.port>
</properties>
<dependencies>
<dependency>
<groupId>org.alfresco</groupId>
<artifactId>dev-community-repo-amps-overlay</artifactId>
<version>${project.version}</version>
<type>war</type>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Provided dependencies to satisfy Cargo requirement, but not include in built WAR -->
<dependency>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-server-root</artifactId>
<version>${dependency.alfresco-server-root.version}</version>
<type>war</type>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.alfresco</groupId>
<artifactId>api-explorer</artifactId>
<version>${alfresco.api-explorer.version}</version>
<type>war</type>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.alfresco</groupId>
<artifactId>dev-community-share-amps-overlay</artifactId>
<version>${project.version}</version>
<type>war</type>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>alfresco</finalName>
<plugins>
<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven3-plugin</artifactId>
<configuration>
<container>
<containerId>tomcat10x</containerId>
<type>embedded</type>
<log>target/cargo.log</log>
<systemProperties>
<cargo.tomcat.ajp.port>-1</cargo.tomcat.ajp.port>
<index.subsystem.name>solr6</index.subsystem.name>
<solr.port>${tomcat.default.solr6.port}</solr.port>
<dir.root>${runtime.data.folder}/alf_data</dir.root>
<solr.host>localhost</solr.host>
<solr.base.url>/solr</solr.base.url>
<solr.secureComms>secret</solr.secureComms>
<solr.sharedSecret>secret</solr.sharedSecret>
<encryption.keystore.type>pkcs12</encryption.keystore.type>
<encryption.cipherAlgorithm>AES/CBC/PKCS5Padding</encryption.cipherAlgorithm>
<encryption.keyAlgorithm>AES</encryption.keyAlgorithm>
<metadata-keystore.password>mp6yc0UD9e</metadata-keystore.password>
<metadata-keystore.aliases>metadata</metadata-keystore.aliases>
<metadata-keystore.metadata.password>mp6yc0UD9e</metadata-keystore.metadata.password>
<metadata-keystore.metadata.algorithm>AES</metadata-keystore.metadata.algorithm>
</systemProperties>
</container>
<configuration>
<configfiles>
<configfile>
<file>${runtime.tomcat.conf.folder}/alfresco-context.xml</file>
<todir>conf</todir>
<tofile>context.xml</tofile>
</configfile>
<configfile>
<file>${runtime.tomcat.conf.folder}/tomcat-users.xml</file>
<todir>conf</todir>
<tofile>tomcat-users.xml</tofile>
</configfile>
</configfiles>
<files>
<file>
<file>${runtime.tomcat.conf.folder}/shared</file>
<todir>shared</todir>
</file>
</files>
</configuration>
<deployables>
<deployable>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-server-root</artifactId>
<type>war</type>
<properties>
<context>/</context>
</properties>
</deployable>
<deployable>
<groupId>org.alfresco</groupId>
<artifactId>api-explorer</artifactId>
<type>war</type>
<properties>
<context>/api-explorer</context>
</properties>
</deployable>
<deployable>
<groupId>org.alfresco</groupId>
<artifactId>dev-community-share-amps-overlay</artifactId>
<type>war</type>
<properties>
<context>/share</context>
</properties>
</deployable>
<deployable>
<groupId>org.alfresco</groupId>
<artifactId>dev-acs-community-amps-overlay</artifactId>
<type>war</type>
<properties>
<context>/alfresco</context>
</properties>
</deployable>
</deployables>
</configuration>
<dependencies>
<!-- Workaround required to make Share work after Tika upgrade -->
<dependency>
<groupId>org.apache.sis.core</groupId>
<artifactId>sis-utility</artifactId>
<version>${dependency.sis.version}</version>
</dependency>
<dependency>
<groupId>org.apache.sis.core</groupId>
<artifactId>sis-metadata</artifactId>
<version>${dependency.sis.version}</version>
</dependency>
<dependency>
<groupId>org.apache.sis.storage</groupId>
<artifactId>sis-storage</artifactId>
<version>${dependency.sis.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<!-- Download and unpack the tomcat config zip file -->
<execution>
<id>unpack-tomcat-config</id>
<phase>generate-resources</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/tomcat-conf</outputDirectory>
<artifactItems>
<artifactItem>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-dev-community-tomcat</artifactId>
<version>${project.version}</version>
<classifier>config</classifier>
<overWrite>true</overWrite>
<type>zip</type>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<!-- Copy tomcat config from build directory to correct location -->
<!--
Q. Why not unpack directly to ${runtime.tomcat.conf.folder} ?
A. There is no way of telling maven NOT to overwrite the directory if it is already there.
overWrite doesn't work, overWriteIfNewer doesn't work either. The plugin uses marker files
to determine whether the zip has already been unpacked, rather than using the actual folder
where the contents are being unpacked - so if a clean is performed the marker files disappear
and the zip is unpacked regardless (overwriting your customised tomcat config). Whilst you
can change the location of the marker files, doing things this way really is going to be more
reliable, since the existing directory will be respected properly.
-->
<execution>
<id>copy-tomcat-config</id>
<goals>
<goal>run</goal>
</goals>
<phase>generate-resources</phase>
<configuration>
<target>
<taskdef resource="net/sf/antcontrib/antcontrib.properties" classpathref="maven.plugin.classpath" />
<if>
<available file="${runtime.tomcat.conf.folder}" type="dir" />
<then>
<echo message="Skipping creation of tomcat configuration, already exists: ${runtime.tomcat.conf.folder}" />
</then>
<else>
<echo message="Creating tomcat runtime configuration: ${runtime.tomcat.conf.folder}" />
<copy todir="${runtime.tomcat.conf.folder}">
<fileset dir="${project.build.directory}/tomcat-conf" />
</copy>
</else>
</if>
</target>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>ant-contrib</groupId>
<artifactId>ant-contrib</artifactId>
<version>20020829</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>license-maven-plugin</artifactId>
<executions>
<execution>
<id>third-party-licenses</id>
<phase>none</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>run</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven3-plugin</artifactId>
<executions>
<execution>
<id>run-exploded-webapp</id>
<phase>verify</phase>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
<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>dev-acs-community-amps-overlay</artifactId>
<name>Content Services WAR with amps</name>
<packaging>war</packaging>
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-dev-community-tomcat-env</artifactId>
<version>7.0.1-SNAPSHOT</version>
</parent>
<properties>
<dependency.sis.version>1.0</dependency.sis.version>
<tomcat.version>7.0.86</tomcat.version>
<tomcat.default.solr6.port>8983</tomcat.default.solr6.port>
</properties>
<dependencies>
<dependency>
<groupId>org.alfresco</groupId>
<artifactId>content-services-community</artifactId>
<type>war</type>
</dependency>
<dependency>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-share-services</artifactId>
<type>amp</type>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
<build>
<finalName>alfresco</finalName>
<plugins>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.6</version>
<configuration>
<overlays>
<overlay>
<groupId>org.alfresco</groupId>
<artifactId>content-services-community</artifactId>
<type>war</type>
</overlay>
<overlay>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-share-services</artifactId>
<type>amp</type>
</overlay>
</overlays>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
<executions>
<execution>
<id>prepare-exploded-war</id>
<goals>
<goal>exploded</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.alfresco.maven.plugin</groupId>
<artifactId>alfresco-maven-plugin</artifactId>
<version>4.1.0</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<systemProperties>
<index.subsystem.name>solr6</index.subsystem.name>
<solr.port>${tomcat.default.solr6.port}</solr.port>
<dir.root>${runtime.data.folder}/alf_data</dir.root>
<solr.host>localhost</solr.host>
<solr.base.url>/solr</solr.base.url>
<solr.secureComms>none</solr.secureComms>
<encryption.keystore.type>pkcs12</encryption.keystore.type>
<encryption.cipherAlgorithm>AES/CBC/PKCS5Padding</encryption.cipherAlgorithm>
<encryption.keyAlgorithm>AES</encryption.keyAlgorithm>
<metadata-keystore.password>mp6yc0UD9e</metadata-keystore.password>
<metadata-keystore.aliases>metadata</metadata-keystore.aliases>
<metadata-keystore.metadata.password>mp6yc0UD9e</metadata-keystore.metadata.password>
<metadata-keystore.metadata.algorithm>AES</metadata-keystore.metadata.algorithm>
</systemProperties>
<delegate>false</delegate>
<useSeparateTomcatClassLoader>true</useSeparateTomcatClassLoader>
<contextFile>${runtime.tomcat.conf.folder}/alfresco-context.xml</contextFile>
<tomcatUsers>${runtime.tomcat.conf.folder}/tomcat-users.xml</tomcatUsers>
<webapps>
<webapp>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-server-root</artifactId>
<version>${dependency.alfresco-server-root.version}</version>
<contextPath>/</contextPath>
<type>war</type>
<asWebapp>true</asWebapp>
<!-- Workaround for https://issues.apache.org/jira/browse/MTOMCAT-269 -->
<!-- tomcat7-maven-plugin does not pick up the context.xml file from META-INF -->
<contextFile>${project.build.directory}/tomcat/webapps/alfresco-server-root/META-INF/context.xml</contextFile>
</webapp>
<webapp>
<groupId>org.alfresco</groupId>
<artifactId>api-explorer</artifactId>
<version>${alfresco.api-explorer.version}</version>
<contextPath>/api-explorer</contextPath>
<type>war</type>
<asWebapp>true</asWebapp>
</webapp>
</webapps>
</configuration>
<dependencies>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-util</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-coyote</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-api</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-dbcp</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-servlet-api</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jsp-api</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jasper</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jasper-el</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-el-api</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-tribes</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina-ha</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-annotations-api</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-juli</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-logging-juli</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-logging-log4j</artifactId>
<version>${tomcat.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<!-- Download and unpack the tomcat config zip file -->
<execution>
<id>unpack-tomcat-config</id>
<phase>generate-resources</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/tomcat-conf</outputDirectory>
<artifactItems>
<artifactItem>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-dev-community-tomcat</artifactId>
<version>${project.version}</version>
<classifier>config</classifier>
<overWrite>true</overWrite>
<type>zip</type>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<!-- Copy tomcat config from build directory to correct location -->
<!--
Q. Why not unpack directly to ${runtime.tomcat.conf.folder} ?
A. There is no way of telling maven NOT to overwrite the directory if it is already there.
overWrite doesn't work, overWriteIfNewer doesn't work either. The plugin uses marker files
to determine whether the zip has already been unpacked, rather than using the actual folder
where the contents are being unpacked - so if a clean is performed the marker files disappear
and the zip is unpacked regardless (overwriting your customised tomcat config). Whilst you
can change the location of the marker files, doing things this way really is going to be more
reliable, since the existing directory will be respected properly.
-->
<execution>
<id>copy-tomcat-config</id>
<goals>
<goal>run</goal>
</goals>
<phase>generate-resources</phase>
<configuration>
<target>
<taskdef resource="net/sf/antcontrib/antcontrib.properties"
classpathref="maven.plugin.classpath"/>
<if>
<available file="${runtime.tomcat.conf.folder}" type="dir"/>
<then>
<echo message="Skipping creation of tomcat configuration, already exists: ${runtime.tomcat.conf.folder}"/>
</then>
<else>
<echo message="Creating tomcat runtime configuration: ${runtime.tomcat.conf.folder}"/>
<copy todir="${runtime.tomcat.conf.folder}">
<fileset dir="${project.build.directory}/tomcat-conf"/>
</copy>
</else>
</if>
</target>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>ant-contrib</groupId>
<artifactId>ant-contrib</artifactId>
<version>20020829</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>run</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<executions>
<execution>
<id>run-exploded-webapp</id>
<phase>verify</phase>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>withShare</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<webapps combine.children="append">
<webapp>
<groupId>org.alfresco</groupId>
<artifactId>share</artifactId>
<contextPath>/share</contextPath>
<version>${alfresco.share.version}</version>
<type>war</type>
<asWebapp>true</asWebapp>
</webapp>
</webapps>
</configuration>
<!-- Workaround required to make Share work after Tika upgrade -->
<dependencies>
<dependency>
<groupId>org.apache.sis.core</groupId>
<artifactId>sis-utility</artifactId>
<version>${dependency.sis.version}</version>
</dependency>
<dependency>
<groupId>org.apache.sis.core</groupId>
<artifactId>sis-metadata</artifactId>
<version>${dependency.sis.version}</version>
</dependency>
<dependency>
<groupId>org.apache.sis.storage</groupId>
<artifactId>sis-storage</artifactId>
<version>${dependency.sis.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
-141
View File
@@ -1,141 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<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>dev-community-repo-amps-overlay</artifactId>
<name>Repo WAR with amps</name>
<packaging>war</packaging>
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-dev-community-tomcat-env</artifactId>
<version>26.2.0-A.25</version>
</parent>
<build>
<finalName>alfresco</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>unpack-war-file</id>
<phase>process-resources</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.alfresco</groupId>
<artifactId>content-services-community</artifactId>
<version>${dependency.alfresco-community-repo.version}</version>
<type>war</type>
<outputDirectory>${project.build.directory}/alfresco</outputDirectory>
</artifactItem>
</artifactItems>
</configuration>
</execution>
<execution>
<id>copy-base-amps</id>
<phase>process-resources</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-share-services</artifactId>
<version>${dependency.alfresco-community-repo.version}</version>
<type>amp</type>
<overWrite>false</overWrite>
<outputDirectory>${project.build.directory}/amps</outputDirectory>
</artifactItem>
<!-- Inclusion of AOS results in
java.lang.ClassNotFoundException: org.alfresco.repo.web.filter.beans.BeanProxyFilter
This is probably because, the configuration of the dev tomcat, unlike production
is shared between webapps and something (possibly a web.xml fragment) is
incorrectly being picked up by the /share webapp. The previous version did
not include aos or googledrive.
<artifactItem>
<groupId>org.alfresco.aos-module</groupId>
<artifactId>alfresco-aos-module</artifactId>
<type>amp</type>
<overWrite>false</overWrite>
<outputDirectory>${project.build.directory}/amps</outputDirectory>
</artifactItem>
-->
<artifactItem>
<groupId>org.alfresco.integrations</groupId>
<artifactId>alfresco-googledrive-repo-community</artifactId>
<type>amp</type>
<overWrite>false</overWrite>
<outputDirectory>${project.build.directory}/amps</outputDirectory>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.alfresco.maven.plugin</groupId>
<artifactId>alfresco-maven-plugin</artifactId>
<executions>
<execution>
<id>install-amps</id>
<goals>
<goal>install</goal>
</goals>
<phase>prepare-package</phase>
<configuration>
<backup>true</backup>
<ampLocation>
${project.build.directory}/amps
</ampLocation>
<warLocation>${project.build.directory}/alfresco</warLocation>
</configuration>
</execution>
</executions>
<configuration>
<includeWebResources>true</includeWebResources>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>ags</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-ags-amp</id>
<phase>process-resources</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-community-repo</artifactId>
<version>${dependency.alfresco-community-repo.version}</version>
<type>amp</type>
<overWrite>false</overWrite>
<outputDirectory>${project.build.directory}/amps</outputDirectory>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
-120
View File
@@ -1,120 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<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>dev-community-share-amps-overlay</artifactId>
<name>Share WAR with amps</name>
<packaging>war</packaging>
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-dev-community-tomcat-env</artifactId>
<version>26.2.0-A.25</version>
</parent>
<build>
<finalName>share</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>unpack-war-file</id>
<phase>process-resources</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.alfresco</groupId>
<artifactId>share</artifactId>
<version>${dependency.alfresco-community-share.version}</version>
<type>war</type>
<outputDirectory>${project.build.directory}/share</outputDirectory>
</artifactItem>
</artifactItems>
</configuration>
</execution>
<execution>
<id>copy-base-amps</id>
<phase>process-resources</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.alfresco.integrations</groupId>
<artifactId>alfresco-googledrive-share</artifactId>
<version>${alfresco.googledrive.version}</version>
<type>amp</type>
<overWrite>false</overWrite>
<outputDirectory>${project.build.directory}/amps-share</outputDirectory>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.alfresco.maven.plugin</groupId>
<artifactId>alfresco-maven-plugin</artifactId>
<executions>
<execution>
<id>install-amps</id>
<goals>
<goal>install</goal>
</goals>
<phase>prepare-package</phase>
<configuration>
<backup>true</backup>
<ampLocation>
${project.build.directory}/amps-share
</ampLocation>
<warLocation>${project.build.directory}/share</warLocation>
</configuration>
</execution>
</executions>
<configuration>
<includeWebResources>true</includeWebResources>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>ags</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-ags-amp</id>
<phase>process-resources</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-community-share</artifactId>
<version>${dependency.alfresco-community-share.version}</version>
<type>amp</type>
<overWrite>false</overWrite>
<outputDirectory>${project.build.directory}/amps-share</outputDirectory>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
+62 -62
View File
@@ -1,62 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<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-dev-community-tomcat</artifactId>
<name>Tomcat Configuration</name>
<packaging>pom</packaging>
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-dev-community-tomcat-env</artifactId>
<version>26.2.0-A.25</version>
</parent>
<properties>
<dev.tomcat.conf.folder>${basedir}/src/main/tomcat</dev.tomcat.conf.folder>
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<inherited>false</inherited>
<executions>
<execution>
<id>dev-tomcat-config-zip</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<attach>true</attach>
<descriptors>
<descriptor>src/assembly/tomcat-conf.xml</descriptor>
</descriptors>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>properties-maven-plugin</artifactId>
<executions>
<!-- Read alfresco-global.properties if it exists, to use it for cleaning runtime data (e.g. which database?) -->
<execution>
<id>read-alfresco-global-properties</id>
<phase>initialize</phase>
<goals>
<goal>read-project-properties</goal>
</goals>
<configuration>
<files>
<file>${alfresco.properties.file}</file>
</files>
<quiet>true</quiet>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
<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-dev-community-tomcat</artifactId>
<name>Tomcat Configuration</name>
<packaging>pom</packaging>
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-dev-community-tomcat-env</artifactId>
<version>7.0.1-SNAPSHOT</version>
</parent>
<properties>
<dev.tomcat.conf.folder>${basedir}/src/main/tomcat</dev.tomcat.conf.folder>
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<inherited>false</inherited>
<executions>
<execution>
<id>dev-tomcat-config-zip</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<attach>true</attach>
<descriptors>
<descriptor>src/assembly/tomcat-conf.xml</descriptor>
</descriptors>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>properties-maven-plugin</artifactId>
<executions>
<!-- Read alfresco-global.properties if it exists, to use it for cleaning runtime data (e.g. which database?) -->
<execution>
<id>read-alfresco-global-properties</id>
<phase>initialize</phase>
<goals>
<goal>read-project-properties</goal>
</goals>
<configuration>
<files>
<file>${alfresco.properties.file}</file>
</files>
<quiet>true</quiet>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Resources cachingAllowed="true" cacheMaxSize="102400">
<PostResources className="org.apache.catalina.webresources.DirResourceSet"
base="${catalina.home}/shared/classes"
webAppMount="/WEB-INF/classes"/>
</Resources>
<Context docBase="${project.build.directory}/${project.build.finalName}" path="/alfresco">
<Resources className="org.apache.naming.resources.VirtualDirContext"
extraResourcePaths="/=${project.build.directory}/${project.build.finalName}" />
<Loader searchVirtualFirst="true"
className="org.apache.catalina.loader.VirtualWebappLoader"
virtualClasspath="${project.build.outputDirectory};${runtime.tomcat.conf.folder}/shared/classes" />
</Context>
@@ -20,7 +20,7 @@ db.create.command=create database ${db.name}
#db.name=alfresco
#db.username=alfresco
#db.password=alfresco
#db.driver=com.mysql.cj.jdbc.Driver
#db.driver=com.mysql.jdbc.Driver
#db.url=jdbc:mysql://localhost/${db.name}?useUnicode=true&characterEncoding=UTF-8
#db.master.url=jdbc:mysql://localhost/
#db.master.username=${db.username}
@@ -116,8 +116,3 @@ share.host=localhost
share.port=8080
transformer.debug.entries=10
mimetype.config.dir=dev/dev-acs-amps-overlay/target/dev-instance/tomcat/shared/classes/alfresco/extension/mimetypes
rendition.config.dir=dev/dev-acs-amps-overlay/target/dev-instance/tomcat/shared/classes/alfresco/extension/transform/renditions
local.transform.pipeline.config.dir=dev/dev-acs-amps-overlay/target/dev-instance/tomcat/shared/classes/alfresco/extension/transform/pipelines
@@ -0,0 +1,21 @@
log4j.logger.org.alfresco.repo.content.transform.TransformerDebug=debug
#log4j.logger.org.alfresco.util.exec.RuntimeExecBootstrapBean=debug
#log4j.logger.org.alfresco.util.exec.RuntimeExec=debug
#log4j.logger.org.alfresco.repo.rendition2=debug
#log4j.logger.org.alfresco.repo.content.metadata=debug
#log4j.logger.org.alfresco.repo.rendition2.LocalTransformClient=debug
#log4j.logger.org.alfresco.repo.rendition.RenditionServiceImpl=debug
#log4j.logger.org.alfresco.enterprise.repo.rendition2.RemoteTransformClient=debug
#log4j.logger.org.alfresco.repo.content.transform.LocalTransformServiceRegistry=debug
#log4j.logger.org.alfresco.enterprise.repo.rendition2.RemoteTransformServiceRegistry=debug
#log4j.logger.org.alfresco.repo.rendition2.RenditionDefinitionRegistry2Impl=debug
#log4j.logger.org.alfresco.repo.content.MimetypeMap=debug
#log4j.logger.org.alfresco.repo.content.transform.LocalTransform=debug
#log4j.logger.org.alfresco.repo.rawevents=debug
#log4j.logger.org.alfresco.repo.jscript.RhinoScriptProcessor=debug
#log4j.logger.RhinoScriptProcessor.calls=debug
@@ -1,36 +0,0 @@
logger.alfresco-repo-content-transform-TransformerDebug.name=org.alfresco.repo.content.transform.TransformerDebug
logger.alfresco-repo-content-transform-TransformerDebug.level=debug
#logger.alfresco-util-exec-RuntimeExecBootstrapBean.name=org.alfresco.util.exec.RuntimeExecBootstrapBean
#logger.alfresco-util-exec-RuntimeExecBootstrapBean.level=debug
#logger.alfresco-util-exec-RuntimeExec.name=org.alfresco.util.exec.RuntimeExec
#logger.alfresco-util-exec-RuntimeExec.level=debug
#logger.alfresco-repo-rendition2.name=org.alfresco.repo.rendition2
#logger.alfresco-repo-rendition2.level=debug
#logger.alfresco-repo-content-metadata.name=org.alfresco.repo.content.metadata
#logger.alfresco-repo-content-metadata.level=debug
#logger.alfresco-repo-rendition2-LocalTransformClient.name=org.alfresco.repo.rendition2.LocalTransformClient
#logger.alfresco-repo-rendition2-LocalTransformClient.level=debug
#logger.alfresco-repo-rendition-RenditionServiceImpl.name=org.alfresco.repo.rendition.RenditionServiceImpl
#logger.alfresco-repo-rendition-RenditionServiceImpl.level=debug
#logger.alfresco-enterprise-repo-rendition2-RemoteTransformClient.name=org.alfresco.enterprise.repo.rendition2.RemoteTransformClient
#logger.alfresco-enterprise-repo-rendition2-RemoteTransformClient.level=debug
#logger.alfresco-repo-content-transform-LocalTransformServiceRegistry.name=org.alfresco.repo.content.transform.LocalTransformServiceRegistry
#logger.alfresco-repo-content-transform-LocalTransformServiceRegistry.level=debug
#logger.alfresco-repo-rendition2-RenditionDefinitionRegistry2Impl.name=org.alfresco.repo.rendition2.RenditionDefinitionRegistry2Impl
#logger.alfresco-repo-rendition2-RenditionDefinitionRegistry2Impl.level=debug
#logger.alfresco-repo-content-MimetypeMap.name=org.alfresco.repo.content.MimetypeMap
#logger.alfresco-repo-content-MimetypeMap.level=debug
#logger.alfresco-repo-content-transform-LocalTransform.name=org.alfresco.repo.content.transform.LocalTransform
#logger.alfresco-repo-content-transform-LocalTransform.level=debug
#logger.alfresco-repo-rawevents.name=org.alfresco.repo.rawevents
#logger.alfresco-repo-rawevents.level=debug
#logger.alfresco-repo-jscript-RhinoScriptProcessor.name=org.alfresco.repo.jscript.RhinoScriptProcessor
#logger.alfresco-repo-jscript-RhinoScriptProcessor.level=debug
#logger.calls.name=RhinoScriptProcessor.calls
#logger.calls.level=debug
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<Context crossContext="true">
<Environment name="solr/home" type="java.lang.String" value="${solr.solr.home}" override="true"/>
<Environment name="solr/model/dir" type="java.lang.String" value="${solr.solr.model.dir}" override="true"/>
<Environment name="solr/content/dir" type="java.lang.String" value="${solr.solr.content.dir}" override="true"/>
<Resources className="org.apache.naming.resources.VirtualDirContext"
extraResourcePaths="/=${project.build.directory}/tomcat/webapps/alfresco-solr4" />
<Loader searchVirtualFirst="true"
className="org.apache.catalina.loader.VirtualWebappLoader"
virtualClasspath="${project.build.directory}/tomcat/webapps/alfresco-solr4/WEB-INF/classes" />
</Context>
+19 -16
View File
@@ -6,7 +6,7 @@ version: "2"
services:
transform-core-aio:
image: alfresco/alfresco-transform-core-aio:${TRANSFORMERS_TAG}
image: alfresco/alfresco-transform-core-aio:2.4.0
mem_limit: 1536m
environment:
JAVA_OPTS: " -Xms256m -Xmx1536m"
@@ -14,7 +14,7 @@ services:
- 8090:8090
postgres:
image: postgres:15.4
image: postgres:13.1
mem_limit: 512m
environment:
- POSTGRES_PASSWORD=alfresco
@@ -25,29 +25,25 @@ services:
- 5432:5432
solr6:
image: alfresco/alfresco-search-services:2.0.20
image: alfresco/alfresco-search-services:2.0.0
mem_limit: 2g
environment:
#Solr needs to know how to register itself with Alfresco
SOLR_ALFRESCO_HOST: "host.docker.internal"
SOLR_ALFRESCO_PORT: "8080"
- SOLR_ALFRESCO_HOST=alfresco
- SOLR_ALFRESCO_PORT=8080
#Alfresco needs to know how to call solr
SOLR_SOLR_HOST: "localhost"
SOLR_SOLR_PORT: "8983"
- SOLR_SOLR_HOST=solr6
- SOLR_SOLR_PORT=8983
#Create the default alfresco and archive cores
SOLR_CREATE_ALFRESCO_DEFAULTS: "alfresco,archive"
- SOLR_CREATE_ALFRESCO_DEFAULTS=alfresco,archive
#HTTP by default
ALFRESCO_SECURE_COMMS: "secret"
JAVA_TOOL_OPTIONS:
"
-Dalfresco.secureComms.secret=secret
"
SOLR_JAVA_MEM: "-Xms2g -Xmx2g"
- ALFRESCO_SECURE_COMMS=none
- "SOLR_JAVA_MEM=-Xms2g -Xmx2g"
ports:
- 8983:8983 #Browser port
- 8083:8983 #Browser port
activemq:
image: alfresco/alfresco-activemq:5.18.3-jre17-rockylinux8
image: alfresco/alfresco-activemq:5.16.1
mem_limit: 1g
ports:
- 8161:8161 # Web Console
@@ -55,3 +51,10 @@ services:
- 61616:61616 # OpenWire
- 61613:61613 # STOMP
# proxy:
# image: alfresco/acs-community-ngnix:1.0.0
# mem_limit: 128m
# depends_on:
# - alfresco
# ports:
# - 8080:8080
+27 -41
View File
@@ -1,41 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<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-dev-community-tomcat-env</artifactId>
<name>Development Tomcat Environment</name>
<packaging>pom</packaging>
<parent>
<groupId>org.alfresco</groupId>
<artifactId>acs-community-packaging</artifactId>
<version>26.2.0-A.25</version>
</parent>
<properties>
<!-- Development Tomcat runtime environment configuration -->
<runtime.data.folder>${project.build.directory}/dev-instance/runtime</runtime.data.folder>
<runtime.tomcat.conf.folder>${project.build.directory}/dev-instance/tomcat</runtime.tomcat.conf.folder>
<alfresco.properties.file>${runtime.tomcat.conf.folder}/shared/classes/alfresco-global.properties</alfresco.properties.file>
</properties>
<modules>
<module>dev-tomcat</module>
<module>dev-repo-amps-overlay</module>
<module>dev-share-amps-overlay</module>
<module>dev-acs-amps-overlay</module>
</modules>
<build>
<pluginManagement>
<plugins>
<!-- Versions higher than 3.3.0 were causing the tomcat dev environment to fail to start with a
ClassNotFoundException: org.springframework.web.context.ContextLoaderListener -->
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
<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-dev-community-tomcat-env</artifactId>
<name>Development Tomcat Environment</name>
<packaging>pom</packaging>
<parent>
<groupId>org.alfresco</groupId>
<artifactId>acs-community-packaging</artifactId>
<version>7.0.1-SNAPSHOT</version>
</parent>
<properties>
<!-- Development Tomcat runtime environment configuration -->
<runtime.data.folder>${project.build.directory}/dev-instance/runtime</runtime.data.folder>
<runtime.tomcat.conf.folder>${project.build.directory}/dev-instance/tomcat</runtime.tomcat.conf.folder>
<alfresco.properties.file>${runtime.tomcat.conf.folder}/shared/classes/alfresco-global.properties</alfresco.properties.file>
</properties>
<modules>
<module>dev-tomcat</module>
<module>dev-acs-amps-overlay</module>
</modules>
</project>
-60
View File
@@ -1,60 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<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-governance-services-community-distribution</artifactId>
<name>Alfresco Governance Services Community Distribution Zip</name>
<packaging>pom</packaging>
<parent>
<groupId>org.alfresco</groupId>
<artifactId>acs-community-packaging</artifactId>
<version>26.2.0-A.25</version>
</parent>
<dependencies>
<dependency>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-community-repo</artifactId>
<version>${dependency.alfresco-community-repo.version}</version>
<type>amp</type>
</dependency>
<dependency>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-community-rest-api-explorer</artifactId>
<version>${dependency.alfresco-community-repo.version}</version>
<type>war</type>
</dependency>
<dependency>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-community-share</artifactId>
<version>${dependency.alfresco-community-share.version}</version>
<type>amp</type>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<inherited>false</inherited>
<executions>
<execution>
<id>create-distribution</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<finalName>alfresco-governance-services-community-${project.version}</finalName>
<appendAssemblyId>false</appendAssemblyId>
<descriptors>
<descriptor>src/assemble/ags-distribution.xml</descriptor>
</descriptors>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
<id>distribution-ags</id>
<formats>
<format>zip</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<dependencySets>
<dependencySet>
<useProjectArtifact>false</useProjectArtifact>
<useTransitiveDependencies>false</useTransitiveDependencies>
<unpack>false</unpack>
</dependencySet>
</dependencySets>
</assembly>
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>acs-community-packaging</artifactId>
<version>26.2.0-A.25</version>
<version>7.0.1-SNAPSHOT</version>
</parent>
<dependencies>
+2 -2
View File
@@ -38,7 +38,7 @@
<!-- Share -->
<fileSet>
<directory>${project.build.directory}/dependency/alfresco-content-services-share-distribution-${dependency.acs-packaging.version}</directory>
<directory>${project.build.directory}/dependency/alfresco-content-services-share-distribution-${alfresco.share.version}</directory>
<outputDirectory/>
<includes>
<include>web-server/**</include>
@@ -47,7 +47,7 @@
</includes>
</fileSet>
<fileSet>
<directory>${project.build.directory}/dependency/alfresco-content-services-share-distribution-${dependency.acs-packaging.version}/web-extension-samples</directory>
<directory>${project.build.directory}/dependency/alfresco-content-services-share-distribution-${alfresco.share.version}/web-extension-samples</directory>
<outputDirectory>web-server/shared/classes/alfresco/web-extension</outputDirectory>
</fileSet>
</fileSets>
+1 -1
View File
@@ -4,6 +4,6 @@ Alfresco Community (Build: ${project.version})
Contains:
- Alfresco Content Services Community: ${project.version}
- Alfresco Repository: ${dependency.alfresco-community-repo.version}
- Alfresco Share: ${dependency.alfresco-community-share.version}
- Alfresco Share: ${alfresco.share.version}
For users of Alfresco Community Edition, more information on this release is available at https://community.alfresco.com/community/ecm
+1 -2
View File
@@ -1,5 +1,4 @@
ARG BASE_IMAGE
FROM ${BASE_IMAGE}
FROM alfresco/alfresco-community-repo-base:${repo.image.tag}
# Set default docker_context.
ARG resource_path=target
-39
View File
@@ -1,39 +0,0 @@
ARG BASE_IMAGE
# BUILD STAGE AGS
FROM debian:11-slim AS AGSBUILDER
RUN export DEBIAN_FRONTEND=noninteractive; \
apt-get update -qqy && apt-get -yqq install unzip && \
mkdir -p /build/gs-api-explorer
### Copy the AGS war from the local context
COPY target/gs-api-explorer-*.war /build
RUN unzip -q /build/gs-api-explorer-*.war -d /build/gs-api-explorer && \
chmod -R g-w,o= /build
# ACTUAL IMAGE
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.
USER root
COPY target/alfresco-governance-services-community-repo-*.amp /usr/local/tomcat/amps/
# Install amps on alfresco.war
RUN java -jar /usr/local/tomcat/alfresco-mmt/alfresco-mmt*.jar install \
/usr/local/tomcat/amps \
/usr/local/tomcat/webapps/alfresco -directory -nobackup
### Copy gs-api-explorer war into webapps folder
COPY --chown=root:Alfresco --from=AGSBUILDER /build/gs-api-explorer /usr/local/tomcat/webapps/gs-api-explorer
# All files in the tomcat folder must be owned by root user and Alfresco group as mentioned in the parent Dockerfile
RUN chgrp -R Alfresco /usr/local/tomcat && \
find /usr/local/tomcat/webapps -type d -exec chmod 0750 {} \; && \
find /usr/local/tomcat/webapps -type f -exec chmod 0640 {} \; && \
chmod -R g+r /usr/local/tomcat/webapps
# Switching back to alfresco user after having added amps files to run the container as non-root
USER alfresco
-74
View File
@@ -1,74 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>content-services-community-docker-ags</artifactId>
<name>Alfresco Content Services Community Docker Image Builder for AGS</name>
<description>Alfresco Governance Services Community Repository Extension</description>
<packaging>pom</packaging>
<parent>
<groupId>org.alfresco</groupId>
<artifactId>content-services-community-docker</artifactId>
<version>26.2.0-A.25</version>
</parent>
<properties>
<image.name>alfresco/alfresco-governance-repository-community</image.name>
<base.image>alfresco/alfresco-content-repository-community:${image.tag}</base.image>
<scripts.directory>${project.parent.parent.basedir}/scripts/dev</scripts.directory>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>unpack-war-files</id>
<phase>process-resources</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.alfresco</groupId>
<artifactId>api-explorer</artifactId>
<version>${alfresco.api-explorer.version}</version>
<type>war</type>
<outputDirectory>${project.build.directory}/war/api-explorer</outputDirectory>
</artifactItem>
</artifactItems>
</configuration>
</execution>
<execution>
<id>copy-amps</id>
<phase>process-resources</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-community-rest-api-explorer</artifactId>
<version>${dependency.alfresco-community-repo.version}</version>
<type>war</type>
<destFileName>gs-api-explorer-${dependency.alfresco-community-repo.version}.war</destFileName>
<outputDirectory>${project.build.directory}</outputDirectory>
</artifactItem>
<artifactItem>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-community-repo</artifactId>
<version>${dependency.alfresco-community-repo.version}</version>
<type>amp</type>
<outputDirectory>${project.build.directory}</outputDirectory>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
+9 -175
View File
@@ -7,14 +7,11 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>acs-community-packaging</artifactId>
<version>26.2.0-A.25</version>
<version>7.0.1-SNAPSHOT</version>
</parent>
<properties>
<image.name>alfresco/alfresco-content-repository-community</image.name>
<docker.quay-expires.value>NEVER</docker.quay-expires.value>
<base.image>alfresco/alfresco-community-repo-base:${repo.image.tag}</base.image>
<scripts.directory>${project.parent.basedir}/scripts/dev</scripts.directory>
</properties>
<build>
@@ -52,7 +49,7 @@
<artifactItem>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-share-services</artifactId>
<version>${dependency.alfresco-community-repo.version}</version>
<version>${alfresco.alfresco-share-services.version}</version>
<type>amp</type>
<overWrite>false</overWrite>
<outputDirectory>${project.build.directory}/amps</outputDirectory>
@@ -67,7 +64,7 @@
<profiles>
<profile>
<id>build-docker-images</id>
<id>communityDocker</id>
<!-- builds "image:latest" locally -->
<build>
<plugins>
@@ -78,12 +75,6 @@
<images>
<image>
<name>${image.name}:${image.tag}</name>
<build>
<args>
<BASE_IMAGE>${base.image}</BASE_IMAGE>
</args>
<contextDir>${project.basedir}</contextDir>
</build>
</image>
</images>
</configuration>
@@ -102,74 +93,7 @@
</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>
<builderName>${builder.name}</builderName>
<platforms>
<platform>linux/amd64</platform>
<platform>linux/arm64</platform>
</platforms>
<attestations>
<provenance>false</provenance>
</attestations>
</buildx>
<dockerFileDir>${project.basedir}</dockerFileDir>
<args>
<BASE_IMAGE>${local.registry}/${base.image}</BASE_IMAGE>
</args>
</build>
</image>
</images>
</configuration>
<executions>
<execution>
<id>build-push-image</id>
<phase>install</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>
<id>internal</id>
<!-- publishes "image:latest" to Quay -->
<build>
<plugins>
@@ -180,23 +104,8 @@
<images>
<!-- Quay image -->
<image>
<name>${image.registry}/${image.name}:${image.tag}</name>
<build>
<buildx>
<builderName>${builder.name}</builderName>
<platforms>
<platform>linux/amd64</platform>
<platform>linux/arm64</platform>
</platforms>
<attestations>
<provenance>false</provenance>
</attestations>
</buildx>
<dockerFileDir>${project.basedir}</dockerFileDir>
<args>
<BASE_IMAGE>${local.registry}/${base.image}</BASE_IMAGE>
</args>
</build>
<name>${image.name}:${image.tag}</name>
<registry>${image.registry}</registry>
</image>
</images>
</configuration>
@@ -211,35 +120,13 @@
</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>release</id>
<!-- publishes "image:release_version" to DockerHub and Quay -->
<!-- publishes "image:release_version" to DockerHub -->
<build>
<plugins>
<plugin>
@@ -254,22 +141,6 @@
<!-- DockerHub image -->
<image>
<name>${image.name}:${project.version}</name>
<build>
<buildx>
<builderName>${builder.name}</builderName>
<platforms>
<platform>linux/amd64</platform>
<platform>linux/arm64</platform>
</platforms>
<attestations>
<provenance>false</provenance>
</attestations>
</buildx>
<dockerFileDir>${project.basedir}</dockerFileDir>
<args>
<BASE_IMAGE>${local.registry}/${base.image}</BASE_IMAGE>
</args>
</build>
</image>
</images>
</configuration>
@@ -285,23 +156,8 @@
<images>
<!-- Quay image -->
<image>
<name>${image.registry}/${image.name}:${project.version}</name>
<build>
<buildx>
<builderName>${builder.name}</builderName>
<platforms>
<platform>linux/amd64</platform>
<platform>linux/arm64</platform>
</platforms>
<attestations>
<provenance>false</provenance>
</attestations>
</buildx>
<dockerFileDir>${project.basedir}</dockerFileDir>
<args>
<BASE_IMAGE>${local.registry}/${base.image}</BASE_IMAGE>
</args>
</build>
<name>${image.name}:${project.version}</name>
<registry>${image.registry}</registry>
</image>
</images>
</configuration>
@@ -312,28 +168,6 @@
</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>
-15
View File
@@ -1,15 +0,0 @@
### Apply AGS community share AMP to Share image
ARG BASE_IMAGE
FROM ${BASE_IMAGE}
LABEL quay.expires-after=${docker.quay-expires.value}
### Copy the AMP from build context to amps_share
COPY target/alfresco-governance-services-community-share-*.amp /usr/local/tomcat/amps_share/
### Install AMP on share
RUN java -jar /usr/local/tomcat/alfresco-mmt/alfresco-mmt*.jar install \
/usr/local/tomcat/amps_share/alfresco-governance-services-community-share-*.amp /usr/local/tomcat/webapps/share -nobackup
ENTRYPOINT ["/usr/local/tomcat/shared/classes/alfresco/substituter.sh", "catalina.sh run"]
EXPOSE 8000
-323
View File
@@ -1,323 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>share-community-docker-ags</artifactId>
<packaging>pom</packaging>
<name>Alfresco Share Community Docker Image Builder for AGS</name>
<description>Alfresco Governance Services Share Community Extension</description>
<parent>
<groupId>org.alfresco</groupId>
<artifactId>share-community-docker</artifactId>
<version>26.2.0-A.25</version>
</parent>
<properties>
<image.name>alfresco/alfresco-governance-share-community</image.name>
<docker.quay-expires.value>NEVER</docker.quay-expires.value>
<base.image>alfresco/alfresco-share-base:${share.image.tag}</base.image>
<scripts.directory>${project.parent.parent.basedir}/scripts/dev</scripts.directory>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-amps</id>
<phase>process-resources</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-community-share</artifactId>
<version>${dependency.alfresco-community-share.version}</version>
<type>amp</type>
</artifactItem>
</artifactItems>
<outputDirectory>${project.build.directory}</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<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}</BASE_IMAGE>
</args>
<contextDir>${project.basedir}</contextDir>
</build>
</image>
</images>
</configuration>
<executions>
<execution>
<id>build-image</id>
<phase>install</phase>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</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>
<builderName>${builder.name}</builderName>
<platforms>
<platform>linux/amd64</platform>
<platform>linux/arm64</platform>
</platforms>
<attestations>
<provenance>false</provenance>
</attestations>
</buildx>
<dockerFileDir>${project.basedir}</dockerFileDir>
<args>
<BASE_IMAGE>${local.registry}/${base.image}</BASE_IMAGE>
</args>
</build>
</image>
</images>
</configuration>
<executions>
<execution>
<id>build-push-image</id>
<phase>install</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" to Quay -->
<build>
<plugins>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<configuration>
<images>
<!-- Quay image -->
<image>
<name>${image.registry}/${image.name}:${image.tag}</name>
<build>
<buildx>
<builderName>${builder.name}</builderName>
<platforms>
<platform>linux/amd64</platform>
<platform>linux/arm64</platform>
</platforms>
<attestations>
<provenance>false</provenance>
</attestations>
</buildx>
<dockerFileDir>${project.basedir}</dockerFileDir>
<args>
<BASE_IMAGE>${local.registry}/${base.image}</BASE_IMAGE>
</args>
</build>
</image>
</images>
</configuration>
<executions>
<execution>
<id>build-push-image</id>
<phase>install</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>release</id>
<!-- publishes "image:release_version" to DockerHub and Quay -->
<build>
<plugins>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<executions>
<execution>
<id>build-push-image</id>
<phase>deploy</phase>
<configuration combine.self="override">
<images>
<!-- DockerHub image -->
<image>
<name>${image.name}:${project.version}</name>
<build>
<buildx>
<builderName>${builder.name}</builderName>
<platforms>
<platform>linux/amd64</platform>
<platform>linux/arm64</platform>
</platforms>
<attestations>
<provenance>false</provenance>
</attestations>
</buildx>
<dockerFileDir>${project.basedir}</dockerFileDir>
<args>
<BASE_IMAGE>${local.registry}/${base.image}</BASE_IMAGE>
</args>
</build>
</image>
</images>
</configuration>
<goals>
<goal>build</goal>
<goal>push</goal>
</goals>
</execution>
<execution>
<id>build-push-quay-image</id>
<phase>deploy</phase>
<configuration combine.self="override">
<images>
<!-- Quay image -->
<image>
<name>${image.registry}/${image.name}:${project.version}</name>
<build>
<buildx>
<builderName>${builder.name}</builderName>
<platforms>
<platform>linux/amd64</platform>
<platform>linux/arm64</platform>
</platforms>
<attestations>
<provenance>false</provenance>
</attestations>
</buildx>
<dockerFileDir>${project.basedir}</dockerFileDir>
<args>
<BASE_IMAGE>${local.registry}/${base.image}</BASE_IMAGE>
</args>
</build>
</image>
</images>
</configuration>
<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>
</profiles>
</project>
-12
View File
@@ -1,12 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>share-community-docker</artifactId>
<name>Alfresco Share Community Docker Image Builder</name>
<packaging>pom</packaging>
<parent>
<groupId>org.alfresco</groupId>
<artifactId>acs-community-packaging</artifactId>
<version>26.2.0-A.25</version>
</parent>
</project>
+41 -74
View File
@@ -3,29 +3,31 @@
<artifactId>acs-community-packaging</artifactId>
<packaging>pom</packaging>
<name>Alfresco Content Services Community Packaging</name>
<version>26.2.0-A.25</version>
<version>7.0.1-SNAPSHOT</version>
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<relativePath>../alfresco-community-repo/pom.xml</relativePath>
<version>26.2.0.83</version>
<version>9.29</version>
</parent>
<properties>
<dependency.alfresco-community-repo.version>26.2.0.83</dependency.alfresco-community-repo.version>
<dependency.alfresco-community-share.version>26.2.0.53</dependency.alfresco-community-share.version>
<dependency.acs-packaging.version>26.2.0-A.25</dependency.acs-packaging.version> <!-- for Share distribution zip -->
<dependency.alfresco-community-repo.version>9.29</dependency.alfresco-community-repo.version>
<alfresco.share.version>7.0.1</alfresco.share.version>
<alfresco.alfresco-share-services.version>${alfresco.share.version}</alfresco.alfresco-share-services.version>
<alfresco.api-explorer.version>7.0.0</alfresco.api-explorer.version>
<repo.image.tag>${dependency.alfresco-community-repo.version}</repo.image.tag>
<share.image.tag>${dependency.alfresco-community-share.version}</share.image.tag>
<share.image.tag>${alfresco.share.version}</share.image.tag>
</properties>
<scm>
<connection>scm:git:https://github.com/Alfresco/acs-community-packaging.git</connection>
<developerConnection>scm:git:https://github.com/Alfresco/acs-community-packaging.git</developerConnection>
<url>https://github.com/Alfresco/acs-community-packaging</url>
<tag>26.2.0-A.25</tag>
<tag>HEAD</tag>
</scm>
<distributionManagement>
@@ -39,42 +41,30 @@
</snapshotRepository>
</distributionManagement>
<repositories>
<repository>
<id>alfresco-public</id>
<url>https://artifacts.alfresco.com/nexus/content/groups/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>alfresco-public-snapshots</id>
<url>https://artifacts.alfresco.com/nexus/content/groups/public-snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<modules>
<module>distribution</module>
<module>docker-alfresco</module>
<module>docker-share</module>
</modules>
<profiles>
<profile>
<id>ags</id>
<id>fullBuild</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<modules>
<module>distribution-ags</module>
<module>docker-alfresco/ags</module>
<module>docker-share/ags</module>
<module>distribution</module>
<module>public-javadoc</module>
<module>docker-alfresco</module>
</modules>
</profile>
<profile>
<id>communityDocker</id>
<modules>
<module>distribution</module>
<module>docker-alfresco</module>
</modules>
</profile>
<profile>
<id>internal</id>
<modules>
<module>distribution</module>
<module>docker-alfresco</module>
</modules>
</profile>
<profile>
@@ -86,8 +76,8 @@
<profile>
<id>release</id>
<modules>
<module>public-javadoc</module>
<module>dev</module>
<module>distribution</module>
<module>docker-alfresco</module>
</modules>
</profile>
<profile>
@@ -159,20 +149,10 @@
<artifactId>alfresco-community-repo-distribution</artifactId>
<version>${dependency.alfresco-community-repo.version}</version>
</dependency>
<dependency>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-mmt</artifactId>
<version>${dependency.alfresco-community-repo.version}</version>
</dependency>
<dependency>
<groupId>org.alfresco.tas</groupId>
<artifactId>cmis</artifactId>
<version>${dependency.alfresco-community-repo.version}</version>
</dependency>
<dependency>
<groupId>org.alfresco.tas</groupId>
<artifactId>cmis</artifactId>
<artifactId>alfresco-community-repo-cmis-test</artifactId>
<version>${dependency.alfresco-community-repo.version}</version>
<classifier>tests</classifier>
<scope>test</scope>
@@ -193,12 +173,7 @@
</dependency>
<dependency>
<groupId>org.alfresco.tas</groupId>
<artifactId>restapi</artifactId>
<version>${dependency.alfresco-community-repo.version}</version>
</dependency>
<dependency>
<groupId>org.alfresco.tas</groupId>
<artifactId>restapi</artifactId>
<artifactId>alfresco-community-repo-restapi-test</artifactId>
<version>${dependency.alfresco-community-repo.version}</version>
<classifier>tests</classifier>
<scope>test</scope>
@@ -210,50 +185,42 @@
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-content-services-share-distribution</artifactId>
<version>${dependency.acs-packaging.version}</version>
<version>${alfresco.share.version}</version>
<type>zip</type>
</dependency>
<dependency>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-share-services</artifactId>
<version>${dependency.alfresco-community-repo.version}</version>
<version>${alfresco.alfresco-share-services.version}</version>
<type>amp</type>
</dependency>
<dependency>
<groupId>org.alfresco</groupId>
<artifactId>share</artifactId>
<version>${dependency.alfresco-community-share.version}</version>
<version>${alfresco.share.version}</version>
<type>war</type>
</dependency>
<dependency>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-wcmqs-distribution</artifactId>
<version>${dependency.alfresco-community-share.version}</version>
<version>${alfresco.share.version}</version>
<type>zip</type>
</dependency>
<dependency>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-wcmqs-web</artifactId>
<version>${dependency.alfresco-community-share.version}</version>
<version>${alfresco.share.version}</version>
<classifier>classes</classifier>
</dependency>
<dependency>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-wcmqs-client-api</artifactId>
<version>${dependency.alfresco-community-share.version}</version>
<version>${alfresco.share.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>com.diffplug.spotless</groupId>
<artifactId>spotless-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
+2 -2
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>acs-community-packaging</artifactId>
<version>26.2.0-A.25</version>
<version>7.0.1-SNAPSHOT</version>
</parent>
<packaging>pom</packaging>
@@ -23,7 +23,7 @@
<groupId>org.alfresco</groupId>
<artifactId>share</artifactId>
<classifier>classes</classifier>
<version>${dependency.alfresco-community-share.version}</version>
<version>${alfresco.share.version}</version>
</dependency>
<!-- Downloads the docflex license -->
-102
View File
@@ -1,102 +0,0 @@
#!/usr/bin/env bash
echo "=========================== Starting Build Script ==========================="
PS4="\[\e[35m\]+ \[\e[m\]"
set -vex
pushd "$(dirname "${BASH_SOURCE[0]}")/../../"
source "$(dirname "${BASH_SOURCE[0]}")/build_functions.sh"
usage() {
echo "Builds the upstream projects first, then the current one." 1>&2;
echo 1>&2;
echo "Usage: $0 [-m]" 1>&2;
echo " -m: Flag to build Docker images with multi-architecture" 1>&2;
echo " -h: Display the usage information" 1>&2;
exit 1;
}
while getopts "mh" option; do
case $option in
m)
DOCKER_BUILD_PROFILE=build-multiarch-docker-images
;;
h)
usage
;;
esac
done
BUILD_PROFILE=${DOCKER_BUILD_PROFILE:-build-docker-images}
COM_DEPENDENCY_VERSION="$(retrievePomProperty "dependency.alfresco-community-repo.version")"
REPO_IMAGE=$([[ "${COM_DEPENDENCY_VERSION}" =~ ^.+-SNAPSHOT$ ]] && echo "-Drepo.image.tag=latest" || echo)
# Either both the parent and the upstream dependency are the same, or else fail the build
if [ "${COM_DEPENDENCY_VERSION}" != "$(retrievePomParentVersion)" ]; then
printf "Upstream dependency version (%s) is different then the project parent version!\n" "${COM_DEPENDENCY_VERSION}"
exit 1
fi
# Prevent merging of any SNAPSHOT dependencies into the master or the release/* branches
if [[ $(isPullRequestBuild) && "${COM_DEPENDENCY_VERSION}" =~ ^.+-SNAPSHOT$ && "${BRANCH_NAME}" =~ ^master$|^release/.+$ ]] ; then
printf "PRs with SNAPSHOT dependencies are not allowed into master or release branches\n"
exit 1
fi
# Prevent release jobs from starting when there are SNAPSHOT upstream dependencies
if [[ "${COM_DEPENDENCY_VERSION}" =~ ^.+-SNAPSHOT$ ]] && [ "${JOB_NAME,,}" = "release" ] ; then
printf "Cannot release project with SNAPSHOT dependencies!\n"
exit 1
fi
UPSTREAM_REPO="github.com/Alfresco/alfresco-community-repo.git"
# Search, checkout and build the same branch on the upstream project in case of SNAPSHOT dependencies
# Otherwise, checkout the upstream tag and build its Docker image (use just "mvn package", without "mvn install")
if [[ "${COM_DEPENDENCY_VERSION}" =~ ^.+-SNAPSHOT$ ]] ; then
pullAndBuildSameBranchOnUpstream "${UPSTREAM_REPO}" "-P$BUILD_PROFILE -Pags -Dlicense.failOnNotUptodateHeader=true"
else
pullUpstreamTagAndBuildDockerImage "${UPSTREAM_REPO}" "${COM_DEPENDENCY_VERSION}" "-P$BUILD_PROFILE -Pags -Dlicense.failOnNotUptodateHeader=true"
fi
SHARE_DEPENDENCY_VERSION="$(retrievePomProperty "dependency.alfresco-community-share.version")"
SHARE_IMAGE=$([[ "${SHARE_DEPENDENCY_VERSION}" =~ ^.+-SNAPSHOT$ ]] && echo "-Dshare.image.tag=latest" || echo)
# Prevent merging of any SNAPSHOT dependencies into the master or the release/* branches
if [[ $(isPullRequestBuild) && "${SHARE_DEPENDENCY_VERSION}" =~ ^.+-SNAPSHOT$ && "${BRANCH_NAME}" =~ ^master$|^release/.+$ ]] ; then
printf "PRs with SNAPSHOT dependencies are not allowed into master or release branches\n"
exit 1
fi
# Prevent release jobs from starting when there are SNAPSHOT upstream dependencies
if [[ "${SHARE_DEPENDENCY_VERSION}" =~ ^.+-SNAPSHOT$ ]] && [ "${JOB_NAME,,}" = "release" ] ; then
printf "Cannot release project with SNAPSHOT dependencies!\n"
exit 1
fi
SHARE_UPSTREAM_REPO="github.com/Alfresco/alfresco-community-share.git"
# Checkout the upstream share project (tag or branch; + build if the latter)
if [[ "${SHARE_DEPENDENCY_VERSION}" =~ ^.+-SNAPSHOT$ ]] ; then
pullAndBuildSameBranchOnUpstream "${SHARE_UPSTREAM_REPO}" "-P$BUILD_PROFILE -Pags -Dlicense.failOnNotUptodateHeader=true -Ddocker.quay-expires.value=NEVER ${REPO_IMAGE} -Ddependency.alfresco-community-repo.version=${COM_DEPENDENCY_VERSION}"
else
pullUpstreamTagAndBuildDockerImage "${SHARE_UPSTREAM_REPO}" "${SHARE_DEPENDENCY_VERSION}" "-P$BUILD_PROFILE -Pags -Dlicense.failOnNotUptodateHeader=true -Ddocker.quay-expires.value=NEVER -Ddependency.alfresco-community-repo.version=${COM_DEPENDENCY_VERSION}"
fi
# Build the current project
mvn -B -ntp -V -q install -DskipTests -Dmaven.javadoc.skip=true -P$BUILD_PROFILE -Pags ${REPO_IMAGE} ${SHARE_IMAGE}
# Build alfresco image with jdbc drivers (used by search API tests for MariaDB/MySQL)
MYSQL_JDBC_TAG=$(mvn help:evaluate -Dexpression=dependency.mysql.version -q -DforceStdout)
mvn dependency:copy -Dartifact=mysql:mysql-connector-java:${MYSQL_JDBC_TAG}:jar -DoutputDirectory=tests/environment/alfresco-with-jdbc-drivers
MARIADB_JDBC_TAG=$(mvn help:evaluate -Dexpression=dependency.mariadb.version -q -DforceStdout)
mvn dependency:copy -Dartifact=org.mariadb.jdbc:mariadb-java-client:${MARIADB_JDBC_TAG}:jar -DoutputDirectory=tests/environment/alfresco-with-jdbc-drivers
REPO_LATEST_IMAGE=$(docker images --format='{{.Repository}}:{{.Tag}}' | grep "alfresco-content-repository-community:latest")
docker build -t alfresco-repository-databases:latest -f tests/environment/alfresco-with-jdbc-drivers/alfresco.Dockerfile . --build-arg BASE_IMAGE=${REPO_LATEST_IMAGE}
popd
set +vex
echo "=========================== Finishing Build Script =========================="
-23
View File
@@ -1,23 +0,0 @@
#!/usr/bin/env bash
echo "=========================== Starting Copy to Release Bucket Script ==========================="
PS4="\[\e[35m\]+ \[\e[m\]"
set -vex
#
# Copy from S3 Release bucket to S3 eu.dl bucket
#
if [ -z "${RELEASE_VERSION}" ]; then
echo "Please provide a RELEASE_VERSION in the format <acs-version>-<additional-info> (e.g. 7.2.0-A2)"
exit 1
fi
SOURCE="s3://alfresco-artefacts-staging/community/RM/${RELEASE_VERSION}"
DESTINATION="s3://eu.dl.alfresco.com/release/community/RM/${RELEASE_VERSION}"
printf "\n%s\n%s\n" "${SOURCE}" "${DESTINATION}"
aws s3 cp --acl private --recursive --copy-props none "${SOURCE}" "${DESTINATION}"
set +vex
echo "=========================== Finishing Copy to Release Bucket Script =========================="
-23
View File
@@ -1,23 +0,0 @@
#!/usr/bin/env bash
echo "=========================== Starting Copy to Release Bucket Script ==========================="
PS4="\[\e[35m\]+ \[\e[m\]"
set -vex
#
# Copy from S3 Release bucket to S3 eu.dl bucket
#
if [ -z "${RELEASE_VERSION}" ]; then
echo "Please provide a RELEASE_VERSION in the format <acs-version>-<additional-info> (e.g. 7.2.0-A2)"
exit 1
fi
SOURCE="s3://alfresco-artefacts-staging/alfresco-content-services-community/release/${BRANCH_NAME}/${BUILD_NUMBER}"
DESTINATION="s3://eu.dl.alfresco.com/release/community/${RELEASE_VERSION}-build-${BUILD_NUMBER}"
printf "\n%s\n%s\n" "${SOURCE}" "${DESTINATION}"
aws s3 cp --acl private --recursive --copy-props none "${SOURCE}" "${DESTINATION}"
set +vex
echo "=========================== Finishing Copy to Release Bucket Script =========================="
-52
View File
@@ -1,52 +0,0 @@
#!/usr/bin/env bash
echo "========================== Starting Prepare Staging Deploy Script ==========================="
PS4="\[\e[35m\]+ \[\e[m\]"
set -vex
pushd "$(dirname "${BASH_SOURCE[0]}")/../../"
# Identify latest annotated tag (latest version)
export VERSION=$(git describe --abbrev=0 --tags)
# Get third party license scripts.
git clone --depth=1 https://github.com/Alfresco/third-party-license-overrides.git
# Move the final artifacts to a single folder (deploy_dir) to be copied to S3
mkdir -p deploy_dir
cp distribution/target/alfresco.war deploy_dir
cp distribution/target/*-distribution*.zip deploy_dir
# Create third party license csv file and add it to the deploy directory.
unzip deploy_dir/*-distribution*.zip -d deploy_dir/community-acs
zippaths=""
for file in `find deploy_dir/community-acs -name "*.amp" -o -name "*.war" -not -name "ROOT.war" -not -name "_vti_bin.war"`
do
zippaths+="$file|"
done
zippaths=${zippaths::-1}
python3 ./third-party-license-overrides/thirdPartyLicenseCSVCreator.py --zippaths "${zippaths}" --version "${VERSION}" --combined --output "deploy_dir"
rm -rf deploy_dir/community-acs
echo "Local deploy directory content:"
ls -lA deploy_dir
# Create deploy directory for AGS.
mkdir -p deploy_dir_ags
cp distribution-ags/target/*.zip deploy_dir_ags
# Generate third party license csv for AGS.
unzip deploy_dir_ags/*.zip -d deploy_dir_ags/community-ags
zippaths=""
for file in `find deploy_dir_ags/community-ags -name "*.amp" -o -name "*.war"`
do
zippaths+="$file|"
done
zippaths=${zippaths::-1}
python3 ./third-party-license-overrides/thirdPartyLicenseCSVCreator.py --zippaths ${zippaths} --version "${VERSION}" --combined --output "deploy_dir_ags"
rm -rf deploy_dir_ags/community-ags
echo "Local AGS deploy directory content:"
ls -lA deploy_dir_ags
# Tidy up.
rm -rf ./third-party-license-overrides
popd
set +vex
echo "========================== Finishing Prepare Staging Deploy Script =========================="
-52
View File
@@ -1,52 +0,0 @@
#!/usr/bin/env bash
function updatePomParent() {
local PROJECT="$1"
local PARENT_POM_VERSION="$2"
if [ -d "${ROOT_DIR}/${PROJECT}" -a -n "${PARENT_POM_VERSION}" ]
then
# Cannot use "mvn versions:update-parent" as the version must exist before it can be set. The ed command is also faster.
pushd "${ROOT_DIR}/${PROJECT}" &>/dev/null
ed -s pom.xml &>/tmp/$$.log << EOF
/<parent>
/<version>.*<\/version>/s//<version>${PARENT_POM_VERSION}<\/version>/
wq
EOF
if [ $? -ne 0 ]
then
echo
echo "\"update of <pom><parent><version> version failed on ${PROJECT}"
cat "/tmp/$$.log"
exit 1
fi
printf "%-25s %46s=%s\n" "${PROJECT}" "<pom><parent><version>" "${PARENT_POM_VERSION}"
popd &>/dev/null
fi
}
function updatePomProperty() {
local PROJECT="$1"
local PROPERTY_VALUE="$2"
local PROPERTY_NAME="$3"
if [ -d "${ROOT_DIR}/${PROJECT}" -a -n "${PROPERTY_VALUE}" ]
then
# Can use "mvn versions:set-property", but ed is so much faster.
# mvn -B versions:set-property versions:commit -Dproperty="${PROPERTY_NAME}" "-DnewVersion=${PROPERTY_VALUE}"
pushd "${ROOT_DIR}/${PROJECT}" &>/dev/null
ed -s pom.xml &>/tmp/$$.log << EOF
/\(<${PROPERTY_NAME}>\).*\(<\/${PROPERTY_NAME}>\)/s//\1${PROPERTY_VALUE}\2/
wq
EOF
if [ $? -ne 0 ]
then
echo
echo "\"update of <${PROPERTY_NAME}> failed on ${PROJECT}"
cat "/tmp/$$.log"
exit 1
fi
printf "%-25s %46s=%s\n" "${PROJECT}" "<${PROPERTY_NAME}>" "${PROPERTY_VALUE}"
popd &>/dev/null
fi
}
-245
View File
@@ -1,245 +0,0 @@
#!/usr/bin/env bash
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="${SCRIPT_DIR}/../../.."
ENV_FILE=".linkPoms.env"
ENV_PATH="${ROOT_DIR}/${ENV_FILE}"
source "$(dirname "${BASH_SOURCE[0]}")/dev_functions.sh"
usage() {
echo "Updates the downstream projects with the versions of the upstream projects. Reversed by unlinkPoms.sh" 1>&2;
echo 1>&2;
echo "Usage: $0 [-b <branch>] [-mpxuh]" 1>&2;
echo " -m: Checkout master of each project" 1>&2;
echo " -b: Checkout the <branch> of each project or master if <branch> is blank" 1>&2;
echo " -p: Pull the latest version of each project" 1>&2;
echo " -x: Skip the extract of values from each project" 1>&2;
echo " -u: Skip the update of values in each project" 1>&2;
echo " -h: Display this help" 1>&2;
exit 1;
}
function checkout() {
local PROJECT="${1}"
local BRANCH="${2}"
if [ -d "${ROOT_DIR}/${PROJECT}" ]
then
pushd "${ROOT_DIR}/${PROJECT}" &>/dev/null
git checkout "${BRANCH}" &>/tmp/$$.log
if [ $? -ne 0 ]
then
echo
echo "\"git checkout ${BRANCH}\" failed on ${PROJECT}"
cat "/tmp/$$.log"
exit 1
fi
echo "${PROJECT} is now on ${BRANCH}"
popd &>/dev/null
fi
}
function pull_latest() {
local PROJECT="${1}"
if [ -d "${ROOT_DIR}/${PROJECT}" ]
then
pushd "${ROOT_DIR}/${PROJECT}" &>/dev/null
git pull &>/tmp/$$.log
if [ $? -ne 0 ]
then
echo
echo "\"git pull\" failed on ${PROJECT}"
cat "/tmp/$$.log"
exit 1
fi
echo "${PROJECT} is now using latest"
popd &>/dev/null
fi
}
function readTopLevelTag() {
local TAG_NAME="${1}"
local POM_FILE="${2}"
# Might be possible to generalise this function to accept an XPath so it could be used in place of sed commands
# Read the file with an IFS (Input Field Separator) of the start of XML tag character <
local IFS=\>
local DEPTH=-99
while read -d \< ENTITY CONTENT
do
if [[ $ENTITY == project\ * ]] # outer <project> tag
then
DEPTH=0
elif [[ $ENTITY == /* ]] # end tag
then
((DEPTH=DEPTH-1))
else # start tag
((DEPTH=DEPTH+1))
fi
if [[ $ENTITY = "${TAG_NAME}" ]] && [[ $DEPTH == 1 ]] ; then
echo $CONTENT
exit
fi
done < $POM_FILE
exit 1
}
function exportPomVersion() {
local PROJECT="${1}"
local ENV_NAME="${2}"
if [ -d "${ROOT_DIR}/${PROJECT}" ]
then
pushd "${ROOT_DIR}/${PROJECT}" &>/dev/null
# Same as slower/simpler: "mvn help:evaluate -Dexpression=project.version"
VERSION=$(readTopLevelTag version pom.xml)
if [ $? -ne 0 ]
then
echo
echo "\"readTopLevelTagContent version pom.xml\" failed on ${PROJECT}"
exit 1
fi
echo "export ${ENV_NAME}=${VERSION}" >> "${ENV_PATH}"
popd &>/dev/null
fi
}
function exportPomParent() {
local PROJECT="${1}"
local ENV_NAME="${2}"
if [ -d "${ROOT_DIR}/${PROJECT}" ]
then
pushd "${ROOT_DIR}/${PROJECT}" &>/dev/null
# Same as slower/simpler: "mvn help:evaluate -Dexpression=project.parent.version"
PROPERTY_VALUE=$(sed -n '/<parent>/,/<\/parent>/p' pom.xml | sed -n "s/.*<version>\(.*\)<\/version>/\1/p" | sed 's/\r//g')
if [ $? -ne 0 ]
then
echo
echo "\"sed -n '/<parent>/,/<\/parent>/p' pom.xml | sed -n \\\"s/.*<version>\(.*\)<\/version>/\1/p\\\" | sed 's/\r//g'\" failed on ${PROJECT}"
exit 1
fi
echo "export ${ENV_NAME}=${PROPERTY_VALUE}" >> "${ENV_PATH}"
popd &>/dev/null
fi
}
# Original version was simpler/slower: exportPomPropertyOrig <project> <env_name> project.parent.version
function exportPomProperty() {
local PROJECT="${1}"
local ENV_NAME="${2}"
local PROPERTY_NAME="${3}"
if [ -d "${ROOT_DIR}/${PROJECT}" ]
then
pushd "${ROOT_DIR}/${PROJECT}" &>/dev/null
# Same as slower/simpler: "mvn help:evaluate -Dexpression=${PROPERTY_NAME}"
PROPERTY_VALUE=$(sed -n '/<properties>/,/<\/properties>/p' pom.xml | sed -n "s/.*<${PROPERTY_NAME}>\(.*\)<\/${PROPERTY_NAME}>/\1/p" | sed 's/\r//g')
if [ $? -ne 0 ]
then
echo
echo "\"sed -n '/<properties>/,/<\/properties>/p' pom.xml | sed -n \\\"s/.*<${PROPERTY_NAME}>\(.*\)<\/${PROPERTY_NAME}>/\1/p\\\" | sed 's/\r//g'\" failed on ${PROJECT}"
exit 1
fi
echo "export ${ENV_NAME}=${PROPERTY_VALUE}" >> "${ENV_PATH}"
popd &>/dev/null
fi
}
while getopts "b:mpxuh" arg; do
case $arg in
b)
B_FLAG_SET="true"
;;
m)
M_FLAG_B_FLAG_SET="true"
;;
p)
# git pull after git checkout
;;
x)
SKIP_EXPORT="true"
;;
u)
SKIP_UPDATE="true"
;;
h | *)
usage
exit 0
;;
esac
done
if [ -n "${B_FLAG_SET}" -a -n "${M_FLAG_B_FLAG_SET}" ]
then
echo "-m and -b may not both be set"
exit 1
fi
OPTIND=1
while getopts "b:mpxuh" arg; do
case $arg in
b)
BRANCH="${OPTARG:-master}"
checkout alfresco-community-repo "${BRANCH}"
checkout alfresco-community-share "${BRANCH}"
checkout acs-community-packaging "${BRANCH}"
;;
m)
BRANCH="master"
checkout alfresco-community-repo "${BRANCH}"
checkout alfresco-community-share "${BRANCH}"
checkout acs-community-packaging "${BRANCH}"
;;
esac
done
OPTIND=1
while getopts "b:mpxuh" arg; do
case $arg in
p)
pull_latest alfresco-community-repo
pull_latest alfresco-community-share
pull_latest acs-community-packaging
;;
esac
done
if [ -z ${SKIP_EXPORT+x} ]
then
rm -f "${ENV_FILE}"
exportPomVersion alfresco-community-repo COM_R_VERSION
exportPomVersion alfresco-enterprise-repo ENT_R_VERSION
exportPomParent alfresco-enterprise-repo ENT_R_PARENT
exportPomProperty alfresco-enterprise-repo ENT_R_DEP_COM_R dependency.alfresco-community-repo.version
exportPomVersion alfresco-community-share COM_S_VERSION
exportPomProperty alfresco-community-share COM_S_DEP_COM_R dependency.alfresco-community-repo.version
exportPomProperty alfresco-community-share COM_S_DEP_ENT_R dependency.alfresco-enterprise-repo.version
exportPomVersion acs-community-packaging COM_P_VERSION
exportPomParent acs-community-packaging COM_P_PARENT
exportPomProperty acs-community-packaging COM_P_DEP_COM_R dependency.alfresco-community-repo.version
exportPomProperty acs-community-packaging COM_P_DEP_COM_S dependency.alfresco-community-share.version
cat "${ENV_FILE}"
fi
if [ -z ${SKIP_UPDATE+x} ]
then
if [ ! -f "${ENV_FILE}" ]
then
echo ""${ENV_FILE}" does not exist."
exit 1
fi
source "${ENV_FILE}"
updatePomProperty alfresco-community-share "$COM_R_VERSION" dependency.alfresco-community-repo.version
updatePomParent acs-community-packaging "$COM_R_VERSION"
updatePomProperty acs-community-packaging "$COM_R_VERSION" dependency.alfresco-community-repo.version
updatePomProperty acs-community-packaging "$COM_S_VERSION" dependency.alfresco-community-share.version
fi
-21
View File
@@ -1,21 +0,0 @@
#!/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'
-5
View File
@@ -1,5 +0,0 @@
set -x
export TRANSFORMERS_TAG=$(mvn -f acs-community-packaging/pom.xml help:evaluate -Dexpression=dependency.alfresco-transform-core.version -q -DforceStdout)
# .env files are picked up from project directory correctly on docker-compose 1.23.0+
docker compose -f acs-community-packaging/dev/docker-compose.yml up
-37
View File
@@ -1,37 +0,0 @@
#!/usr/bin/env bash
set -o errexit
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="${SCRIPT_DIR}/../../.."
ENV_FILE=".linkPoms.env"
source "$(dirname "${BASH_SOURCE[0]}")/dev_functions.sh"
usage() {
echo "Reverts changes made by linkPoms.sh using values stored in ${ENV_FILE}" 1>&2;
echo 1>&2;
echo "Usage: $0 [-h]" 1>&2;
echo " -h: Display this help" 1>&2;
exit 1;
}
while getopts "lh" arg; do
case $arg in
l)
LOGGING_OUT=`tty`
;;
h | *)
usage
exit 0
;;
esac
done
source ${ENV_FILE}
updatePomProperty alfresco-community-share "$COM_S_DEP_COM_R" dependency.alfresco-community-repo.version
updatePomParent acs-community-packaging "$COM_P_PARENT"
updatePomProperty acs-community-packaging "$COM_P_DEP_COM_R" dependency.alfresco-community-repo.version
updatePomProperty acs-community-packaging "$COM_P_DEP_COM_S" dependency.alfresco-community-share.version
@@ -1,7 +0,0 @@
#!/usr/bin/env bash
set +x
mvn spotless:apply validate -DlicenseUpdateHeaders=true -Pags,all-tas-tests > /dev/null || true
set -x
+53
View File
@@ -0,0 +1,53 @@
#!/usr/bin/env bash
echo "=========================== Starting Build Script ==========================="
PS4="\[\e[35m\]+ \[\e[m\]"
set -vex
pushd "$(dirname "${BASH_SOURCE[0]}")/../../"
source "$(dirname "${BASH_SOURCE[0]}")/build_functions.sh"
DEPENDENCY_VERSION="$(retrievePomProperty "dependency.alfresco-community-repo.version")"
# Either both the parent and the upstream dependency are the same, or else fail the build
if [ "${DEPENDENCY_VERSION}" != "$(retrievePomParentVersion)" ]; then
printf "Upstream dependency version (%s) is different then the project parent version!\n" "${DEPENDENCY_VERSION}"
exit 1
fi
# Prevent merging of any SNAPSHOT dependencies into the master or the release/* branches
if [[ $(isPullRequestBuild) && "${DEPENDENCY_VERSION}" =~ ^.+-SNAPSHOT$ && "${TRAVIS_BRANCH}" =~ ^master$|^release/.+$ ]] ; then
printf "PRs with SNAPSHOT dependencies are not allowed into master or release branches\n"
exit 1
fi
# Prevent release jobs from starting when there are SNAPSHOT upstream dependencies
if [[ "${DEPENDENCY_VERSION}" =~ ^.+-SNAPSHOT$ ]] && [ "${TRAVIS_BUILD_STAGE_NAME,,}" = "release" ] ; then
printf "Cannot release project with SNAPSHOT dependencies!\n"
exit 1
fi
UPSTREAM_REPO="github.com/Alfresco/alfresco-community-repo.git"
# For release jobs, check if the upstream dependency is the latest tag on the upstream repository (on the same branch)
if isBranchBuild && [ "${TRAVIS_BUILD_STAGE_NAME,,}" = "release" ] && [ "${DEPENDENCY_VERSION}" != "$(retieveLatestTag "${UPSTREAM_REPO}" "${TRAVIS_BRANCH}")" ] ; then
printf "Upstream dependency is not up to date with %s / %s\n" "${UPSTREAM_REPO}" "${TRAVIS_BRANCH}"
exit 1
fi
# Search, checkout and build the same branch on the upstream project in case of SNAPSHOT dependencies
# Otherwise, checkout the upstream tag and build its Docker image (use just "mvn package", without "mvn install")
if [[ "${DEPENDENCY_VERSION}" =~ ^.+-SNAPSHOT$ ]] ; then
pullAndBuildSameBranchOnUpstream "${UPSTREAM_REPO}" "-PcommunityDocker"
else
pullUpstreamTagAndBuildDockerImage "${UPSTREAM_REPO}" "${DEPENDENCY_VERSION}" "-PcommunityDocker"
fi
# Build the current project
mvn -B -V -q install -DskipTests -Dmaven.javadoc.skip=true -PcommunityDocker \
$([[ "${DEPENDENCY_VERSION}" =~ ^.+-SNAPSHOT$ ]] && echo "-Drepo.image.tag=latest")
popd
set +vex
echo "=========================== Finishing Build Script =========================="
+19 -13
View File
@@ -2,18 +2,18 @@
set +vx
function isPullRequestBuild() {
test "${PULL_REQUEST}" != "false"
test "${TRAVIS_PULL_REQUEST}" != "false"
}
function isBranchBuild() {
test "${PULL_REQUEST}" = "false"
test "${TRAVIS_PULL_REQUEST}" = "false"
}
function cloneRepo() {
local REPO="${1}"
local TAG_OR_BRANCH="${2}"
printf "Cloning \"%s\" on %s\n" "${TAG_OR_BRANCH}" "${REPO}"
printf "Clonning \"%s\" on %s\n" "${TAG_OR_BRANCH}" "${REPO}"
# clone the repository branch/tag
pushd "$(dirname "${BASH_SOURCE[0]}")/../../../" >/dev/null
@@ -67,7 +67,7 @@ function evaluatePomProperty() {
pushd "$(dirname "${BASH_SOURCE[0]}")/../../" >/dev/null
mvn -B -ntp -q help:evaluate -Dexpression="${KEY}" -DforceStdout
mvn -B -q help:evaluate -Dexpression="${KEY}" -DforceStdout
popd >/dev/null
}
@@ -82,9 +82,15 @@ function remoteBranchExists() {
function identifyUpstreamSourceBranch() {
local UPSTREAM_REPO="${1}"
# use the current branch name (or in case of PRs, the source branch name)
if remoteBranchExists "${UPSTREAM_REPO}" "${BRANCH_NAME}" ; then
echo "${BRANCH_NAME}"
# if it's a pull request, use the source branch name (if it exists)
if isPullRequestBuild && remoteBranchExists "${UPSTREAM_REPO}" "${TRAVIS_PULL_REQUEST_BRANCH}" ; then
echo "${TRAVIS_PULL_REQUEST_BRANCH}"
exit 0
fi
# otherwise use the current branch name (or in case of PRs, the target branch name)
if remoteBranchExists "${UPSTREAM_REPO}" "${TRAVIS_BRANCH}" ; then
echo "${TRAVIS_BRANCH}"
exit 0
fi
@@ -116,7 +122,7 @@ function buildUpstreamTag() {
cd "$(basename "${UPSTREAM_REPO%.git}")"
mvn -B -ntp -V clean package -DskipTests -Dmaven.javadoc.skip=true "-Dimage.tag=${TAG}" ${EXTRA_BUILD_ARGUMENTS}
mvn -B -V clean package -DskipTests -Dmaven.javadoc.skip=true "-Dimage.tag=${TAG}" ${EXTRA_BUILD_ARGUMENTS}
popd
}
@@ -129,8 +135,8 @@ function buildSameBranchOnUpstream() {
cd "$(basename "${UPSTREAM_REPO%.git}")"
mvn -B -ntp -V -q clean install -DskipTests -Dmaven.javadoc.skip=true ${EXTRA_BUILD_ARGUMENTS}
mvn -B -ntp -V -q install -DskipTests -f packaging/tests/pom.xml
mvn -B -V -q clean install -DskipTests -Dmaven.javadoc.skip=true ${EXTRA_BUILD_ARGUMENTS}
mvn -B -V -q install -DskipTests -f packaging/tests/pom.xml
popd
}
@@ -146,7 +152,7 @@ function pullUpstreamTagAndBuildDockerImage() {
cd "$(basename "${UPSTREAM_REPO%.git}")"
mvn -B -ntp -V clean package -DskipTests -Dmaven.javadoc.skip=true "-Dimage.tag=${TAG}" ${EXTRA_BUILD_ARGUMENTS}
mvn -B -V clean package -DskipTests -Dmaven.javadoc.skip=true "-Dimage.tag=${TAG}" ${EXTRA_BUILD_ARGUMENTS}
popd
}
@@ -163,8 +169,8 @@ function pullAndBuildSameBranchOnUpstream() {
cd "$(basename "${UPSTREAM_REPO%.git}")"
mvn -B -ntp -V -q clean install -DskipTests -Dmaven.javadoc.skip=true ${EXTRA_BUILD_ARGUMENTS}
mvn -B -ntp -V -q install -DskipTests -f packaging/tests/pom.xml
mvn -B -V -q clean install -DskipTests -Dmaven.javadoc.skip=true ${EXTRA_BUILD_ARGUMENTS}
mvn -B -V -q install -DskipTests -f packaging/tests/pom.xml
popd
}
View File
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
echo "=========================== Starting Copy to Release Bucket Script ==========================="
PS4="\[\e[35m\]+ \[\e[m\]"
set -vex
#
# Copy from S3 Release bucket to S3 eu.dl bucket
#
if [ -z "${RELEASE_VERSION}" ]; then
echo "Please provide a RELEASE_VERSION in the format <acs-version>-<additional-info> (7.0.1-EA or 7.0.1-SNAPSHOT)"
exit 1
fi
BUILD_NUMBER="$(aws s3 ls --recursive s3://alfresco-artefacts-staging/alfresco-content-services-community/release/${TRAVIS_BRANCH}/ | grep 'alfresco.war' | awk '{print $4}' | cut -d/ -f5 | sort -n -r | head -1)"
echo "Release Build Number: ${BUILD_NUMBER}"
SOURCE="s3://alfresco-artefacts-staging/alfresco-content-services-community/release/${TRAVIS_BRANCH}/${BUILD_NUMBER}"
DESTINATION="s3://eu.dl.alfresco.com/release/community/${RELEASE_VERSION}-build-${BUILD_NUMBER}"
printf "\n%s\n%s\n" "${SOURCE}" "${DESTINATION}"
aws s3 cp --acl private \
"${SOURCE}/alfresco.war" \
"${DESTINATION}/alfresco.war"
aws s3 cp --acl private \
"${SOURCE}/alfresco-content-services-community-distribution-${RELEASE_VERSION}.zip" \
"${DESTINATION}/alfresco-content-services-community-distribution-${RELEASE_VERSION}.zip"
set +vex
echo "=========================== Finishing Copy to Release Bucket Script =========================="
+8 -2
View File
@@ -5,11 +5,17 @@ set -vex
pushd "$(dirname "${BASH_SOURCE[0]}")/../../"
# Maven Setup
mkdir -p "${HOME}/.m2" && cp -f .travis.settings.xml "${HOME}/.m2/settings.xml"
find "${HOME}/.m2/repository/" -type d -name "*-SNAPSHOT*" | xargs -r -l rm -rf
# Docker Logins
echo "${DOCKERHUB_PASSWORD}" | docker login -u="${DOCKERHUB_USERNAME}" --password-stdin
echo "${QUAY_PASSWORD}" | docker login -u="${QUAY_USERNAME}" --password-stdin quay.io
if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then
echo "${DOCKERHUB_PASSWORD}" | docker login -u="${DOCKERHUB_USERNAME}" --password-stdin
echo "${QUAY_PASSWORD}" | docker login -u="${QUAY_USERNAME}" --password-stdin quay.io
fi
# not helpful in this script
# export HOST_IP=$(hostname -I | cut -f1 -d' ')
popd
set +vex
@@ -6,22 +6,20 @@ pushd "$(dirname "${BASH_SOURCE[0]}")/../../"
if [ -z "${RELEASE_VERSION}" ] || [ -z "${DEVELOPMENT_VERSION}" ]; then
echo "Please provide a Release and Development version in the format <acs-version>-<additional-info> (e.g. 7.2.0-A2)"
echo "Please provide a Release and Development version in the format <acs-version>-<additional-info> (7.0.1-EA or 7.0.1-SNAPSHOT)"
exit 1
fi
# Use full history for release
git checkout -B "${BRANCH_NAME}"
# Define git identity for commits
git checkout -B "${TRAVIS_BRANCH}"
# Add email to link commits to user
git config user.email "${GIT_EMAIL}"
git config user.name "${GIT_USERNAME}"
mvn -B \
-ntp \
-Prelease,all-tas-tests -Pags \
-Prelease,fullBuild,all-tas-tests \
-DreleaseVersion="${RELEASE_VERSION}" \
-DdevelopmentVersion="${DEVELOPMENT_VERSION}" \
"-Darguments=-Prelease,all-tas-tests -Pags -DskipTests -Dbuild-number=${BUILD_NUMBER}" \
"-Darguments=-Prelease,fullBuild,all-tas-tests -DskipTests -Dbuild-number=${TRAVIS_BUILD_NUMBER}" \
release:clean release:prepare release:perform \
-DscmCommentPrefix="[maven-release-plugin][skip ci] " \
-Dusername="${GIT_USERNAME}" \
@@ -9,7 +9,7 @@ pushd "$(dirname "${BASH_SOURCE[0]}")/../../"
#
if [ -z "${RELEASE_VERSION}" ]; then
echo "Please provide a RELEASE_VERSION in the format <acs-version>-<additional-info> (e.g. 7.2.0-A2)"
echo "Please provide a RELEASE_VERSION in the format <acs-version>-<additional-info> (7.0.1-EA or 7.0.1-SNAPSHOT)"
exit 1
fi
+4 -9
View File
@@ -1,9 +1,4 @@
SOLR6_TAG=2.0.20
ACTIVEMQ_TAG=6.2.0-jre17-rockylinux8
ELASTICSEARCH_TAG=8.17.0
BATCH_INDEXING_TAG=5.7.0-A.3
POSTGRES_TAG=17.9
MYSQL_TAG=8.0.30
MARIADB_TAG=11.8
OPENSEARCH_TAG=2.11.1
OPENSEARCH_DASHBOARDS_TAG=2.11.1
TRANSFORMERS_TAG=2.4.0
SOLR6_TAG=2.0.2-RC2
POSTGRES_TAG=13.1
ACTIVEMQ_TAG=5.16.1
@@ -1,6 +0,0 @@
#BUILDING CONTAINER FOR TAS TESTING WITH DIFFERENT JDBC CONNECTORS
ARG BASE_IMAGE
FROM $BASE_IMAGE
COPY tests/environment/alfresco-with-jdbc-drivers/*.jar /usr/local/tomcat/lib/
@@ -28,8 +28,7 @@ services:
-Ddb.url=jdbc:postgresql://postgres:5432/alfresco
-Dsolr.host=solr6
-Dsolr.port=8983
-Dsolr.secureComms=secret
-Dsolr.sharedSecret=secret
-Dsolr.secureComms=none
-Dsolr.base.url=/solr
-Dindex.subsystem.name=solr6
-Dalfresco.restApi.basicAuthScheme=true
@@ -64,19 +63,15 @@ services:
image: alfresco/alfresco-search-services:${SOLR6_TAG}
environment:
#Solr needs to know how to register itself with Alfresco
SOLR_ALFRESCO_HOST: "alfresco"
SOLR_ALFRESCO_PORT: "8080"
- SOLR_ALFRESCO_HOST=alfresco
- SOLR_ALFRESCO_PORT=8080
#Alfresco needs to know how to call solr
SOLR_SOLR_HOST: "solr6"
SOLR_SOLR_PORT: "8983"
- SOLR_SOLR_HOST=solr6
- SOLR_SOLR_PORT=8983
#Create the default alfresco and archive cores
SOLR_CREATE_ALFRESCO_DEFAULTS: "alfresco,archive"
- SOLR_CREATE_ALFRESCO_DEFAULTS=alfresco,archive
#HTTP by default
ALFRESCO_SECURE_COMMS: "secret"
JAVA_TOOL_OPTIONS:
"
-Dalfresco.secureComms.secret=secret
"
- ALFRESCO_SECURE_COMMS=none
ports:
- 8083:8983 #Browser port
+7 -12
View File
@@ -28,8 +28,7 @@ services:
-Ddb.url=jdbc:postgresql://postgres:5432/alfresco
-Dsolr.host=solr6
-Dsolr.port=8983
-Dsolr.secureComms=secret
-Dsolr.sharedSecret=secret
-Dsolr.secureComms=none
-Dsolr.base.url=/solr
-Dindex.subsystem.name=solr6
-Dalfresco.restApi.basicAuthScheme=true
@@ -67,19 +66,15 @@ services:
image: alfresco/alfresco-search-services:${SOLR6_TAG}
environment:
#Solr needs to know how to register itself with Alfresco
SOLR_ALFRESCO_HOST: "alfresco"
SOLR_ALFRESCO_PORT: "8080"
- SOLR_ALFRESCO_HOST=alfresco
- SOLR_ALFRESCO_PORT=8080
#Alfresco needs to know how to call solr
SOLR_SOLR_HOST: "solr6"
SOLR_SOLR_PORT: "8983"
- SOLR_SOLR_HOST=solr6
- SOLR_SOLR_PORT=8983
#Create the default alfresco and archive cores
SOLR_CREATE_ALFRESCO_DEFAULTS: "alfresco,archive"
- SOLR_CREATE_ALFRESCO_DEFAULTS=alfresco,archive
#HTTP by default
ALFRESCO_SECURE_COMMS: "secret"
JAVA_TOOL_OPTIONS:
"
-Dalfresco.secureComms.secret=secret
"
- ALFRESCO_SECURE_COMMS=none
ports:
- 8083:8983 #Browser port
+1 -4
View File
@@ -6,18 +6,15 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>acs-community-packaging</artifactId>
<version>26.2.0-A.25</version>
<version>7.0.1-SNAPSHOT</version>
</parent>
<modules>
<module>testcontainers-env</module>
<module>tas-restapi</module>
<module>tas-elasticsearch</module>
<module>tas-cmis</module>
<module>tas-email</module>
<module>tas-webdav</module>
<module>tas-integration</module>
<module>tas-distribution-zip</module>
</modules>
<properties>
+3 -3
View File
@@ -9,7 +9,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>content-repository-community-tests</artifactId>
<version>26.2.0-A.25</version>
<version>7.0.1-SNAPSHOT</version>
</parent>
<developers>
@@ -58,13 +58,13 @@
<dependencies>
<dependency>
<groupId>org.alfresco.tas</groupId>
<artifactId>cmis</artifactId>
<artifactId>alfresco-community-repo-cmis-test</artifactId>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.alfresco.tas</groupId>
<artifactId>cmis</artifactId>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
</dependencies>
@@ -0,0 +1,26 @@
# Root logger option
log4j.rootLogger=INFO, file, stdout
# Direct log messages to a log file
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./target/reports/alfresco-tas.log
log4j.appender.file.MaxBackupIndex=10
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%t] %d{HH:mm:ss} %-5p %c{1}:%L - %m%n
# Direct log messages to stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[%t] %d{HH:mm:ss} %-5p %c{1}:%L - %m%n
# TestRail particular log file
# Direct log messages to a log file
log4j.appender.testrailLog=org.apache.log4j.RollingFileAppender
log4j.appender.testrailLog.File=./target/reports/alfresco-testrail.log
log4j.appender.testrailLog.MaxBackupIndex=10
log4j.appender.testrailLog.layout=org.apache.log4j.PatternLayout
log4j.appender.testrailLog.layout.ConversionPattern=%d{HH:mm:ss} %-5p %c{1}:%L - %m%n
log4j.category.testrail=INFO, testrailLog
log4j.additivity.testrail=false
@@ -1,42 +0,0 @@
# Root logger option
rootLogger.level=info
rootLogger.appenderRef.stdout.ref=ConsoleAppender
rootLogger.appenderRef.rolling.ref=RollingAppender
###### File appender definition #######
appender.rolling.type=RollingFile
appender.rolling.name=RollingAppender
appender.rolling.fileName=./target/reports/alfresco-tas.log
appender.rolling.filePattern=./target/reports/alfresco-tas.log.%i
appender.rolling.layout.type=PatternLayout
appender.rolling.layout.pattern=[%t] %d{HH:mm:ss} %-5p %c{1}:%L - %replace{%m}{[\r\n]+}{}%n
appender.rolling.policies.type = Policies
appender.rolling.policies.size.type=SizeBasedTriggeringPolicy
appender.rolling.policies.size.size=10MB
appender.rolling.strategy.type=DefaultRolloverStrategy
appender.rolling.strategy.max=10
###### Console appender definition #######
appender.console.type=Console
appender.console.name=ConsoleAppender
appender.console.layout.type=PatternLayout
appender.console.layout.pattern=[%t] %d{HH:mm:ss} %-5p %c{1}:%L - %replace{%m}{[\r\n]+}{}%n
# TestRail particular log file
# Direct log messages to a log file
logger.testrail.name=testrail
logger.testrail.level=info
logger.testrail.additivity=false
logger.testrail.appenderRef.testrail.ref=TestrailAppender
appender.testrail.name=TestrailAppender
appender.testrail.type=RollingFile
appender.testrail.fileName=./target/reports/alfresco-testrail.log
appender.testrail.filePattern=./target/reports/alfresco-testrail.log.%i
appender.testrail.layout.type=PatternLayout
appender.testrail.layout.pattern=%d{HH:mm:ss} %-5p %c{1}:%L - %replace{%m}{[\r\n]+}{}%n
appender.testrail.policies.type=Policies
appender.testrail.policies.size.type=SizeBasedTriggeringPolicy
appender.testrail.policies.size.size=10MB
appender.testrail.strategy.type=DefaultRolloverStrategy
appender.testrail.strategy.max=10
-76
View File
@@ -1,76 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.alfresco.tas</groupId>
<artifactId>content-repository-community-distribution-test</artifactId>
<name>Community Distribution test</name>
<packaging>jar</packaging>
<parent>
<groupId>org.alfresco</groupId>
<artifactId>content-repository-community-tests</artifactId>
<version>26.2.0-A.25</version>
</parent>
<properties>
<dependency.assertj-core.version>3.13.2</dependency.assertj-core.version>
<suiteXmlFile>${project.basedir}/src/test/resources/distribution-suite.xml</suiteXmlFile>
</properties>
<dependencies>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.26.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.alfresco.tas</groupId>
<artifactId>restapi</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-model</artifactId>
<version>3.9.4</version>
</dependency>
</dependencies>
<profiles>
<profile>
<id>run-distribution-zip-contents-check</id>
<properties>
<suiteXmlFile>${project.basedir}/src/test/resources/distribution-suite.xml</suiteXmlFile>
</properties>
</profile>
</profiles>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<suiteXmlFiles>
<suiteXmlFile>${suiteXmlFile}</suiteXmlFile>
</suiteXmlFiles>
<argLine>
--illegal-access=warn
</argLine>
</configuration>
</plugin>
<!-- Build only test jar -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -1,128 +0,0 @@
/*
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2021 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.distribution;
import static org.assertj.core.api.Assertions.assertThat;
import java.io.File;
import java.io.FileReader;
import java.io.FilenameFilter;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.apache.maven.model.Model;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
import org.testng.annotations.Test;
public class CheckDistributionZipContents
{
public static final String ACS_PREFIX = "alfresco-content-services-community-distribution";
public static final String ACS_DIR_NAME = "distribution";
public static final String AGS_PREFIX = "alfresco-governance-services-community";
public static final String AGS_DIR_NAME = "distribution-ags";
public static final String FORMAT = ".zip";
@Test
public void testAcsDistributionZipContents() throws Exception
{
File filesList[] = getDistributionZip(ACS_DIR_NAME, ACS_PREFIX);
for (File file : filesList)
{
List<String> zipEntries = getZipEntries(file.getAbsolutePath());
assertThat(zipEntries).contains(
"keystore/metadata-keystore/keystore-passwords.properties",
"keystore/metadata-keystore/keystore",
"bin/alfresco-mmt.jar",
"bin/apply_amps.bat",
"bin/apply_amps.sh",
"web-server/webapps/ROOT.war",
"web-server/webapps/alfresco.war",
"web-server/webapps/share.war",
"web-server/webapps/_vti_bin.war",
"web-server/conf/Catalina/localhost/alfresco.xml",
"web-server/shared/classes/alfresco/web-extension/share-config-custom.xml");
}
}
@Test
public void testAgsDistributionZipContents() throws Exception
{
String repoVersion = getPomValues().getProperties().getProperty("dependency.alfresco-community-repo.version");
String shareVersion = getPomValues().getProperties().getProperty("dependency.alfresco-community-share.version");
File filesList[] = getDistributionZip(AGS_DIR_NAME, AGS_PREFIX);
for (File file : filesList)
{
List<String> zipEntries = getZipEntries(file.getAbsolutePath());
assertThat(zipEntries).contains(
"alfresco-governance-services-community-repo-" + repoVersion + ".amp",
"alfresco-governance-services-community-rest-api-explorer-" + repoVersion + ".war",
"alfresco-governance-services-community-share-" + shareVersion + ".amp");
}
}
private File[] getDistributionZip(String dirName, String prefix) throws Exception
{
String resourcePath = Paths.get("").toAbsolutePath().getParent().getParent().toString() + "/" + dirName + "/" + "target" + "/";
File distributionZip = new File(resourcePath);
FilenameFilter zipFileFilter = (dir, name) -> {
if (name.startsWith(prefix) && name.endsWith(FORMAT))
{
return true;
}
else
{
return false;
}
};
return distributionZip.listFiles(zipFileFilter);
}
private List<String> getZipEntries(String filePath) throws Exception
{
List<String> zipEntries = new ArrayList<>();
ZipFile zipFile = new ZipFile(new File(filePath));
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements())
{
ZipEntry entry = entries.nextElement();
zipEntries.add(entry.toString());
}
return zipEntries;
}
private Model getPomValues() throws Exception
{
String parentPom = Paths.get("").toAbsolutePath().getParent().getParent().toString() + "/pom.xml";
MavenXpp3Reader reader = new MavenXpp3Reader();
Model model = reader.read(new FileReader(parentPom));
return model;
}
}
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="Alfresco tests Community Distribution Zip" preserve-order="true" verbose="1">
<test name="distribution" verbose="3" preserve-order="true">
<classes>
<class name="org.alfresco.distribution.CheckDistributionZipContents"></class>
</classes>
</test>
</suite>
-135
View File
@@ -1,135 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.alfresco.tas</groupId>
<artifactId>content-repository-community-elasticsearch-test</artifactId>
<name>Elasticsearch test</name>
<packaging>jar</packaging>
<parent>
<groupId>org.alfresco</groupId>
<artifactId>content-repository-community-tests</artifactId>
<version>26.2.0-A.25</version>
</parent>
<profiles>
<profile>
<id>elastic</id>
<properties>
<suiteXmlFile>${project.basedir}/src/test/resources/test-suites/standard-elasticsearch-suites.xml</suiteXmlFile>
<search.engine.type />
<database.type />
</properties>
</profile>
<profile>
<id>elastic-basic-auth</id>
<properties>
<suiteXmlFile>${project.basedir}/src/test/resources/test-suites/elasticsearch-basic-auth-suite.xml</suiteXmlFile>
<search.engine.type />
<database.type />
</properties>
</profile>
</profiles>
<dependencies>
<dependency>
<groupId>org.alfresco.tas</groupId>
<artifactId>content-repository-community-testcontainers</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.alfresco.tas</groupId>
<artifactId>restapi</artifactId>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.alfresco.tas</groupId>
<artifactId>restapi</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.parsson</groupId>
<artifactId>parsson</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.opensearch.client</groupId>
<artifactId>opensearch-java</artifactId>
<version>${dependency.opensearch.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.eclipse</groupId>
<artifactId>yasson</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<redirectTestOutputToFile>false</redirectTestOutputToFile>
<suiteXmlFiles>
<suiteXmlFile>${suiteXmlFile}</suiteXmlFile>
</suiteXmlFiles>
<!-- Keeping illegal-access=warn for Java 11 compatibility, even though it has no effect on JDK 21 -->
<argLine>
--illegal-access=warn
--add-opens=java.base/java.lang=ALL-UNNAMED
</argLine>
</configuration>
</plugin>
<!-- Build only test jar -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -1,201 +0,0 @@
/*
* #%L
* Alfresco Tas Elasticsearch
* %%
* Copyright (C) 2026 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.elasticsearch;
import static org.alfresco.elasticsearch.SearchQueryService.req;
import static org.alfresco.utility.model.FileType.TEXT_PLAIN;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.alfresco.elasticsearch.utility.ElasticsearchRESTHelper;
import org.alfresco.rest.core.RestWrapper;
import org.alfresco.rest.model.RestCategoryLinkBodyModel;
import org.alfresco.rest.model.RestCategoryModel;
import org.alfresco.rest.search.SearchRequest;
import org.alfresco.tas.AlfrescoStackInitializer;
import org.alfresco.utility.data.DataContent;
import org.alfresco.utility.data.DataSite;
import org.alfresco.utility.data.DataUser;
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.alfresco.utility.network.ServerHealth;
import org.alfresco.utility.report.log.Step;
/**
* Tests to verify batch indexing of category using Elasticsearch.
*/
@SuppressWarnings({"PMD.JUnitTestsShouldIncludeAssert"})
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
initializers = AlfrescoStackInitializer.class)
public class CategoryIndexingTests extends AbstractTestNGSpringContextTests
{
private static final String TEST_PREFIX = RandomData.getRandomAlphanumeric() + "_";
private static final String CATEGORY_A_NAME = TEST_PREFIX + "CategoryA";
private static final String CATEGORY_B_NAME = TEST_PREFIX + "CategoryB";
private static final String ROOT_CATEGORY_ID = "-root-";
private static final RestCategoryModel ROOT_CATEGORY = RestCategoryModel.builder().id(ROOT_CATEGORY_ID).create();
@Autowired
private ElasticsearchRESTHelper helper;
@Autowired
private ServerHealth serverHealth;
@Autowired
private DataUser dataUser;
@Autowired
private DataSite dataSite;
@Autowired
private DataContent dataContent;
@Autowired
protected SearchQueryService searchQueryService;
@Autowired
protected RestWrapper restClient;
private RestCategoryModel categoryA;
private RestCategoryModel categoryB;
private UserModel testUser;
private SiteModel testSite;
private FileModel testFile;
private FolderModel testFolder;
/** Create a user, private site and two categories. Create a folder (in category B) containing a document (in category A and category B). */
@BeforeClass(alwaysRun = true)
public void dataPreparation()
{
serverHealth.isServerReachable();
serverHealth.assertServerIsOnline();
Step.STEP("Create two categories under the root.");
List<RestCategoryModel> categories = List.of(RestCategoryModel.builder().name(CATEGORY_A_NAME).create(),
RestCategoryModel.builder().name(CATEGORY_B_NAME).create());
List<RestCategoryModel> categoryList = restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI()
.usingCategory(ROOT_CATEGORY).createCategoriesList(categories).getEntries();
categoryA = categoryList.get(0).onModel();
categoryB = categoryList.get(1).onModel();
Step.STEP("Create a test user and use them to create a private site with a folder containing a document.");
testUser = dataUser.createRandomTestUser();
testSite = dataSite.usingUser(testUser).createPrivateRandomSite();
FolderModel folderModel = FolderModel.getRandomFolderModel();
testFolder = dataContent.usingUser(testUser).usingSite(testSite).createFolder(folderModel);
FileModel fileModel = FileModel.getRandomFileModel(TEXT_PLAIN);
testFile = dataContent.usingUser(testUser).usingResource(testFolder).createContent(fileModel);
Step.STEP("Assign the document to both categories and the folder to category B.");
RestCategoryLinkBodyModel categoryALink = RestCategoryLinkBodyModel.builder().categoryId(categoryA.getId()).create();
RestCategoryLinkBodyModel categoryBLink = RestCategoryLinkBodyModel.builder().categoryId(categoryB.getId()).create();
restClient.authenticateUser(testUser).withCoreAPI().usingNode(testFile).linkToCategory(categoryALink);
restClient.authenticateUser(testUser).withCoreAPI().usingNode(testFile).linkToCategory(categoryBLink);
restClient.authenticateUser(testUser).withCoreAPI().usingNode(testFolder).linkToCategory(categoryBLink);
}
/** Check we can find the document assigned to a category. */
@Test(groups = TestGroup.SEARCH)
public void testFindDocumentByCategory()
{
SearchRequest query = req("cm:categories:\"" + categoryA.getId() + "\"");
searchQueryService.expectResultsFromQuery(query, testUser, testFile.getName());
}
/** Check we can find the document assigned to a category even when the query includes a StoreRef. */
@Test(groups = TestGroup.SEARCH)
public void testFindDocumentByCategoryWithStoreRef()
{
SearchRequest query = req("cm:categories:\"workspace://SpacesStore/" + categoryA.getId() + "\"");
searchQueryService.expectResultsFromQuery(query, testUser, testFile.getName());
}
/** Check we can find the folder and document assigned to the other category. */
@Test(groups = TestGroup.SEARCH)
public void testFindFolderByCategory()
{
SearchRequest query = req("cm:categories:\"" + categoryB.getId() + "\"");
searchQueryService.expectResultsFromQuery(query, testUser, testFile.getName(), testFolder.getName());
}
/** Check we can find the document by the pseudo-path created for the category. */
@Test(groups = TestGroup.SEARCH)
public void testQueryByCategoryPseudoPath()
{
SearchRequest query = req("PATH:\"/cm:categoryRoot/cm:generalclassifiable/cm:" + CATEGORY_A_NAME + "/*\"");
searchQueryService.expectResultsFromQuery(query, testUser, testFile.getName());
}
/** Check we can find the document by a partial path match for the category. */
@Test(groups = TestGroup.SEARCH)
public void testQueryByPartialCategoryPathA()
{
SearchRequest query = req("PATH:\"//cm:" + CATEGORY_A_NAME + "/*\"");
searchQueryService.expectResultsFromQuery(query, testUser, testFile.getName());
}
/** Check we can find the document and folder by a partial path match for the second category. */
@Test(groups = TestGroup.SEARCH)
public void testQueryByPartialCategoryPathB()
{
SearchRequest query = req("PATH:\"//cm:" + CATEGORY_B_NAME + "/*\"");
searchQueryService.expectResultsFromQuery(query, testUser, testFile.getName(), testFolder.getName());
}
/** Check we cannot find the document by a partial path match for the category that has been applied to it and then deleted. */
@Test(groups = TestGroup.SEARCH)
public void testQueryByPathOnDeletedCategory() throws InterruptedException
{
// create 2 categories
final RestCategoryModel categoryToDelete = helper.createCategory();
final RestCategoryModel otherCategory = helper.createCategory();
// assign both categories to a document
helper.linkToCategory(testUser, testFile, categoryToDelete);
helper.linkToCategory(testUser, testFile, otherCategory);
// we can find the document by partial category paths after it is linked to both categories
SearchRequest categoryToDeleteQuery = req("PATH:\"//cm:" + categoryToDelete.getName() + "/*\"");
searchQueryService.expectResultsFromQuery(categoryToDeleteQuery, testUser, testFile.getName());
SearchRequest otherCategoryQuery = req("PATH:\"//cm:" + otherCategory.getName() + "/*\"");
searchQueryService.expectResultsFromQuery(otherCategoryQuery, testUser, testFile.getName());
// delete one of linked categories
restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI()
.usingCategory(categoryToDelete).deleteCategory();
restClient.assertStatusCodeIs(HttpStatus.NO_CONTENT);
// we cannot find the document by partial category path anymore after the category is deleted
searchQueryService.expectNoResultsFromQuery(categoryToDeleteQuery, testUser);
// we can still find the document by partial category path with a different category that has been assigned but not deleted
searchQueryService.expectResultsFromQuery(otherCategoryQuery, testUser, testFile.getName());
}
}
@@ -1,395 +0,0 @@
/*
* #%L
* Alfresco Tas Elasticsearch
* %%
* Copyright (C) 2026 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.elasticsearch;
import static org.apache.commons.lang3.StringUtils.EMPTY;
import static org.alfresco.elasticsearch.SearchQueryService.req;
import static org.alfresco.utility.data.RandomData.getRandomFile;
import static org.alfresco.utility.data.RandomData.getRandomName;
import static org.alfresco.utility.report.log.Step.STEP;
import java.time.Clock;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.alfresco.rest.model.RestTagModel;
import org.alfresco.rest.search.SearchRequest;
import org.alfresco.tas.AlfrescoStackInitializer;
import org.alfresco.utility.data.DataContent;
import org.alfresco.utility.data.DataUser;
import org.alfresco.utility.model.ContentModel;
import org.alfresco.utility.model.FileModel;
import org.alfresco.utility.model.FileType;
import org.alfresco.utility.model.FolderModel;
import org.alfresco.utility.model.TestGroup;
import org.alfresco.utility.model.UserModel;
import org.alfresco.utility.network.ServerHealth;
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
initializers = AlfrescoStackInitializer.class)
public class ElasticsearchBoostedSearchTests extends AbstractTestNGSpringContextTests
{
private static final String SEARCH_TERM = "mountain";
private static final String DIFFERENT_SEARCH_TERM = "fountain";
@Autowired
private ServerHealth serverHealth;
@Autowired
private DataUser dataUser;
@Autowired
private DataContent dataContent;
@Autowired
private SearchQueryService searchQueryService;
private UserModel testUser;
private ContentModel fileWithTermInName;
private ContentModel fileWithDifferentTermInName;
private ContentModel fileWithPhraseInContent;
private ContentModel fileWithTermInTitle;
private ContentModel folderWithTermInName;
private ContentModel folderWithTermInTitle;
private ContentModel testFolder;
private LocalDateTime creationTime;
private LocalDateTime afterCreationTime;
@BeforeClass(alwaysRun = true)
public void dataPreparation()
{
serverHealth.assertServerIsOnline();
STEP("Create a test user and few files and folders containing searched term in name, title and content");
testUser = dataUser.createRandomTestUser();
testFolder = createFolder(getRandomName("folder"));
fileWithTermInName = createFile(testFolder, SEARCH_TERM + ".txt", "dummy content");
fileWithDifferentTermInName = createFile(testFolder, DIFFERENT_SEARCH_TERM + ".txt", "dummy other content");
fileWithPhraseInContent = createFile(testFolder, getRandomFile(FileType.TEXT_PLAIN), "content with " + SEARCH_TERM + " searched phrase");
folderWithTermInName = createFolder(testFolder, SEARCH_TERM);
creationTime = ZonedDateTime.now(Clock.system(ZoneOffset.UTC)).toLocalDateTime();
fileWithTermInTitle = createRandomFileWithTitle(testFolder, SEARCH_TERM);
folderWithTermInTitle = createRandomFolderWithTitle(testFolder, SEARCH_TERM);
afterCreationTime = ZonedDateTime.now(Clock.system(ZoneOffset.UTC)).toLocalDateTime();
}
@AfterClass
public void dataCleanup()
{
STEP("Clean up created files, folders and user");
dataContent.usingAdmin().usingResource(testFolder).deleteContent();
dataUser.deleteUser(testUser);
}
@Test(groups = {TestGroup.SEARCH})
public void testAftsQuery_simpleTermBoost()
{
STEP("Search for files and folders by name with higher priority for files");
String boostedQuery = "TYPE:('cm:content'^2 OR 'cm:folder'^0.5) AND cm:name:" + SEARCH_TERM;
SearchRequest searchRequest = req("afts", boostedQuery);
searchQueryService.expectResultsInOrder(searchRequest, testUser, fileWithTermInName.getName(), folderWithTermInName.getName());
STEP("Search for files and folders by name with higher priority for folders");
String invertedBoost = "TYPE:('cm:content'^0.5 OR 'cm:folder'^2) AND cm:name:" + SEARCH_TERM;
searchRequest = req("afts", invertedBoost);
searchQueryService.expectResultsInOrder(searchRequest, testUser, folderWithTermInName.getName(), fileWithTermInName.getName());
}
@Test(groups = {TestGroup.SEARCH})
public void testAftsQuery_complexTermBoost()
{
STEP("Search for files and folders by name or title with higher priority for files by name");
String boostedQuery1 = "TYPE:('cm:content'^4 OR 'cm:folder'^0.5)^6 AND (cm:name:" + SEARCH_TERM + "^1.5 OR cm:title:" + SEARCH_TERM + "^0.5)";
SearchRequest searchRequest = req("afts", boostedQuery1);
searchQueryService.expectResultsInOrder(searchRequest, testUser, fileWithTermInName.getName(), fileWithTermInTitle.getName(), folderWithTermInName.getName(), folderWithTermInTitle.getName());
STEP("Search for files and folders by name or title with higher priority for folders by name");
String boostedQuery2 = "TYPE:('cm:content'^0.5 OR 'cm:folder'^4)^6 AND (cm:name:" + SEARCH_TERM + "^1.5 OR cm:title:" + SEARCH_TERM + "^0.5)";
searchRequest = req("afts", boostedQuery2);
searchQueryService.expectResultsInOrder(searchRequest, testUser, folderWithTermInName.getName(), folderWithTermInTitle.getName(), fileWithTermInName.getName(), fileWithTermInTitle.getName());
STEP("Search for files and folders by name or title with higher priority for files by title");
String boostedQuery3 = "TYPE:('cm:content'^4 OR 'cm:folder'^0.5)^6 AND (cm:name:" + SEARCH_TERM + "^0.5 OR cm:title:" + SEARCH_TERM + "^1.5)";
searchRequest = req("afts", boostedQuery3);
searchQueryService.expectResultsInOrder(searchRequest, testUser, fileWithTermInTitle.getName(), fileWithTermInName.getName(), folderWithTermInTitle.getName(), folderWithTermInName.getName());
STEP("Search for files and folders by name or title with higher priority for folders by title");
String boostedQuery4 = "TYPE:('cm:content'^0.5 OR 'cm:folder'^4)^6 AND (cm:name:" + SEARCH_TERM + "^0.5 OR cm:title:" + SEARCH_TERM + "^1.5)";
searchRequest = req("afts", boostedQuery4);
searchQueryService.expectResultsInOrder(searchRequest, testUser, folderWithTermInTitle.getName(), folderWithTermInName.getName(), fileWithTermInTitle.getName(), fileWithTermInName.getName());
}
@Test(groups = {TestGroup.SEARCH})
public void testAftsQuery_phraseBoost()
{
STEP("Search for files by name or TEXT with higher priority for name filter");
String boostedQuery = "TYPE:'cm:content' AND (cm:name:" + SEARCH_TERM + "^2 OR TEXT:'" + SEARCH_TERM + " searched'^0.1)";
SearchRequest searchRequest = req("afts", boostedQuery);
searchQueryService.expectResultsInOrder(searchRequest, testUser, fileWithTermInName.getName(), fileWithPhraseInContent.getName());
STEP("Search for files by name or TEXT with higher priority for TEXT filter");
String invertedBoost = "TYPE:'cm:content' AND (cm:name:" + SEARCH_TERM + "^0.1 OR TEXT:'" + SEARCH_TERM + " searched'^2)";
searchRequest = req("afts", invertedBoost);
searchQueryService.expectResultsInOrder(searchRequest, testUser, fileWithPhraseInContent.getName(), fileWithTermInName.getName());
}
@Test(groups = {TestGroup.SEARCH})
public void testAftsQuery_exactTermBoost()
{
STEP("Search for files by exact name or content with higher priority for exact name filter");
String boostedQuery = "TYPE:'cm:content' AND (=cm:name:" + SEARCH_TERM + ".txt^2 OR =cm:content:" + SEARCH_TERM + "^0.5)";
SearchRequest searchRequest = req("afts", boostedQuery);
searchQueryService.expectResultsInOrder(searchRequest, testUser, fileWithTermInName.getName(), fileWithPhraseInContent.getName());
STEP("Search for files by exact name or content with higher priority for exact content filter");
String invertedBoost = "TYPE:'cm:content' AND (=cm:name:" + SEARCH_TERM + ".txt^0.1 OR =cm:content:" + SEARCH_TERM + "^3)";
searchRequest = req("afts", invertedBoost);
searchQueryService.expectResultsInOrder(searchRequest, testUser, fileWithPhraseInContent.getName(), fileWithTermInName.getName());
}
@Test(groups = {TestGroup.SEARCH})
public void testAftsQuery_expandedTermBoost()
{
STEP("Search for files by expanded name and two different terms with higher priority for first term");
String boostedQuery = "TYPE:'cm:content' AND (~cm:name:" + SEARCH_TERM + "^3 OR ~cm:name:" + DIFFERENT_SEARCH_TERM + "^0.1)";
SearchRequest searchRequest = req("afts", boostedQuery);
searchQueryService.expectResultsInOrder(searchRequest, testUser, fileWithTermInName.getName(), fileWithDifferentTermInName.getName());
STEP("Search for files by expanded name and two different terms with higher priority for second term");
String invertedBoost = "TYPE:'cm:content' AND (~cm:name:" + SEARCH_TERM + "^0.1 OR ~cm:name:" + DIFFERENT_SEARCH_TERM + "^3)";
searchRequest = req("afts", invertedBoost);
searchQueryService.expectResultsInOrder(searchRequest, testUser, fileWithDifferentTermInName.getName(), fileWithTermInName.getName());
}
@Test(groups = {TestGroup.SEARCH})
public void testAftsQuery_fuzzyMatchingBoost()
{
STEP("Fuzzy matching search for files by name or title with higher priority for fuzzy name filter");
String boostedQuery = "TYPE:'cm:content' AND (cm:name:" + SEARCH_TERM + "~0.7^3 OR cm:title:" + SEARCH_TERM + "^0.01)";
SearchRequest searchRequest = req("afts", boostedQuery);
searchQueryService.expectResultsInOrder(searchRequest, testUser, fileWithTermInName.getName(), fileWithDifferentTermInName.getName(), fileWithTermInTitle.getName());
STEP("Fuzzy matching search for files by name or title with higher priority for fuzzy title filter");
String invertedBoost = "TYPE:'cm:content' AND (cm:name:" + SEARCH_TERM + "~0.7^0.01 OR cm:title:" + SEARCH_TERM + "^3)";
searchRequest = req("afts", invertedBoost);
searchQueryService.expectResultsInOrder(searchRequest, testUser, fileWithTermInTitle.getName(), fileWithTermInName.getName(), fileWithDifferentTermInName.getName());
}
/**
* Verify if boosts works fine with words proximity search. Files containing terms within specific distance from another one should be returned.
*/
@Test(groups = {TestGroup.SEARCH})
public void testAftsQuery_proximitySearchBoost()
{
STEP("Search for files by name or proximity TEXT with higher priority for name filter");
String boostedQuery = "TYPE:'cm:content' AND (cm:name:" + SEARCH_TERM + "^5 OR TEXT:(" + SEARCH_TERM + " *(1) phrase)^0.1)";
SearchRequest searchRequest = req("afts", boostedQuery);
searchQueryService.expectResultsInOrder(searchRequest, testUser, fileWithTermInName.getName(), fileWithPhraseInContent.getName());
STEP("Search for files by name or proximity TEXT with higher priority for proximity TEXT filter");
String invertedBoost = "TYPE:'cm:content' AND (cm:name:" + SEARCH_TERM + "^0.1 OR TEXT:(" + SEARCH_TERM + " *(1) phrase)^5)";
searchRequest = req("afts", invertedBoost);
searchQueryService.expectResultsInOrder(searchRequest, testUser, fileWithPhraseInContent.getName(), fileWithTermInName.getName());
}
@Test(groups = {TestGroup.SEARCH})
public void testAftsQuery_dateRangeSearchBoost()
{
String timeFrom = creationTime.format(DateTimeFormatter.ISO_DATE_TIME);
String timeTo = afterCreationTime.format(DateTimeFormatter.ISO_DATE_TIME);
STEP("Search for files and folders by name or creation time range with higher priority for name filter");
String boostedQuery = "TYPE:('cm:content'^16 OR 'cm:folder'^1) AND (cm:name:" + SEARCH_TERM + "^4 OR cm:created:['" + timeFrom + "' TO '" + timeTo + "']^0.1)^3";
SearchRequest searchRequest = req("afts", boostedQuery);
searchQueryService.expectResultsInOrder(searchRequest, testUser, fileWithTermInName.getName(), folderWithTermInName.getName(), fileWithTermInTitle.getName(), folderWithTermInTitle.getName());
STEP("Search for files and folders by name or creation time range with higher priority for creation time range filter");
String invertedBoost = "TYPE:('cm:content' OR 'cm:folder'^3) AND (cm:name:" + SEARCH_TERM + "^0.1 OR cm:created:['" + timeFrom + "' TO '" + timeTo + "']^4)^3";
searchRequest = req("afts", invertedBoost);
searchQueryService.expectResultsInOrder(searchRequest, testUser, folderWithTermInTitle.getName(), fileWithTermInTitle.getName(), folderWithTermInName.getName(), fileWithTermInName.getName());
}
/**
* Verify if boosts works fine with words range search. Files containing words from alphabetical range (from 'mountain' to 'phrase', this includes word 'other') should be returned.
*/
@Test(groups = {TestGroup.SEARCH})
public void testAftsQuery_wordsRangeSearchBoost()
{
String contentPath = "AND PATH:\"/app:company_home/cm:" + testFolder.getName() + "//*\"";
STEP("Search for files by name or words in content from given range with higher priority for name filter");
String boostedQuery = "TYPE:'cm:content' AND (cm:name:" + SEARCH_TERM + "^3 OR cm:content:" + SEARCH_TERM + "..phrase^0.1) " + contentPath;
SearchRequest searchRequest = req("afts", boostedQuery);
searchQueryService.expectResultsStartingWithOneOf(searchRequest, testUser, fileWithTermInName.getName());
searchQueryService.expectResultsFromQuery(searchRequest, testUser, fileWithTermInName.getName(), fileWithPhraseInContent.getName(), fileWithDifferentTermInName.getName());
STEP("Search for files by name or words in content from given range with higher priority for words in content from given range filter");
String invertedBoost = "TYPE:'cm:content' AND (cm:name:" + SEARCH_TERM + "^0.1 OR cm:content:" + SEARCH_TERM + "..phrase^3) " + contentPath;
searchRequest = req("afts", invertedBoost);
searchQueryService.expectResultsStartingWithOneOf(searchRequest, testUser, fileWithPhraseInContent.getName(), fileWithDifferentTermInName.getName());
searchQueryService.expectResultsFromQuery(searchRequest, testUser, fileWithTermInName.getName(), fileWithPhraseInContent.getName(), fileWithDifferentTermInName.getName());
}
@Test(groups = {TestGroup.SEARCH})
public void testAftsQuery_wildcardSearchBoost()
{
STEP("Search for files by wildcard name or title with higher priority for wildcard name filter");
String wildcardTerm = SEARCH_TERM.replaceFirst("^.", "?");
String boostedQuery = "TYPE:'cm:content' AND (cm:name:" + wildcardTerm + "^3 OR cm:title:" + SEARCH_TERM + "^0.1)";
SearchRequest searchRequest = req("afts", boostedQuery);
searchQueryService.expectResultsStartingWithOneOf(searchRequest, testUser, fileWithTermInName.getName(), fileWithDifferentTermInName.getName());
searchQueryService.expectResultsFromQuery(searchRequest, testUser, fileWithTermInName.getName(), fileWithDifferentTermInName.getName(), fileWithTermInTitle.getName());
STEP("Search for files by wildcard name or title with higher priority for wildcard title filter");
wildcardTerm = SEARCH_TERM.replaceFirst("^.", "*");
String invertedBoost = "TYPE:'cm:content' AND (cm:name:" + wildcardTerm + "^0.1 OR cm:title:" + SEARCH_TERM + "^3)";
searchRequest = req("afts", invertedBoost);
searchQueryService.expectResultsStartingWithOneOf(searchRequest, testUser, fileWithTermInTitle.getName());
searchQueryService.expectResultsFromQuery(searchRequest, testUser, fileWithTermInName.getName(), fileWithDifferentTermInName.getName(), fileWithTermInTitle.getName());
}
@Test(groups = {TestGroup.SEARCH})
public void testAftsQuery_invalidNegativeBoost()
{
STEP("Try to search for files by name using negative boost and expect 500 error response");
String boostedQuery = "TYPE:'cm:content'^-2 AND cm:name:" + SEARCH_TERM;
SearchRequest searchRequest = req("afts", boostedQuery);
searchQueryService.expectErrorFromQuery(searchRequest, testUser, HttpStatus.INTERNAL_SERVER_ERROR, EMPTY);
}
private ContentModel createRandomFileWithTitle(ContentModel parent, String title)
{
return createRandomFile(parent, title, null, null);
}
private ContentModel createRandomFileWithTitle(String title)
{
return createRandomFile(title, null, null);
}
private ContentModel createRandomFile(ContentModel parent, String title, String description, String tag)
{
return createFile(parent, getRandomFile(FileType.TEXT_PLAIN), "dummy content", title, description, tag);
}
private ContentModel createRandomFile(String title, String description, String tag)
{
return createFile(getRandomFile(FileType.TEXT_PLAIN), "dummy content", title, description, tag);
}
private ContentModel createFile(ContentModel parent, String filename, String content)
{
return createFile(parent, filename, content, null, null, null);
}
private ContentModel createFile(String filename, String content)
{
return createFile(filename, content, null, null, null);
}
private ContentModel createFile(ContentModel parent, String filename, String content, String title, String description, String tag)
{
FileModel fileModel = new FileModel(filename, FileType.TEXT_PLAIN, content);
fileModel.setTitle(title);
fileModel.setDescription(description);
FileModel file = dataContent
.usingAdmin()
.usingResource(parent)
.createContent(fileModel);
if (StringUtils.isNotBlank(tag))
{
dataContent
.usingAdmin()
.usingResource(file)
.addTagToContent(RestTagModel.builder().tag(tag).create());
}
return file;
}
private ContentModel createFile(String filename, String content, String title, String description, String tag)
{
ContentModel contentRoot = new ContentModel("-root-");
contentRoot.setNodeRef(contentRoot.getName());
return createFile(contentRoot, filename, content, title, description, tag);
}
private ContentModel createRandomFolderWithTitle(ContentModel parent, String title)
{
return createFolder(parent, getRandomName("folder"), title, null, null);
}
private ContentModel createFolder(ContentModel parent, String folderName)
{
return createFolder(parent, folderName, null, null, null);
}
private ContentModel createFolder(String folderName)
{
return createFolder(folderName, null, null, null);
}
private ContentModel createFolder(ContentModel parent, String folderName, String title, String description, String tag)
{
FolderModel folderModel = new FolderModel(folderName, title, description);
FolderModel folder = dataContent
.usingAdmin()
.usingResource(parent)
.createFolder(folderModel);
if (StringUtils.isNotBlank(tag))
{
dataContent
.usingAdmin()
.usingResource(folder)
.addTagToContent(RestTagModel.builder().tag(tag).create());
}
return folder;
}
private ContentModel createFolder(String folderName, String title, String description, String tag)
{
ContentModel contentRoot = new ContentModel("-root-");
contentRoot.setNodeRef(contentRoot.getName());
return createFolder(contentRoot, folderName, title, description, tag);
}
}
@@ -1,145 +0,0 @@
/*
* #%L
* Alfresco Tas Elasticsearch
* %%
* Copyright (C) 2026 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.elasticsearch;
import static org.alfresco.elasticsearch.SearchQueryService.req;
import static org.alfresco.tas.TestDataUtility.getAlphabeticUUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.alfresco.rest.search.SearchRequest;
import org.alfresco.tas.AlfrescoStackInitializer;
import org.alfresco.utility.data.DataContent;
import org.alfresco.utility.data.DataSite;
import org.alfresco.utility.data.DataUser;
import org.alfresco.utility.model.FileModel;
import org.alfresco.utility.model.FileType;
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.alfresco.utility.network.ServerHealth;
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
initializers = AlfrescoStackInitializer.class)
/**
* Tests for CMIS queries that require path indexing against Elasticsearch.
*/
public class ElasticsearchCMISPathTests extends AbstractTestNGSpringContextTests
{
private static final String PREFIX = getAlphabeticUUID();
private static final String FOLDER_0_NAME = PREFIX + "_folder0";
private static final String FOLDER_00_NAME = PREFIX + "_folder00";
private static final String FOLDER_000_NAME = PREFIX + "_folder000";
private static final String FOLDER_1_NAME = PREFIX + "_folder1";
private static final String DOC_0000_NAME = PREFIX + "_doc0000.txt";
private static final String DOC_00_NAME = PREFIX + "_doc00.txt";
private static final String DOC_01_NAME = PREFIX + "_doc01.txt";
private static final String DOC_10_NAME = PREFIX + "_doc10.txt";
@Autowired
private DataUser dataUser;
@Autowired
private DataContent dataContent;
@Autowired
private DataSite dataSite;
@Autowired
private ServerHealth serverHealth;
@Autowired
protected SearchQueryService searchQueryService;
private UserModel user;
private SiteModel siteModel;
private FolderModel folder0;
private FolderModel folder00;
private FolderModel folder000;
private FolderModel folder1;
private FileModel document0000;
private FileModel document00;
private FileModel document01;
private FileModel document10;
/**
* Data will be prepared using the schema below:
*
* <pre>
* Site
* + Document Library
* +-folder0
* | +-folder00
* | | +-folder000
* | | +-document0000
* | +-document00
* | +-document01
* +-folder1
* +-document10
* </pre>
*/
@BeforeClass(alwaysRun = true)
public void dataPreparation()
{
serverHealth.assertServerIsOnline();
user = dataUser.createRandomTestUser();
siteModel = dataSite.usingUser(user).createPrivateRandomSite();
folder0 = dataContent.usingUser(user).usingSite(siteModel).createFolderCmisApi(FOLDER_0_NAME);
folder1 = dataContent.usingUser(user).usingSite(siteModel).createFolderCmisApi(FOLDER_1_NAME);
folder00 = dataContent.usingUser(user).usingResource(folder0).createFolderCmisApi(FOLDER_00_NAME);
folder000 = dataContent.usingUser(user).usingResource(folder00).createFolderCmisApi(FOLDER_000_NAME);
document0000 = createContent(DOC_0000_NAME, "This is document 0000", folder000, user);
document00 = createContent(DOC_00_NAME, "This is document 00", folder0, user);
document01 = createContent(DOC_01_NAME, "This is document 01", folder0, user);
document10 = createContent(DOC_10_NAME, "This is document 10", folder1, user);
}
@Test(groups = TestGroup.SEARCH)
public void inTreeQuery_selectSubfolders()
{
SearchRequest query = req("cmis", "SELECT * FROM cmis:folder WHERE IN_TREE('" + folder0.getNodeRef() + "')");
searchQueryService.expectResultsFromQuery(query, user, FOLDER_00_NAME, FOLDER_000_NAME);
}
@Test(groups = TestGroup.SEARCH)
public void inTreeQuery_selectDocuments()
{
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE IN_TREE('" + folder0.getNodeRef() + "')");
searchQueryService.expectResultsFromQuery(query, user, DOC_0000_NAME, DOC_00_NAME, DOC_01_NAME);
}
private FileModel createContent(String filename, String content, FolderModel folderModel, UserModel user)
{
FileModel fileModel = new FileModel(filename, FileType.TEXT_PLAIN, content);
return dataContent.usingUser(user).usingResource(folderModel).createContent(fileModel);
}
}
@@ -1,604 +0,0 @@
/*
* #%L
* Alfresco Tas Elasticsearch
* %%
* Copyright (C) 2026 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.elasticsearch;
import static java.util.stream.Collectors.toList;
import static org.alfresco.elasticsearch.SearchQueryService.req;
import static org.alfresco.tas.TestDataUtility.getAlphabeticUUID;
import static org.alfresco.utility.report.log.Step.STEP;
import java.io.IOException;
import java.time.Instant;
import java.util.GregorianCalendar;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpStatus;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.alfresco.rest.search.SearchRequest;
import org.alfresco.tas.AlfrescoStackInitializer;
import org.alfresco.utility.Utility;
import org.alfresco.utility.constants.UserRole;
import org.alfresco.utility.data.DataContent;
import org.alfresco.utility.data.DataSite;
import org.alfresco.utility.data.DataUser;
import org.alfresco.utility.model.FileModel;
import org.alfresco.utility.model.FileType;
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.alfresco.utility.network.ServerHealth;
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
initializers = AlfrescoStackInitializer.class)
/**
* In this test we are verifying end-to-end the indexing and CMIS queries against Elasticsearch. In order to test ACLs we created 2 sites and 3 users.
*/
public class ElasticsearchCMISTests extends AbstractTestNGSpringContextTests
{
private static final String PREFIX = getAlphabeticUUID();
private static final String SUFFIX = getAlphabeticUUID();
private static final String UNIQUE_WORD = getAlphabeticUUID();
private static final String FILE_0_NAME = PREFIX + "_test.txt" + SUFFIX;
private static final String FILE_1_NAME = "internal_" + PREFIX + "_and_" + SUFFIX + ".txt";
private static final String FILE_2_NAME = PREFIX + "_user2doc_" + SUFFIX;
/** This is a file that user 1 doesn't have access to and so shouldn't be returned in their search results. */
private static final String USER_2_FILE_NAME = PREFIX + "_user2only_" + SUFFIX;
private static final String FOLDER_PREFIX = getAlphabeticUUID();
private static final String FOLDER_0_NAME = FOLDER_PREFIX + "_folder0";
private static final String FOLDER_1_NAME = FOLDER_PREFIX + "_folder1";
private static final int X_DIMENSION = 48;
@Autowired
private DataUser dataUser;
@Autowired
private DataContent dataContent;
@Autowired
private DataSite dataSite;
@Autowired
private ServerHealth serverHealth;
@Autowired
protected SearchQueryService searchQueryService;
private UserModel user1;
private UserModel user2;
private UserModel userMultiSite;
private SiteModel siteModel1;
private SiteModel siteModel2;
private FileModel file0;
private List<String> fileCreationDates;
private String file3Name;
/**
* Scoping predicate restricting the result universe to the 4 documents user1 owns in this class. Used to AND into negative / open-range queries so they aren't polluted by bootstrap content or by files created in other test classes that share the same Spring context.
*/
private String user1FilesScope;
/**
* Scoping predicate restricting to file3 (the only EXIF-bearing image we upload). Used to AND into negative / open-range integer queries on exif:pixelXDimension.
*/
private String file3Scope;
/**
* Data will be prepared using the schema below:
* <p>
* Site1: - Users: user1, userMultiSite - Documents: FILE_0_NAME (owner: user1), FILE_1_NAME (owner: user1), FILE_2_NAME (owner: user2), file3Name (owner: user1) - Folders: FOLDER_0_NAME (owner: user1), FOLDER_1_NAME (owner: user1)
* <p>
* Site2: - Users: user2, userMultiSite - Documents: USER_2_FILE_NAME (owner: user2)
*/
@BeforeClass(alwaysRun = true)
public void dataPreparation() throws IOException, InterruptedException
{
serverHealth.assertServerIsOnline();
user1 = dataUser.createRandomTestUser();
user2 = dataUser.createRandomTestUser();
userMultiSite = dataUser.createRandomTestUser();
siteModel1 = dataSite.usingUser(user1).createPrivateRandomSite();
siteModel2 = dataSite.usingUser(user2).createPrivateRandomSite();
dataUser.addUserToSite(user2, siteModel1, UserRole.SiteContributor);
dataUser.addUserToSite(userMultiSite, siteModel1, UserRole.SiteContributor);
dataUser.addUserToSite(userMultiSite, siteModel2, UserRole.SiteContributor);
file0 = createContent(FILE_0_NAME, "This is the first test containing " + UNIQUE_WORD, siteModel1, user1);
FileModel file1 = createContent(FILE_1_NAME, "This is another TEST file containing " + UNIQUE_WORD, siteModel1, user1);
FileModel file2 = createContent(FILE_2_NAME, "This Test file is owned by user2 " + UNIQUE_WORD, siteModel1, user2);
FileModel file3 = uploadDocument("test-data/alfresco-logo.png", user1, siteModel1);
file3Name = file3.getName();
fileCreationDates = getCreationDates(file0, file1, file2);
// Remove user 2 from site, but he keeps ownership on FILE_2_NAME.
dataUser.removeUserFromSite(user2, siteModel1);
// Also create another file that only user 2 has access to.
createContent(USER_2_FILE_NAME, "This is a test file that user1 does not have access to, but it still contains " + UNIQUE_WORD, siteModel2, user2);
dataContent.usingUser(user1).usingSite(siteModel1).createFolder(new FolderModel(FOLDER_0_NAME));
dataContent.usingUser(user1).usingSite(siteModel1).createFolder(new FolderModel(FOLDER_1_NAME));
// Pre-build scoping predicates used by the negative / open-range tests below.
user1FilesScope = "cmis:name IN ('" + FILE_0_NAME + "', '" + FILE_1_NAME + "', '" + FILE_2_NAME + "', '" + file3Name + "')";
file3Scope = "D.cmis:name = '" + file3Name + "'";
STEP("Wait for batch indexer to index the last-created node (FOLDER_1_NAME)");
SearchRequest probe = req("cmis", "SELECT * FROM cmis:folder WHERE cmis:name = '" + FOLDER_1_NAME + "'");
Utility.sleep(500, 60000, () -> searchQueryService.expectResultsFromQuery(probe, user1, FOLDER_1_NAME));
}
@Test(groups = TestGroup.SEARCH)
public void basicQuery()
{
SearchRequest query = req("cmis", "SELECT * FROM cmis:document");
searchQueryService.expectResultsInclude(query, user1, FILE_0_NAME, FILE_1_NAME, FILE_2_NAME, file3Name);
}
@Test(groups = TestGroup.SEARCH)
public void objectIdQuery()
{
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE cmis:objectId = '" + file0.getNodeRef() + "'");
searchQueryService.expectResultsFromQuery(query, user1, FILE_0_NAME);
}
@Test(groups = TestGroup.SEARCH)
public void objectTypeIdQuery()
{
SearchRequest query1 = req("cmis", "SELECT * FROM cmis:folder WHERE cmis:objectTypeId = 'cmis:folder'");
searchQueryService.expectResultsInclude(query1, user1, "documentLibrary", FOLDER_0_NAME, FOLDER_1_NAME, user1.getUsername());
SearchRequest query2 = req("cmis", "SELECT * FROM cmis:folder WHERE cmis:objectTypeId = 'F:st:site'");
searchQueryService.expectResultsInclude(query2, user1, siteModel1.getId());
}
@Test(groups = TestGroup.SEARCH)
public void baseTypeIdQuery()
{
SearchRequest query = req("cmis", "SELECT * FROM cmis:folder WHERE cmis:baseTypeId = 'cmis:folder'");
searchQueryService.expectResultsInclude(query, user1, "documentLibrary", FOLDER_0_NAME, FOLDER_1_NAME, user1.getUsername(), siteModel1.getId());
}
@Test(groups = TestGroup.SEARCH)
public void isNullQuery()
{
SearchRequest query = req("cmis", "SELECT * FROM cmis:folder WHERE cmis:description IS NULL");
searchQueryService.expectResultsInclude(query, user1, "documentLibrary", FOLDER_0_NAME, FOLDER_1_NAME, user1.getUsername());
}
@Test(groups = TestGroup.SEARCH)
public void isNotNullQuery()
{
SearchRequest query = req("cmis", "SELECT * FROM cmis:folder WHERE cmis:description IS NOT NULL");
searchQueryService.expectResultsInclude(query, user1, siteModel1.getId());
}
@Test(groups = TestGroup.SEARCH)
public void matchNamesLikePrefix()
{
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE cmis:name LIKE '" + PREFIX + "%'");
searchQueryService.expectResultsFromQuery(query, user1, FILE_0_NAME, FILE_1_NAME, FILE_2_NAME);
}
@Test(groups = TestGroup.SEARCH)
public void matchNamesLikeSuffix()
{
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE cmis:name LIKE '%" + SUFFIX + "'");
searchQueryService.expectResultsFromQuery(query, user1, FILE_0_NAME, FILE_1_NAME, FILE_2_NAME);
}
@Test(groups = TestGroup.SEARCH)
public void matchContentOfFile()
{
// Check the query is case insensitive.
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE CONTAINS('" + UNIQUE_WORD + "')");
searchQueryService.expectResultsFromQuery(query, user1, FILE_0_NAME, FILE_1_NAME, FILE_2_NAME);
}
@Test(groups = TestGroup.SEARCH)
public void checkPermissionForUser2()
{
// Reuse the prefix query to check which documents user2 can access.
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE cmis:name LIKE '" + PREFIX + "%'");
searchQueryService.expectResultsFromQuery(query, user2, FILE_2_NAME, USER_2_FILE_NAME);
}
@Test(groups = TestGroup.SEARCH)
public void matchDocumentName()
{
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE cmis:name = '" + FILE_0_NAME + "'");
searchQueryService.expectResultsFromQuery(query, user1, FILE_0_NAME);
}
@Test(groups = TestGroup.SEARCH)
public void doesNotMatchDocumentName()
{
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE " + user1FilesScope + " AND cmis:name <> '" + FILE_0_NAME + "'");
searchQueryService.expectResultsFromQuery(query, user1, FILE_1_NAME, FILE_2_NAME, file3Name);
}
@Test(groups = TestGroup.SEARCH)
public void checkOrderByAscSyntax()
{
SearchRequest query = req("cmis", "SELECT cmis:name FROM cmis:document WHERE cmis:name IN('" + FILE_0_NAME + "','" + FILE_1_NAME + "','" + FILE_2_NAME + "') ORDER BY cmis:name ASC");
searchQueryService.expectResultsInOrder(query, user1, true, FILE_0_NAME, FILE_1_NAME, FILE_2_NAME);
}
@Test(groups = TestGroup.SEARCH)
public void checkOrderByDescSyntax()
{
SearchRequest query = req("cmis", "SELECT cmis:name FROM cmis:document WHERE cmis:name IN('" + FILE_0_NAME + "','" + FILE_1_NAME + "','" + FILE_2_NAME + "') ORDER BY cmis:name DESC");
searchQueryService.expectResultsInOrder(query, user1, false, FILE_0_NAME, FILE_1_NAME, FILE_2_NAME);
}
@Test(groups = TestGroup.SEARCH)
public void checkInSyntax()
{
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE cmis:name IN ('" + FILE_0_NAME + "', '" + FILE_1_NAME + "')");
searchQueryService.expectResultsFromQuery(query, user1, FILE_0_NAME, FILE_1_NAME);
}
@Test(groups = TestGroup.SEARCH)
public void checkNotInSyntax()
{
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE " + user1FilesScope + " AND cmis:name NOT IN ('" + FILE_0_NAME + "', '" + FILE_1_NAME + "')");
searchQueryService.expectResultsFromQuery(query, user1, FILE_2_NAME, file3Name);
}
@Test(groups = TestGroup.SEARCH)
public void checkAfterDateSyntax()
{
String file0CreationDate = fileCreationDates.get(0);
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE cmis:creationDate > TIMESTAMP '" + file0CreationDate + "'");
searchQueryService.expectResultsFromQuery(query, user1, FILE_1_NAME, FILE_2_NAME, file3Name);
}
@Test(groups = TestGroup.SEARCH)
public void checkAfterOrSameDateSyntax()
{
String file0CreationDate = fileCreationDates.get(0);
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE cmis:creationDate >= TIMESTAMP '" + file0CreationDate + "'");
searchQueryService.expectResultsFromQuery(query, user1, FILE_0_NAME, FILE_1_NAME, FILE_2_NAME, file3Name);
}
@Test(groups = TestGroup.SEARCH)
public void checkBeforeDateSyntax()
{
String file2CreationDate = fileCreationDates.get(2);
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE " + user1FilesScope + " AND cmis:creationDate < TIMESTAMP '" + file2CreationDate + "'");
searchQueryService.expectResultsFromQuery(query, user1, FILE_0_NAME, FILE_1_NAME);
}
@Test(groups = TestGroup.SEARCH)
public void checkBeforeOrSameDateSyntax()
{
String file2CreationDate = fileCreationDates.get(2);
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE " + user1FilesScope + " AND cmis:creationDate <= TIMESTAMP '" + file2CreationDate + "'");
searchQueryService.expectResultsFromQuery(query, user1, FILE_0_NAME, FILE_1_NAME, FILE_2_NAME);
}
@Test(groups = TestGroup.SEARCH)
public void checkMatchesDateSyntax()
{
String file0CreationDate = fileCreationDates.get(0);
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE cmis:creationDate = TIMESTAMP '" + file0CreationDate + "'");
searchQueryService.expectResultsFromQuery(query, user1, FILE_0_NAME);
}
@Test(groups = TestGroup.SEARCH)
public void checkDoesNotMatchDateSyntax()
{
String file0CreationDate = fileCreationDates.get(0);
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE " + user1FilesScope + " AND cmis:creationDate <> TIMESTAMP '" + file0CreationDate + "'");
searchQueryService.expectResultsFromQuery(query, user1, FILE_1_NAME, FILE_2_NAME, file3Name);
}
@Test(groups = TestGroup.SEARCH)
public void checkInDatesSyntax()
{
String file0CreationDate = fileCreationDates.get(0);
String file1CreationDate = fileCreationDates.get(1);
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE cmis:creationDate IN (TIMESTAMP '" + file0CreationDate + "', TIMESTAMP '" + file1CreationDate + "')");
searchQueryService.expectResultsFromQuery(query, user1, FILE_0_NAME, FILE_1_NAME);
}
@Test(groups = TestGroup.SEARCH)
public void checkNotInDatesSyntax()
{
String file0CreationDate = fileCreationDates.get(0);
String file1CreationDate = fileCreationDates.get(1);
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE " + user1FilesScope + " AND cmis:creationDate NOT IN (TIMESTAMP '" + file0CreationDate + "', TIMESTAMP '" + file1CreationDate + "')");
searchQueryService.expectResultsFromQuery(query, user1, FILE_2_NAME, file3Name);
}
@Test(groups = TestGroup.SEARCH)
public void checkEqualIntegerSyntax()
{
SearchRequest query = req("cmis", "SELECT * FROM cmis:document AS D JOIN exif:exif AS E ON D.cmis:objectId = E.cmis:objectId WHERE E.exif:pixelXDimension = " + X_DIMENSION);
query.setInclude(List.of("properties"));
searchQueryService.expectResultsFromQuery(query, user1, file3Name);
}
@Test(groups = TestGroup.SEARCH)
public void checkDifferentThanIntegerSyntax()
{
SearchRequest query = req("cmis", "SELECT * FROM cmis:document AS D JOIN exif:exif AS E ON D.cmis:objectId = E.cmis:objectId WHERE " + file3Scope + " AND E.exif:pixelXDimension <> " + X_DIMENSION);
query.setInclude(List.of("properties"));
searchQueryService.expectNoResultsFromQuery(query, user1);
}
@Test(groups = TestGroup.SEARCH)
public void checkGreaterThanIntegerSyntax()
{
int lessThanX = X_DIMENSION - 1;
SearchRequest query = req("cmis", "SELECT * FROM cmis:document AS D JOIN exif:exif AS E ON D.cmis:objectId = E.cmis:objectId WHERE " + file3Scope + " AND E.exif:pixelXDimension > " + lessThanX);
query.setInclude(List.of("properties"));
searchQueryService.expectResultsFromQuery(query, user1, file3Name);
query = req("cmis", "SELECT * FROM cmis:document AS D JOIN exif:exif AS E ON D.cmis:objectId = E.cmis:objectId WHERE " + file3Scope + " AND E.exif:pixelXDimension > " + X_DIMENSION);
query.setInclude(List.of("properties"));
searchQueryService.expectNoResultsFromQuery(query, user1);
}
@Test(groups = TestGroup.SEARCH)
public void checkGreaterThanOrEqualIntegerSyntax()
{
SearchRequest query = req("cmis", "SELECT * FROM cmis:document AS D JOIN exif:exif AS E ON D.cmis:objectId = E.cmis:objectId WHERE " + file3Scope + " AND E.exif:pixelXDimension >= " + X_DIMENSION);
query.setInclude(List.of("properties"));
searchQueryService.expectResultsFromQuery(query, user1, file3Name);
int moreThanX = X_DIMENSION + 1;
query = req("cmis", "SELECT * FROM cmis:document AS D JOIN exif:exif AS E ON D.cmis:objectId = E.cmis:objectId WHERE " + file3Scope + " AND E.exif:pixelXDimension >= " + moreThanX);
query.setInclude(List.of("properties"));
searchQueryService.expectNoResultsFromQuery(query, user1);
}
@Test(groups = TestGroup.SEARCH)
public void checkLessThanIntegerSyntax()
{
int moreThanX = X_DIMENSION + 1;
SearchRequest query = req("cmis", "SELECT * FROM cmis:document AS D JOIN exif:exif AS E ON D.cmis:objectId = E.cmis:objectId WHERE E.exif:pixelXDimension < " + moreThanX);
query.setInclude(List.of("properties"));
searchQueryService.expectResultsFromQuery(query, user1, file3Name);
query = req("cmis", "SELECT * FROM cmis:document AS D JOIN exif:exif AS E ON D.cmis:objectId = E.cmis:objectId WHERE E.exif:pixelXDimension < " + X_DIMENSION);
query.setInclude(List.of("properties"));
searchQueryService.expectNoResultsFromQuery(query, user1);
}
@Test(groups = TestGroup.SEARCH)
public void checkLessThanOrEqualIntegerSyntax()
{
SearchRequest query = req("cmis", "SELECT * FROM cmis:document AS D JOIN exif:exif AS E ON D.cmis:objectId = E.cmis:objectId WHERE E.exif:pixelXDimension <= " + X_DIMENSION);
query.setInclude(List.of("properties"));
searchQueryService.expectResultsFromQuery(query, user1, file3Name);
int lessThanX = X_DIMENSION - 1;
query = req("cmis", "SELECT * FROM cmis:document AS D JOIN exif:exif AS E ON D.cmis:objectId = E.cmis:objectId WHERE E.exif:pixelXDimension <= " + lessThanX);
query.setInclude(List.of("properties"));
searchQueryService.expectNoResultsFromQuery(query, user1);
}
@Test(groups = TestGroup.SEARCH)
public void checkInIntegersSyntax()
{
SearchRequest query = req("cmis", "SELECT * FROM cmis:document AS D JOIN exif:exif AS E ON D.cmis:objectId = E.cmis:objectId WHERE E.exif:pixelXDimension IN (" + X_DIMENSION + ", " + 0 + ")");
query.setInclude(List.of("properties"));
searchQueryService.expectResultsFromQuery(query, user1, file3Name);
query = req("cmis", "SELECT * FROM cmis:document AS D JOIN exif:exif AS E ON D.cmis:objectId = E.cmis:objectId WHERE E.exif:pixelXDimension IN (" + 0 + ", " + -1 + ")");
query.setInclude(List.of("properties"));
searchQueryService.expectNoResultsFromQuery(query, user1);
}
@Test(groups = TestGroup.SEARCH)
public void checkNotInIntegersSyntax()
{
SearchRequest query = req("cmis", "SELECT * FROM cmis:document AS D JOIN exif:exif AS E ON D.cmis:objectId = E.cmis:objectId WHERE " + file3Scope + " AND E.exif:pixelXDimension NOT IN (" + X_DIMENSION + ", " + 0 + ")");
query.setInclude(List.of("properties"));
searchQueryService.expectNoResultsFromQuery(query, user1);
query = req("cmis", "SELECT * FROM cmis:document AS D JOIN exif:exif AS E ON D.cmis:objectId = E.cmis:objectId WHERE " + file3Scope + " AND E.exif:pixelXDimension NOT IN (" + 0 + ", " + -1 + ")");
query.setInclude(List.of("properties"));
searchQueryService.expectResultsFromQuery(query, user1, file3Name);
}
@Test(groups = TestGroup.SEARCH)
public void negative_basicCMISQuery_missingFrom()
{
// note: ideally 400 but currently 500 (also for Solr) :-(
SearchRequest query1 = req("cmis", "SELECT *");
searchQueryService.expectErrorFromQuery(query1, user1, HttpStatus.INTERNAL_SERVER_ERROR, "expecting FROM");
SearchRequest query2 = req("cmis", "SELECT * FROM");
searchQueryService.expectErrorFromQuery(query2, user1, HttpStatus.INTERNAL_SERVER_ERROR, "no viable alternative at input");
}
@Test(groups = TestGroup.SEARCH)
public void negative_basicCMISQuery_invalidType()
{
// note: ideally 400 but currently 500 (also for Solr) :-(
SearchRequest query = req("SELECT * FROM cmis:unknown");
searchQueryService.expectErrorFromQuery(query, user1, HttpStatus.INTERNAL_SERVER_ERROR, "Unknown property: {http://www.alfresco.org/model/content/1.0}cmis");
}
@Test(groups = TestGroup.SEARCH)
public void negative_objectTypeIdQuery_invalidType()
{
// note: ideally 400 but currently 500 (also for Solr) :-(
SearchRequest query = req("SELECT * FROM cmis:folder WHERE cmis:objectTypeId = 'unknown:site'");
searchQueryService.expectErrorFromQuery(query, user1, HttpStatus.INTERNAL_SERVER_ERROR, "Unknown property: {http://www.alfresco.org/model/content/1.0}cmis");
}
@Test(groups = TestGroup.SEARCH)
public void negative_isNullQuery_invalidField()
{
SearchRequest query = req("cmis", "SELECT * FROM cmis:folder WHERE cmis:unknown IS NULL");
searchQueryService.expectErrorFromQuery(query, user1, HttpStatus.INTERNAL_SERVER_ERROR, "Unknown column/property cmis:unknown");
}
@Test(groups = TestGroup.SEARCH)
public void negative_isNotNullQuery_invalidField()
{
SearchRequest query = req("cmis", "SELECT * FROM cmis:folder WHERE cmis:unknown IS NOT NULL");
searchQueryService.expectErrorFromQuery(query, user1, HttpStatus.INTERNAL_SERVER_ERROR, "Unknown column/property cmis:unknown");
}
@Test(groups = TestGroup.SEARCH)
public void negative_basicCMISQuery_invalidFieldName()
{
// note: ideally 400 but currently 500 (also for Solr) :-(
SearchRequest query1 = req("SELECT cmis:unknown FROM cmis:document");
searchQueryService.expectErrorFromQuery(query1, user1, HttpStatus.INTERNAL_SERVER_ERROR, "Unknown property: {http://www.alfresco.org/model/content/1.0}cmis");
SearchRequest query2 = req("SELECT cm:unknown FROM cmis:document");
searchQueryService.expectErrorFromQuery(query2, user1, HttpStatus.INTERNAL_SERVER_ERROR, "Unknown property: {http://www.alfresco.org/model/content/1.0}cm");
SearchRequest query3 = req("SELECT my:custom FROM cmis:document");
searchQueryService.expectErrorFromQuery(query3, user1, HttpStatus.INTERNAL_SERVER_ERROR, "Unknown property: {http://www.alfresco.org/model/content/1.0}my");
}
@Test(groups = TestGroup.SEARCH)
public void negative_baseTypeIdQuery_invalidType()
{
// note: ideally 400 but currently 500 (also for Solr) :-(
SearchRequest query = req("SELECT * FROM cmis:folder WHERE cmis:baseTypeId = 'cmis:unknown'");
searchQueryService.expectErrorFromQuery(query, user1, HttpStatus.INTERNAL_SERVER_ERROR, "Unknown property: {http://www.alfresco.org/model/content/1.0}cmis");
}
@Test(groups = TestGroup.SEARCH)
public void selectFolders()
{
SearchRequest query = req("cmis", "SELECT * FROM cmis:folder");
searchQueryService.expectResultsInclude(query, user1,
// We expect the site and the document library, ...
siteModel1.getId(), "documentLibrary",
// the two folders, ...
FOLDER_0_NAME, FOLDER_1_NAME,
// and the user home for the user performing the query.
user1.getUsername());
}
@Test(groups = TestGroup.SEARCH)
public void selectItems()
{
SearchRequest query = req("cmis", "SELECT * FROM cmis:item");
// This error is consistent with the way the DB and Solr handle the request.
searchQueryService.expectErrorFromQuery(query, user1, HttpStatus.INTERNAL_SERVER_ERROR, "Type is not queryable cmis:item");
}
@Test(groups = TestGroup.SEARCH)
public void selectObjects()
{
SearchRequest query = req("cmis", "SELECT * FROM cm:cmobject WHERE cmis:name IN ('" + FILE_0_NAME + "', '" + FOLDER_0_NAME + "')");
searchQueryService.expectResultsFromQuery(query, user1, FILE_0_NAME, FOLDER_0_NAME);
}
@Test(groups = TestGroup.SEARCH)
public void selectPeople()
{
SearchRequest query = req("cmis", "SELECT * FROM cm:person");
searchQueryService.expectNodeTypesFromQuery(query, user1, "cm:person");
}
@Test(groups = TestGroup.SEARCH)
public void tableAlias()
{
SearchRequest query = req("cmis", "SELECT d.cmis:name FROM cmis:document d WHERE d.cmis:name IN ('" + FILE_0_NAME + "')");
searchQueryService.expectResultsFromQuery(query, user1, FILE_0_NAME);
}
@Test(groups = TestGroup.SEARCH)
public void tableAliasWithAs()
{
SearchRequest query = req("cmis", "SELECT d.cmis:name FROM cmis:document AS d WHERE d.cmis:name IN ('" + FILE_0_NAME + "')");
searchQueryService.expectResultsFromQuery(query, user1, FILE_0_NAME);
}
@Test(groups = TestGroup.SEARCH)
public void tableAlias_mismatchedAliasInSelect()
{
SearchRequest query = req("cmis", "SELECT z.cmis:name FROM cmis:document d WHERE d.cmis:name IN ('" + FILE_0_NAME + "')");
searchQueryService.expectErrorFromQuery(query, user1, HttpStatus.INTERNAL_SERVER_ERROR, "No selector for z");
}
@Test(groups = TestGroup.SEARCH)
public void tableAlias_mismatchedAliasInWhere()
{
SearchRequest query = req("cmis", "SELECT d.cmis:name FROM cmis:document d WHERE z.cmis:name IN ('" + FILE_0_NAME + "')");
searchQueryService.expectErrorFromQuery(query, user1, HttpStatus.INTERNAL_SERVER_ERROR, "No selector for z");
}
@Test(groups = TestGroup.SEARCH)
public void tableJoin()
{
SearchRequest query = req("cmis", "SELECT t.*, d.* FROM cm:titled t JOIN cmis:document d ON t.cmis:objectId = d.cmis:objectId WHERE d.cmis:name = '" + FILE_0_NAME + "'");
searchQueryService.expectResultsFromQuery(query, user1, FILE_0_NAME);
}
private FileModel createContent(String filename, String content, SiteModel site, UserModel user)
{
FileModel fileModel = new FileModel(filename, FileType.TEXT_PLAIN, content);
return dataContent.usingUser(user).usingSite(site)
.createContent(fileModel);
}
private List<String> getCreationDates(FileModel... files)
{
return getCreationDates(user1, siteModel1, files);
}
private List<String> getCreationDates(UserModel user, SiteModel site, FileModel... files)
{
return List.of(files)
.stream()
.map(FileModel::getCmisLocation)
.map(cmisLocation -> dataContent.usingUser(user).usingSite(site).getCMISDocument(cmisLocation))
.map(cmisDocument -> cmisDocument.<GregorianCalendar> getProperty("cmis:creationDate"))
.map(creationDateProperty -> creationDateProperty.<GregorianCalendar> getValue())
.map(GregorianCalendar::toInstant)
.map(Instant::toString)
.collect(toList());
}
private FileModel uploadDocument(String filePath, UserModel user, SiteModel site) throws IOException
{
ClassPathResource toUpload = new ClassPathResource(filePath);
return dataContent.usingUser(user)
.usingSite(site)
.uploadDocument(toUpload.getFile());
}
}
@@ -1,311 +0,0 @@
/*
* #%L
* Alfresco Tas Elasticsearch
* %%
* Copyright (C) 2026 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.elasticsearch;
import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.OK;
import static org.testng.Assert.assertTrue;
import static org.alfresco.utility.data.RandomData.getRandomName;
import static org.alfresco.utility.report.log.Step.STEP;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.alfresco.dataprep.CMISUtil;
import org.alfresco.rest.core.RestWrapper;
import org.alfresco.rest.model.RestCategoryLinkBodyModel;
import org.alfresco.rest.model.RestCategoryModel;
import org.alfresco.rest.model.RestCategoryModelsCollection;
import org.alfresco.tas.AlfrescoStackInitializer;
import org.alfresco.utility.Utility;
import org.alfresco.utility.data.DataContent;
import org.alfresco.utility.data.DataSite;
import org.alfresco.utility.data.DataUser;
import org.alfresco.utility.model.FileModel;
import org.alfresco.utility.model.FolderModel;
import org.alfresco.utility.model.RepoTestModel;
import org.alfresco.utility.model.SiteModel;
import org.alfresco.utility.model.TestGroup;
import org.alfresco.utility.model.UserModel;
import org.alfresco.utility.network.ServerHealth;
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
initializers = AlfrescoStackInitializer.class)
public class ElasticsearchCategoriesCountTests extends AbstractTestNGSpringContextTests
{
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_COUNT = "count";
@Autowired
private ServerHealth serverHealth;
@Autowired
private DataUser dataUser;
@Autowired
private DataSite dataSite;
@Autowired
private DataContent dataContent;
@Autowired
private RestWrapper restClient;
private UserModel user;
private SiteModel site;
private RestCategoryModel categoryLinkedWithFolder;
private RestCategoryModel categoryLinkedWithFile;
private RestCategoryModel categoryLinkedWithBoth;
private RestCategoryModel notLinkedCategory;
@BeforeClass(alwaysRun = true)
public void dataPreparation() throws InterruptedException
{
serverHealth.assertServerIsOnline();
STEP("Create user and site");
user = dataUser.createRandomTestUser();
site = dataSite.usingUser(user).createPublicRandomSite();
STEP("Create a folder, file in it and few categories");
final FolderModel folder = dataContent.usingUser(user).usingSite(site).createFolder();
final 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(500, 60000, () -> restClient.authenticateUser(user)
.withCoreAPI()
.usingCategory(categoryLinkedWithBoth)
.include(INCLUDE_COUNT_PARAM)
.getCategory()
.assertThat()
.field(FIELD_COUNT)
.is(2));
}
@AfterClass
public void dataCleanup()
{
STEP("Remove site and user");
dataSite.usingUser(user).deleteSite(site);
dataUser.deleteUser(user);
}
/**
* 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);
}
private RestCategoryModelsCollection linkContentToCategories(final RepoTestModel node, final RestCategoryModel... categories)
{
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;
}
private RestCategoryModel prepareCategoryUnderRoot()
{
return prepareCategoryUnder(createCategoryModelWithId(ROOT_CATEGORY_ID));
}
private RestCategoryModel prepareCategoryUnder(final RestCategoryModel parentCategory)
{
final RestCategoryModel categoryModel = createCategoryModelWithName(getRandomName(CATEGORY_NAME_PREFIX));
final RestCategoryModel createdCategory = restClient.authenticateUser(dataUser.getAdminUser())
.withCoreAPI()
.usingCategory(parentCategory)
.createSingleCategory(categoryModel);
restClient.assertStatusCodeIs(CREATED);
return createdCategory;
}
private RestCategoryModel createCategoryModelWithId(final String id)
{
return createCategoryModelWithIdAndName(id, null);
}
private RestCategoryModel createCategoryModelWithName(final String name)
{
return createCategoryModelWithIdAndName(null, name);
}
private RestCategoryModel createCategoryModelWithIdAndName(final String id, final String name)
{
return RestCategoryModel.builder()
.id(id)
.name(name)
.create();
}
private RestCategoryLinkBodyModel createCategoryLinkModelWithId(final String id)
{
return RestCategoryLinkBodyModel.builder()
.categoryId(id)
.create();
}
}
@@ -1,250 +0,0 @@
/*
* #%L
* Alfresco Tas Elasticsearch
* %%
* Copyright (C) 2026 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.elasticsearch;
import static org.alfresco.utility.report.log.Step.STEP;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.alfresco.dataprep.CMISUtil;
import org.alfresco.rest.core.RestWrapper;
import org.alfresco.rest.model.RestTagModel;
import org.alfresco.rest.model.RestTagModelsCollection;
import org.alfresco.tas.AlfrescoStackInitializer;
import org.alfresco.utility.Utility;
import org.alfresco.utility.data.DataContent;
import org.alfresco.utility.data.DataSite;
import org.alfresco.utility.data.DataUser;
import org.alfresco.utility.data.RandomData;
import org.alfresco.utility.model.FileModel;
import org.alfresco.utility.model.SiteModel;
import org.alfresco.utility.model.TestGroup;
import org.alfresco.utility.model.UserModel;
import org.alfresco.utility.network.ServerHealth;
/**
* Tests to verify batch indexing of tags using Elasticsearch.
*/
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
initializers = AlfrescoStackInitializer.class)
public class ElasticsearchGetTagsTests extends AbstractTestNGSpringContextTests
{
@Autowired
private ServerHealth serverHealth;
@Autowired
private DataUser dataUser;
@Autowired
private DataSite dataSite;
@Autowired
private DataContent dataContent;
@Autowired
private RestWrapper restClient;
private UserModel user;
private SiteModel site;
private RestTagModel apple, banana, pineapple, winegrape, grapefruit, orange;
@BeforeClass(alwaysRun = true)
public void dataPreparation() throws InterruptedException
{
serverHealth.isServerReachable();
serverHealth.assertServerIsOnline();
STEP("Create user and site");
user = dataUser.createRandomTestUser();
site = dataSite.usingUser(user).createPublicRandomSite();
final FileModel document = dataContent.usingAdmin().usingSite(site).createContent(CMISUtil.DocumentType.TEXT_PLAIN);
STEP("Create few tags");
apple = restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI()
.createSingleTag(RestTagModel.builder().tag(RandomData.getRandomName("apple")).create());
banana = restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI()
.createSingleTag(RestTagModel.builder().tag(RandomData.getRandomName("banana")).create());
pineapple = restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI()
.createSingleTag(RestTagModel.builder().tag(RandomData.getRandomName("pineapple")).create());
winegrape = restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI()
.createSingleTag(RestTagModel.builder().tag(RandomData.getRandomName("winegrape")).create());
grapefruit = restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI()
.createSingleTag(RestTagModel.builder().tag(RandomData.getRandomName("grapefruit")).create());
orange = restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI()
.createSingleTag(RestTagModel.builder().tag(RandomData.getRandomName("orange")).create());
STEP("Wait for indexing to complete");
Utility.sleep(500, 60000, () -> restClient.authenticateUser(dataUser.getAdminUser())
.withParams("where=(tag MATCHES ('oran*'))")
.withCoreAPI()
.getTags()
.assertThat()
.entrySetContains("tag", orange.getTag().toLowerCase()));
// Step 2: now attach apple to document - tag node is guaranteed to be in ES
STEP("Attach apple tag to document");
restClient.authenticateUser(user).withCoreAPI().usingResource(document)
.addTag(apple.getTag());
}
@AfterClass
public void dataCleanup()
{
STEP("Remove created tags");
restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI().usingTag(apple).deleteTag();
restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI().usingTag(banana).deleteTag();
restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI().usingTag(pineapple).deleteTag();
restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI().usingTag(winegrape).deleteTag();
restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI().usingTag(grapefruit).deleteTag();
restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI().usingTag(orange).deleteTag();
STEP("Remove site and user");
dataSite.usingUser(user).deleteSite(site);
dataUser.deleteUser(user);
}
/**
* 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");
final RestTagModelsCollection returnedCollection = restClient.authenticateUser(user)
.withParams("where=(tag='" + apple.getTag() + "')")
.withCoreAPI()
.getTags();
restClient.assertStatusCodeIs(HttpStatus.OK);
returnedCollection.assertThat()
.entrySetMatches("tag", Set.of(apple.getTag().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");
final RestTagModelsCollection returnedCollection = restClient.authenticateUser(user)
.withParams("where=(tag IN ('" + apple.getTag() + "', '" + banana.getTag() + "'))")
.withCoreAPI()
.getTags();
restClient.assertStatusCodeIs(HttpStatus.OK);
returnedCollection.assertThat()
.entrySetMatches("tag", Set.of(apple.getTag().toLowerCase(), banana.getTag().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");
final RestTagModelsCollection returnedCollection = restClient.authenticateUser(user)
.withParams("where=(tag MATCHES ('*an*'))")
.withCoreAPI()
.getTags();
restClient.assertStatusCodeIs(HttpStatus.OK);
returnedCollection.assertThat()
.entrySetContains("tag", banana.getTag().toLowerCase(), orange.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 three items in result");
final RestTagModelsCollection returnedCollection = restClient.authenticateUser(user)
.withParams("where=(tag='" + orange.getTag() + "' OR tag MATCHES ('*grape*'))")
.withCoreAPI()
.getTags();
restClient.assertStatusCodeIs(HttpStatus.OK);
returnedCollection.assertThat()
.entrySetMatches("tag", Set.of(orange.getTag().toLowerCase(), grapefruit.getTag().toLowerCase(), winegrape.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 three items in result");
final RestTagModelsCollection returnedCollection = restClient.authenticateUser(user)
.withParams("where=(tag MATCHES ('*apple*') OR tag MATCHES ('grape*'))")
.withCoreAPI()
.getTags();
restClient.assertStatusCodeIs(HttpStatus.OK);
returnedCollection.assertThat()
.entrySetMatches("tag", Set.of(apple.getTag().toLowerCase(), pineapple.getTag().toLowerCase(), grapefruit.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");
restClient.authenticateUser(user)
.withParams("where=(name=apple)")
.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");
restClient.authenticateUser(user)
.withParams("where=(tag=apple AND tag IN ('banana', 'melon'))")
.withCoreAPI()
.getTags();
restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST)
.assertLastError().containsSummary("An invalid WHERE query was received. Unsupported Predicate");
}
}
@@ -1,266 +0,0 @@
/*
* #%L
* Alfresco Tas Elasticsearch
* %%
* Copyright (C) 2026 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.elasticsearch;
import static java.util.Objects.requireNonNull;
import static java.util.Optional.ofNullable;
import static java.util.function.Predicate.not;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Stream.concat;
import static java.util.stream.Stream.of;
import static org.apache.http.entity.ContentType.APPLICATION_JSON;
import static org.assertj.core.api.Assertions.assertThat;
import static org.alfresco.elasticsearch.SearchQueryService.req;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import com.google.common.io.CharStreams;
import com.google.gson.Gson;
import org.apache.http.HeaderElement;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.StringEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.alfresco.dataprep.AlfrescoHttpClient;
import org.alfresco.dataprep.AlfrescoHttpClientFactory;
import org.alfresco.rest.search.SearchRequest;
import org.alfresco.tas.AlfrescoStackInitializer;
import org.alfresco.utility.data.DataUser;
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
initializers = AlfrescoStackInitializer.class)
public class ElasticsearchIsUnsetTest extends AbstractTestNGSpringContextTests
{
private static final String CM_TITLED_ASPECT = "cm:titled";
private static final String CM_TITLE_PROPERTY = "cm:title";
private static final String CM_DESCRIPTION_PROPERTY = "cm:description";
private final Gson gson = new Gson();
@Autowired
SearchQueryService searchQueryService;
@Autowired
private AlfrescoHttpClientFactory alfrescoHttpClientFactory;
@Autowired
private DataUser dataUser;
private AlfrescoHttpClient alfrescoClient;
private AlfNode testNode;
@BeforeClass
public void setUp() throws IOException
{
alfrescoClient = alfrescoHttpClientFactory.getObject();
testNode = createNodeWithTitleButWithoutDescription();
}
@AfterClass
public void tearDown()
{
alfrescoClient.close();
}
@Test
public void testElasticsearchIsUnsetQuery() throws Exception
{
assertNodeIsReadyForTesting();
// cm:title is set, we should have no result
shouldNotFindNode("ISUNSET:\"" + CM_TITLE_PROPERTY + "\"");
shouldFindNode("NOT ISUNSET:\"" + CM_TITLE_PROPERTY + "\"");
// cm:description is not set, we should have a result
shouldFindNode("ISUNSET:\"" + CM_DESCRIPTION_PROPERTY + "\"");
shouldNotFindNode("NOT ISUNSET:\"" + CM_DESCRIPTION_PROPERTY + "\"");
removeCmTitledAspect();
// ISUNSET is type aware, no cm:titled aspect so no result
shouldNotFindNode("ISUNSET:\"" + CM_TITLE_PROPERTY + "\"");
shouldNotFindNode("ISUNSET:\"" + CM_DESCRIPTION_PROPERTY + "\"");
shouldFindNode("NOT ISUNSET:\"" + CM_TITLE_PROPERTY + "\"");
shouldFindNode("NOT ISUNSET:\"" + CM_DESCRIPTION_PROPERTY + "\"");
restoreCmTitledAspect();
// cm:titled aspect is back but cm:title property has been "unset"
shouldFindNode("ISUNSET:\"" + CM_TITLE_PROPERTY + "\"");
shouldFindNode("ISUNSET:\"" + CM_DESCRIPTION_PROPERTY + "\"");
shouldNotFindNode("NOT ISUNSET:\"" + CM_TITLE_PROPERTY + "\"");
shouldNotFindNode("NOT ISUNSET:\"" + CM_DESCRIPTION_PROPERTY + "\"");
}
private void restoreCmTitledAspect() throws IOException
{
setAspects(testNode.getAspects());
}
private void removeCmTitledAspect() throws IOException
{
final List<String> noCmTitled = testNode.getAspects().stream().filter(not(CM_TITLED_ASPECT::equals)).toList();
setAspects(noCmTitled);
}
private void assertNodeIsReadyForTesting()
{
assertThat(testNode.getAspects()).contains(CM_TITLED_ASPECT);
shouldFindNode();
}
private void shouldFindNode(String... conjunctions)
{
searchQueryService.expectResultsInclude(searchByName(conjunctions), dataUser.getAdminUser(), testNode.getName());
}
private void shouldNotFindNode(String... conjunctions)
{
searchQueryService.expectNoResultsFromQuery(searchByName(conjunctions), dataUser.getAdminUser());
}
private SearchRequest searchByName(String... conjunctions)
{
return req(concat(of("cm:name:\"" + testNode.getName() + "\""), of(conjunctions)).collect(joining(" AND ")));
}
private void setAspects(Collection<String> aspectsToSet) throws IOException
{
final URI nodeURI = URI.create(alfrescoClient.getApiVersionUrl()).resolve("nodes/" + testNode.getId());
final HttpPut putRequest = new HttpPut(nodeURI);
final String jsonBody = toJsonString(Map.of("aspectNames", aspectsToSet));
putRequest.setEntity(new StringEntity(jsonBody, APPLICATION_JSON));
nodeApiRequest(putRequest, 200);
}
private AlfNode createNodeWithTitleButWithoutDescription() throws IOException
{
final URI parentURI = URI.create(alfrescoClient.getApiVersionUrl()).resolve("nodes/-my-/children");
final HttpPost postRequest = new HttpPost(parentURI);
final String jsonBody = toJsonString(
Map.of("name", uniqueName(),
"nodeType", "cm:content",
"properties", Map.of("cm:title", "UnsetTestingDocument")));
postRequest.setEntity(new StringEntity(jsonBody, APPLICATION_JSON));
return nodeApiRequest(postRequest, 201);
}
private AlfNode nodeApiRequest(HttpRequestBase request, int expectedResponseCode) throws IOException
{
final HttpResponse response = alfrescoClient.executeAsAdmin(request);
assertThat(response.getStatusLine().getStatusCode()).isEqualTo(expectedResponseCode);
final HttpEntity entity = response.getEntity();
assertThat(entity).isNotNull();
assertThat(entity.getContentType()).isNotNull();
assertThat(entity.getContentType().getElements()).hasSize(1);
final HeaderElement contentTypeHeader = entity.getContentType().getElements()[0];
assertThat(contentTypeHeader.getName()).isEqualTo(APPLICATION_JSON.getMimeType());
final Charset contentCharset = ofNullable(contentTypeHeader.getParameterByName("charset"))
.map(NameValuePair::getValue)
.map(Charset::forName)
.orElse(StandardCharsets.UTF_8);
try (var reader = new InputStreamReader(entity.getContent(), contentCharset))
{
return asNode((Map<String, ?>) fromJsonString(CharStreams.toString(reader)).get("entry"));
}
}
private AlfNode asNode(Map<String, ?> map)
{
return new AlfNode(map);
}
private String uniqueName()
{
return "Name-" + UUID.randomUUID();
}
private String toJsonString(Map<String, ?> map)
{
return gson.toJson(map);
}
private Map<String, ?> fromJsonString(String string)
{
return gson.fromJson(string, Map.class);
}
private static class AlfNode
{
private final Map<String, ?> nodeMap;
private AlfNode(Map<String, ?> nodeMap)
{
this.nodeMap = requireNonNull(nodeMap);
}
public String getId()
{
return requireNonNull((String) nodeMap.get("id"));
}
public Set<String> getAspects()
{
return ofNullable(nodeMap.get("aspectNames"))
.map(c -> (Collection<String>) c)
.map(Set::copyOf)
.orElseGet(Set::of);
}
public String getName()
{
return (String) nodeMap.get("name");
}
}
}
@@ -1,139 +0,0 @@
/*
* #%L
* Alfresco Tas Elasticsearch
* %%
* Copyright (C) 2026 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.elasticsearch;
import static org.alfresco.elasticsearch.SearchQueryService.req;
import static org.alfresco.tas.TestDataUtility.getAlphabeticUUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.alfresco.rest.search.RestRequestLimitsModel;
import org.alfresco.rest.search.SearchRequest;
import org.alfresco.tas.AlfrescoStackInitializer;
import org.alfresco.utility.constants.UserRole;
import org.alfresco.utility.data.DataContent;
import org.alfresco.utility.data.DataSite;
import org.alfresco.utility.data.DataUser;
import org.alfresco.utility.model.FileModel;
import org.alfresco.utility.model.FileType;
import org.alfresco.utility.model.SiteModel;
import org.alfresco.utility.model.TestGroup;
import org.alfresco.utility.model.UserModel;
import org.alfresco.utility.network.ServerHealth;
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml", initializers = AlfrescoStackInitializer.class)
/**
* In this test we are verifying the Track Total Hits feature
*/
public class ElasticsearchLimitTests extends AbstractTestNGSpringContextTests
{
private static final String PREFIX = getAlphabeticUUID() + "_";
private static final int TOTAL_DOCUMENT_COUNT = 20;
@Autowired
private DataUser dataUser;
@Autowired
private DataContent dataContent;
@Autowired
private DataSite dataSite;
@Autowired
private ServerHealth serverHealth;
@Autowired
protected SearchQueryService searchQueryService;
private UserModel userSite1;
private SiteModel siteModel1;
@BeforeClass(alwaysRun = true)
public void dataPreparation()
{
serverHealth.assertServerIsOnline();
userSite1 = dataUser.createRandomTestUser();
siteModel1 = dataSite.usingUser(userSite1).createPrivateRandomSite();
dataUser.addUserToSite(userSite1, siteModel1, UserRole.SiteContributor);
for (int i = 0; i < TOTAL_DOCUMENT_COUNT; i++)
{
createContent(PREFIX + i + ".txt", "Document " + i, siteModel1, userSite1);
}
}
@Test(groups = TestGroup.SEARCH)
public void searchUsingTotalHitsLimitDefaultValue()
{
SearchRequest request = req("cm:name:" + PREFIX + "*.txt");
// Without setting limits, we should get all up to 10k
searchQueryService.expectTotalHitsFromQuery(request, userSite1, TOTAL_DOCUMENT_COUNT);
}
@Test(groups = TestGroup.SEARCH)
public void searchUsingTotalHitsLimitZeroShouldDefault()
{
SearchRequest request = req("cm:name:" + PREFIX + "*.txt");
// Setting trackTotalHitsLimit to 0 should behave as not setting it, counting all up to 10k
request.setLimits(new RestRequestLimitsModel(null, null, 0));
searchQueryService.expectTotalHitsFromQuery(request, userSite1, TOTAL_DOCUMENT_COUNT);
}
@Test(groups = TestGroup.SEARCH)
public void searchUsingTotalHitsLimit()
{
SearchRequest request = req("cm:name:" + PREFIX + "*.txt");
// Setting trackTotalHitsLimit to 10 should only count up to 10
request.setLimits(new RestRequestLimitsModel(null, null, 10));
searchQueryService.expectTotalHitsFromQuery(request, userSite1, 10);
}
@Test(groups = TestGroup.SEARCH)
public void searchUsingTotalHitsUnlimited()
{
SearchRequest request = req("cm:name:" + PREFIX + "*.txt");
// Setting trackTotalHitsLimit to -1 should only count up to max int
request.setLimits(new RestRequestLimitsModel(null, null, -1));
searchQueryService.expectTotalHitsFromQuery(request, userSite1, TOTAL_DOCUMENT_COUNT);
}
private FileModel createContent(String filename, String content, SiteModel site, UserModel user)
{
FileModel fileModel = new FileModel(filename, FileType.TEXT_PLAIN, content);
return dataContent.usingUser(user).usingSite(site).createContent(fileModel);
}
}
@@ -1,263 +0,0 @@
/*
* #%L
* Alfresco Tas Elasticsearch
* %%
* Copyright (C) 2026 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.elasticsearch;
import static java.util.Arrays.asList;
import static org.alfresco.elasticsearch.SearchQueryService.req;
import static org.alfresco.tas.TestDataUtility.getAlphabeticUUID;
import java.util.Map;
import java.util.function.Predicate;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.json.simple.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.alfresco.dataprep.AlfrescoHttpClient;
import org.alfresco.dataprep.AlfrescoHttpClientFactory;
import org.alfresco.rest.search.RestRequestQueryModel;
import org.alfresco.rest.search.SearchNodeModel;
import org.alfresco.rest.search.SearchRequest;
import org.alfresco.tas.AlfrescoStackInitializer;
import org.alfresco.utility.constants.UserRole;
import org.alfresco.utility.data.DataContent;
import org.alfresco.utility.data.DataSite;
import org.alfresco.utility.data.DataUser;
import org.alfresco.utility.model.FileModel;
import org.alfresco.utility.model.FileType;
import org.alfresco.utility.model.SiteModel;
import org.alfresco.utility.model.TestGroup;
import org.alfresco.utility.model.UserModel;
import org.alfresco.utility.network.ServerHealth;
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
initializers = AlfrescoStackInitializer.class)
/**
* In this test we are verifying end-to-end the indexing and search in Elasticsearch. In order to test ACLs we created 2 sites and 3 users.
*/
public class ElasticsearchLiveIndexingTests extends AbstractTestNGSpringContextTests
{
private static final String PREFIX = getAlphabeticUUID() + "_";
private static final String UNIQUE_WORD = getAlphabeticUUID();
private static final String FILE_0_NAME = PREFIX + "test.txt";
private static final String FILE_1_NAME = PREFIX + "another.txt";
private static final String FILE_2_NAME = PREFIX + "user1.txt";
private static final String FILE_3_NAME = PREFIX + "user1Old.txt";
public static final String BEFORE_1970_TXT = "before1970.txt";
@Autowired
private DataUser dataUser;
@Autowired
private DataContent dataContent;
@Autowired
private DataSite dataSite;
@Autowired
private AlfrescoHttpClientFactory alfrescoHttpClientFactory;
@Autowired
private ServerHealth serverHealth;
@Autowired
protected SearchQueryService searchQueryService;
private UserModel userSite1;
private UserModel userSite2;
private UserModel userMultiSite;
private SiteModel siteModel1;
private SiteModel siteModel2;
/**
* Data will be prepared using the schema below:
* <p>
* Site1: - Users: userSite1, userMultiSite - Documents: FILE_0_NAME (owner: userSite1), FILE_1_NAME (owner: userSite1), FILE_3_NAME (owner: userSite2)
* <p>
* Site2: - Users: userSite2, userMultiSite - Documents: FILE_2_NAME (owner: userSite2)
*/
@BeforeClass(alwaysRun = true)
public void dataPreparation()
{
serverHealth.assertServerIsOnline();
userSite1 = dataUser.createRandomTestUser();
userSite2 = dataUser.createRandomTestUser();
userMultiSite = dataUser.createRandomTestUser();
siteModel1 = dataSite.usingUser(userSite1).createPrivateRandomSite();
siteModel2 = dataSite.usingUser(userSite2).createPrivateRandomSite();
dataUser.addUserToSite(userSite2, siteModel1, UserRole.SiteContributor);
dataUser.addUserToSite(userMultiSite, siteModel1, UserRole.SiteContributor);
dataUser.addUserToSite(userMultiSite, siteModel2, UserRole.SiteContributor);
createContent(FILE_0_NAME, "This is the first test containing the unique word " + UNIQUE_WORD, siteModel1, userSite1);
createContent(FILE_1_NAME, "This is another TEST file", siteModel1, userSite1);
createContent(FILE_2_NAME, "This is another test file", siteModel2, userSite2);
createContent(FILE_3_NAME, "This is another Test file", siteModel1, userSite2);
// remove the user from site, but he keeps ownership on FILE_3_NAME
dataUser.removeUserFromSite(userSite2, siteModel1);
}
@Test(groups = TestGroup.SEARCH)
public void searchCanFindAFileUsingIncludeParameter()
{
SearchRequest queryWithoutIncludes = req(UNIQUE_WORD);
Predicate<SearchNodeModel> allFieldsNull = searchNodeModel -> searchNodeModel.getProperties() == null
&& searchNodeModel.getPath() == null
&& searchNodeModel.getAspectNames() == null
&& searchNodeModel.getAllowableOperations() == null
&& searchNodeModel.getPermissions() == null
&& searchNodeModel.getAssociation() == null
&& searchNodeModel.isLocked() == null
&& searchNodeModel.isLink() == null;
searchQueryService.expectAllResultsFromQuery(queryWithoutIncludes, userSite1, allFieldsNull);
SearchRequest queryWithIncludes = new SearchRequest();
// A full list of all fields that can be included is declared in constant:
// org.alfresco.rest.api.search.impl.SearchMapper.PERMITTED_INCLUDES
queryWithIncludes.setInclude(asList("properties", "path", "aspectNames", "isLocked", "allowableOperations",
"permissions", "isLink", "association"));
RestRequestQueryModel queryReq = new RestRequestQueryModel();
queryReq.setQuery(UNIQUE_WORD);
queryWithIncludes.setQuery(queryReq);
Predicate<SearchNodeModel> noFieldsNull = searchNodeModel -> searchNodeModel.getProperties() != null
&& searchNodeModel.getPath() != null
&& searchNodeModel.getAspectNames() != null
&& searchNodeModel.getAllowableOperations() != null
&& searchNodeModel.getPermissions() != null
&& searchNodeModel.getAssociation() != null
&& searchNodeModel.isLocked() != null
&& searchNodeModel.isLink() != null;
searchQueryService.expectAllResultsFromQuery(queryWithIncludes, userSite1, noFieldsNull);
}
@Test(groups = TestGroup.SEARCH)
public void searchCanFindAFile()
{
// this test must found only one documents, while documents in the system are four because
// only one contains the unique word.
searchQueryService.expectResultsFromQuery(req(UNIQUE_WORD), userSite1, FILE_0_NAME);
}
@Test(groups = TestGroup.SEARCH)
public void searchCanFindFilesOnASite()
{
searchQueryService.expectResultsFromQuery(req(PREFIX), userSite1, FILE_0_NAME, FILE_1_NAME, FILE_3_NAME);
}
@Test(groups = TestGroup.SEARCH)
public void searchCanFindAFileOnMultipleSitesWithOwner()
{
searchQueryService.expectResultsFromQuery(req(PREFIX), userSite2, FILE_3_NAME, FILE_2_NAME);
}
@Test(groups = TestGroup.SEARCH)
public void searchCanFindAFileOnMultipleSites()
{
searchQueryService.expectResultsFromQuery(req(PREFIX), userMultiSite, FILE_0_NAME, FILE_1_NAME, FILE_3_NAME, FILE_2_NAME);
}
@Test(groups = TestGroup.SEARCH)
public void wildcardWorksInsideQuotes()
{
searchQueryService.expectResultsFromQuery(req("cm:name:\"" + PREFIX + "user1*\""), userMultiSite, FILE_2_NAME, FILE_3_NAME);
}
@Test(groups = TestGroup.SEARCH)
public void wildcardWorksWithoutQuotes()
{
searchQueryService.expectResultsFromQuery(req("cm:name:" + PREFIX + "user1*"), userMultiSite, FILE_2_NAME, FILE_3_NAME);
}
@Test(groups = TestGroup.SEARCH, enabled = false) // Test should be re-enabled within: ACS-6068
public void wildcardNodeRefQuery()
{
searchQueryService.expectResultsFromQuery(req("ANCESTOR:\"" + siteModel2.getGuid().substring(0, 10) + "*\""), userMultiSite, "documentLibrary", FILE_2_NAME);
}
@Test(groups = TestGroup.SEARCH)
public void findFileWithRangeQuery()
{
searchQueryService.expectResultsFromQuery(req("cm:created:[NOW-1YEAR TO MAX] AND name:" + FILE_0_NAME), userSite1, FILE_0_NAME);
}
@Test(groups = TestGroup.SEARCH)
public void omitFileWithRangeQuery()
{
searchQueryService.expectNoResultsFromQuery(req("cm:created:[MIN TO NOW-2YEARS] AND name:" + FILE_0_NAME), userSite1);
}
@Test(groups = TestGroup.SEARCH)
public void indexAndSearchForDateBefore1970()
{
// Elasticsearch doesn't accept numbers for dates before 1970, so we create and search for a specific document in order to verify that.
createNodeWithProperties(siteModel1, new FileModel(BEFORE_1970_TXT, FileType.TEXT_PLAIN), userSite1,
Map.of("cm:from", -2637887000L));
searchQueryService.expectResultsInclude(req("cm:from:1969-12-01T11:15:13Z"), userSite1, BEFORE_1970_TXT);
}
private FileModel createContent(String filename, String content, SiteModel site, UserModel user)
{
FileModel fileModel = new FileModel(filename, FileType.TEXT_PLAIN, content);
return dataContent.usingUser(user).usingSite(site)
.createContent(fileModel);
}
private void createNodeWithProperties(SiteModel parentSite, FileModel fileModel, UserModel currentUser, Map<String, Object> properties)
{
AlfrescoHttpClient client = alfrescoHttpClientFactory.getObject();
String reqUrl = client.getApiVersionUrl() + "nodes/" + parentSite.getGuid() + "/children";
String name = fileModel.getName();
HttpPost post = new HttpPost(reqUrl);
JSONObject body = new JSONObject();
body.put("name", name);
body.put("nodeType", "cm:content");
JSONObject jsonProperties = new JSONObject();
jsonProperties.putAll(properties);
body.put("properties", jsonProperties);
post.setEntity(client.setMessageBody(body));
// Send Request
logger.info(String.format("POST: '%s'", reqUrl));
HttpResponse response = client.execute(currentUser.getUsername(), currentUser.getPassword(), post);
if (org.apache.http.HttpStatus.SC_CREATED != response.getStatusLine().getStatusCode())
{
throw new RuntimeException("Could not create file. Request response: " + client.getParameterFromJSON(response, "briefSummary", "error"));
}
}
}
@@ -1,301 +0,0 @@
/*
* #%L
* Alfresco Tas Elasticsearch
* %%
* Copyright (C) 2026 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.elasticsearch;
import static org.alfresco.elasticsearch.SearchQueryService.req;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.alfresco.rest.search.SearchRequest;
import org.alfresco.tas.AlfrescoStackInitializer;
import org.alfresco.utility.data.DataContent;
import org.alfresco.utility.data.DataSite;
import org.alfresco.utility.data.DataUser;
import org.alfresco.utility.model.ContentModel;
import org.alfresco.utility.model.FolderModel;
import org.alfresco.utility.model.TestGroup;
import org.alfresco.utility.network.ServerHealth;
import org.alfresco.utility.report.log.Step;
/**
* Tests to verify indexing of paths using Elasticsearch.
*/
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
initializers = AlfrescoStackInitializer.class)
@SuppressWarnings({"PMD.JUnitTestsShouldIncludeAssert", "PMD.JUnit4TestShouldUseTestAnnotation"}) // these are testng tests and use searchQueryService.expectResultsFromQuery for assertion
public class ElasticsearchPathIndexingTests extends AbstractTestNGSpringContextTests
{
@Autowired
private ServerHealth serverHealth;
@Autowired
private DataUser dataUser;
@Autowired
private DataSite dataSite;
@Autowired
private DataContent dataContent;
@Autowired
protected SearchQueryService searchQueryService;
private org.alfresco.utility.model.UserModel testUser;
private org.alfresco.utility.model.SiteModel testSite;
private List<FolderModel> testFolders;
private String testFileName;
private String filenameWhichIncludesWhitespace = "TestFile " + UUID.randomUUID() + ".txt";
private String testFileNameWithWhitespace;
/**
* Create a user and a private site containing some nested folders with a document in.
*/
@BeforeClass(alwaysRun = true)
public void dataPreparation()
{
serverHealth.isServerReachable();
serverHealth.assertServerIsOnline();
Step.STEP("Create a test user and private site containing three nested folders and a document.");
testUser = dataUser.createRandomTestUser();
testSite = dataSite.usingUser(testUser).createPrivateRandomSite();
testFolders = createNestedFolders(3);
testFileName = createDocument(testFolders.get(testFolders.size() - 1));
testFileNameWithWhitespace = createDocument(testFolders.get(testFolders.size() - 1), filenameWhichIncludesWhitespace);
}
@Test(groups = TestGroup.SEARCH)
public void testSimple()
{
SearchRequest query = req("PATH:\"//cm:" + testFileName + "\"");
searchQueryService.expectResultsFromQuery(query, testUser, testFileName);
}
@Test(groups = TestGroup.SEARCH)
public void testRelativePathQuery()
{
SearchRequest query = req("PATH:\"//cm:" + testFileName + "\" ");
searchQueryService.expectResultsFromQuery(query, testUser, testFileName);
}
@Test(groups = TestGroup.SEARCH)
public void testRelativePathQueryWithoutPrefixes()
{
SearchRequest query = req("PATH:\"//" + testFileName + "\" AND name:*");
searchQueryService.expectResultsFromQuery(query, testUser, testFileName);
}
@Test(groups = TestGroup.SEARCH)
public void testWildcardQuery()
{
// The test file should be the only descendent of the last folder.
SearchRequest query = req("PATH:\"//" + testSite.getId() + "//" + testFolders.get(testFolders.size() - 1).getName() + "/*\" AND name:*");
searchQueryService.expectResultsFromQuery(query, testUser, testFileName, testFileNameWithWhitespace);
}
@Test(groups = TestGroup.SEARCH)
public void testWildcardQueryWithNamespaces()
{
// The test file should be the only descendent of the last folder.
SearchRequest query = req("PATH:\"//cm:" + testSite.getId() + "//cm:" + testFolders.get(testFolders.size() - 1).getName() + "/*\" AND name:*");
searchQueryService.expectResultsFromQuery(query, testUser, testFileName, testFileNameWithWhitespace);
}
@Test(groups = TestGroup.SEARCH)
public void testAbsolutePathQuery()
{
String folderPath = testFolders.stream().map(folder -> "cm:" + folder.getName()).collect(Collectors.joining("/"));
SearchRequest query = req("PATH:\"/app:company_home/st:sites/cm:" + testSite.getId() + "/cm:documentLibrary/" + folderPath + "/cm:" + testFileName + "\" AND cm:name:*");
searchQueryService.expectResultsFromQuery(query, testUser, testFileName);
}
@Test(groups = TestGroup.SEARCH)
public void testAbsolutePathQueryWithoutPrefixes()
{
String folderPath = testFolders.stream().map(folder -> folder.getName()).collect(Collectors.joining("/"));
SearchRequest query = req("PATH:\"/company_home/sites/" + testSite.getId() + "/documentLibrary/" + folderPath + "/" + testFileName + "\" AND name:*");
searchQueryService.expectResultsFromQuery(query, testUser, testFileName);
}
@Test(groups = TestGroup.SEARCH)
public void testRootNodes()
{
SearchRequest query = req("PATH:\"/*\" AND name:*");
searchQueryService.expectResultsFromQuery(query, testUser, "categories", "Company Home");
}
@Test(groups = TestGroup.SEARCH)
public void testRootNodesWithoutWildcard()
{
SearchRequest query = req("PATH:\"/\"");
searchQueryService.expectNodeTypesFromQuery(query, testUser, "sys:store_root");
}
@Test(groups = TestGroup.SEARCH)
public void testPathNameMismatch()
{
SearchRequest query = req("PATH:\"/*\" AND name:" + testFileName + " AND name:*");
searchQueryService.expectNoResultsFromQuery(query, testUser);
}
@Test(groups = TestGroup.SEARCH)
public void testPathNameIntersect()
{
SearchRequest query = req("PATH:\"//*\" AND name:" + testFileName + " AND name:*");
searchQueryService.expectResultsFromQuery(query, testUser, testFileName);
}
@Test(groups = TestGroup.SEARCH)
public void testAllDescendentsOfFolder()
{
SearchRequest query = req("PATH:\"//" + testFolders.get(0).getName() + "//*\" AND name:*");
searchQueryService.expectResultsFromQuery(query, testUser, testFileName, testFileNameWithWhitespace, testFolders.get(1).getName(), testFolders.get(2).getName());
}
@Test(groups = TestGroup.SEARCH)
public void testSearchAllDescendentsOfFolderQueryWithNamespaces()
{
SearchRequest query = req("PATH:\"//cm:" + testFolders.get(0).getName() + "//*\" AND name:*");
searchQueryService.expectResultsFromQuery(query, testUser, testFileName, testFileNameWithWhitespace, testFolders.get(1).getName(), testFolders.get(2).getName());
}
@Test(groups = TestGroup.SEARCH)
public void testWhereFolderIsAncestor()
{
SearchRequest query = req("ANCESTOR:\"" + testFolders.get(2).getNodeRef() + "\" AND name:*");
searchQueryService.expectResultsFromQuery(query, testUser, testFileName, testFileNameWithWhitespace);
}
@Test(groups = TestGroup.SEARCH)
public void testAncestorWithWorkspaceReference()
{
SearchRequest query = req("ANCESTOR:\"workspace://SpacesStore/" + testFolders.get(0).getNodeRef() + "\" AND name:*");
searchQueryService.expectResultsFromQuery(query, testUser, testFileName, testFileNameWithWhitespace, testFolders.get(1).getName(), testFolders.get(2).getName());
}
@Test(groups = TestGroup.SEARCH)
public void testPrimaryParent()
{
SearchRequest query = req("PRIMARYPARENT:\"" + testFolders.get(1).getNodeRef() + "\" AND name:*");
searchQueryService.expectResultsFromQuery(query, testUser, testFolders.get(2).getName());
}
@Test(groups = TestGroup.SEARCH)
public void testParent()
{
SearchRequest query = req("PARENT:\"" + testFolders.get(1).getNodeRef() + "\" AND name:*");
searchQueryService.expectResultsFromQuery(query, testUser, testFolders.get(2).getName());
}
@Test(groups = TestGroup.SEARCH)
public void testAllFoldersInSite()
{
SearchRequest query = req("PATH:\"/*/sites/" + testSite.getId() + "/*//*\" AND TYPE:\"cm:folder\" AND name:*");
String[] folderNames = testFolders.stream().map(ContentModel::getName).toArray(String[]::new);
searchQueryService.expectResultsFromQuery(query, testUser, folderNames);
}
@Test(groups = TestGroup.SEARCH)
public void testSearchAllFoldersInSiteQueryWithNamespaces()
{
SearchRequest query = req("PATH:\"/*/st:sites/cm:" + testSite.getId() + "/*//*\" AND TYPE:\"cm:folder\" AND name:*");
String[] folderNames = testFolders.stream().map(ContentModel::getName).toArray(String[]::new);
searchQueryService.expectResultsFromQuery(query, testUser, folderNames);
}
@Test(groups = TestGroup.SEARCH, enabled = false)
public void testUpdatePath()
{
// disabled test: find a way to rename or move a folder on repository
String folderPath = testFolders.stream().map(folder -> "cm:" + folder.getName()).collect(Collectors.joining("/"));
SearchRequest query = req("PATH:\"/app:company_home/cm:" + testFileName + "\" AND cm:name:*");
searchQueryService.expectResultsFromQuery(query, testUser, testFileName);
}
/**
* Create a set of nested folders in the test site using the test user.
*
* @return The folder objects (containing the randomly generated names) in order of depth.
*/
private List<FolderModel> createNestedFolders(int maxDepth)
{
List<FolderModel> folders = new ArrayList<>();
dataContent.usingSite(testSite);
for (int depth = 0; depth < maxDepth; depth++)
{
String folderName = "TestFolder" + depth + "_" + UUID.randomUUID();
FolderModel folderModel = new FolderModel(folderName);
folders.add(folderModel);
if (depth != 0)
{
dataContent.usingResource(folders.get(depth - 1));
}
dataContent.usingUser(testUser)
.createFolder(folderModel);
}
return folders;
}
/**
* Create a document in the given folder using the test user and a random filename.
*
* @param folderModel
* The location to create the document.
* @return The randomly generated name of the new document.
*/
private String createDocument(FolderModel folderModel)
{
return createDocument(folderModel, "TestFile" + UUID.randomUUID() + ".txt");
}
/**
* Create a document in the given folder using the test user and the given filename.
*
* @param folderModel
* The location to create the document.
* @param filename
* the filename.
* @return the passed filename.
*/
private String createDocument(FolderModel folderModel, String filename)
{
dataContent.usingUser(testUser)
.usingResource(folderModel)
.createContent(new org.alfresco.utility.model.FileModel(filename, org.alfresco.utility.model.FileType.TEXT_PLAIN, "content"));
return filename;
}
}
@@ -1,478 +0,0 @@
/*
* #%L
* Alfresco Tas Elasticsearch
* %%
* Copyright (C) 2026 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.elasticsearch;
import static java.util.stream.Collectors.toSet;
import static org.alfresco.elasticsearch.SearchQueryService.req;
import static org.alfresco.utility.report.log.Step.STEP;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.alfresco.rest.search.ResponseHighlightModel;
import org.alfresco.rest.search.RestRequestFieldsModel;
import org.alfresco.rest.search.RestRequestHighlightModel;
import org.alfresco.rest.search.SearchNodeModel;
import org.alfresco.rest.search.SearchRequest;
import org.alfresco.tas.AlfrescoStackInitializer;
import org.alfresco.utility.data.DataContent;
import org.alfresco.utility.data.DataSite;
import org.alfresco.utility.data.DataUser;
import org.alfresco.utility.model.FileModel;
import org.alfresco.utility.model.FileType;
import org.alfresco.utility.model.SiteModel;
import org.alfresco.utility.model.TestGroup;
import org.alfresco.utility.model.UserModel;
import org.alfresco.utility.network.ServerHealth;
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
initializers = AlfrescoStackInitializer.class)
// These are TestNG tests and the assertions are hidden in searchQueryService.
@SuppressWarnings({"PMD.JUnitTestsShouldIncludeAssert"})
public class ElasticsearchPlainHighlightingTests extends AbstractTestNGSpringContextTests
{
static final Logger LOGGER = LoggerFactory.getLogger(ElasticsearchPlainHighlightingTests.class);
static final String FILE_A = "fileA.txt";
static final String FILE_B = "fileB.txt";
static final String FILE_C = "fileC.txt";
static final String CONTENT_A = "The quick brown fox jumps over the lazy dog.";
static final String CONTENT_B = """
The lazy dog sleeps under the quick brown fox. The middle of the document is quite long:
Lorem ipsum dolor sit amet. In veniam tempore hic provident sunt et distinctio velit et reprehenderit
officiis id sapiente omnis et quisquam aliquam et porro perferendis. In sequi placeat ut quaerat voluptatem
ea consequuntur impedit aut eaque enim aut atque rerum.
The end of the document mentions the dog again!
""";
static final String CONTENT_C = """
The rabbit made a ring in the center of the field. He said to the wolf, "Now, you dance around this ring, and sing just as I do."
Rabbit made a larger ring for himself and danced around just beyond the wolf.
The wolf thought that this was the finest dance he had ever seen.
He and the rabbit danced faster and faster, and sang louder and louder.
As the rabbit danced, he moved nearer and nearer to the edge of the field.
The wolf was dancing so fast and singing so loud that he did not notice this.
At last, Brother Rabbit reached the edge of the field; then he jumped into the blackberry bushes and ran away.
The wolf tried to give chase, but he was so dizzy that he could not run. And the rabbit got away without having his ears cut off.
""";
@Autowired
ServerHealth serverHealth;
@Autowired
DataUser dataUser;
@Autowired
DataContent dataContent;
@Autowired
DataSite dataSite;
@Autowired
SearchQueryService searchQueryService;
UserModel user;
SiteModel site;
FileModel fileA;
FileModel fileB;
FileModel fileC;
@BeforeClass(alwaysRun = true)
public void dataPreparation()
{
serverHealth.assertServerIsOnline();
STEP("Create a test user and a private site.");
user = dataUser.createRandomTestUser();
site = dataSite.usingUser(user).createPrivateRandomSite();
STEP("Create two test files with highlightable content");
FileModel fileModelA = new FileModel(FILE_A, FileType.TEXT_PLAIN, CONTENT_A);
fileA = dataContent.usingUser(user).usingSite(site).createContent(fileModelA);
FileModel fileModelB = new FileModel(FILE_B, FileType.TEXT_PLAIN, CONTENT_B);
fileB = dataContent.usingUser(user).usingSite(site).createContent(fileModelB);
FileModel fileModelC = new FileModel(FILE_C, FileType.TEXT_PLAIN, CONTENT_C);
fileC = dataContent.usingUser(user).usingSite(site).createContent(fileModelC);
}
@AfterClass
public void dataCleanup()
{
STEP("Remove test site and user");
dataSite.usingAdmin().deleteSite(site);
dataUser.usingAdmin().deleteUser(user);
}
@Test(groups = {TestGroup.SEARCH})
public void testHighlightsInContent()
{
STEP("Search for files mentioning 'dog'");
String query = "cm:content:dog AND SITE:" + site.getId();
SearchRequest searchRequest = req("afts", query);
RestRequestHighlightModel highlightModel = RestRequestHighlightModel.builder().fields(List.of("cm:content")).build();
searchRequest.setHighlight(highlightModel);
// Configure the expected highlights for each document.
Predicate<SearchNodeModel> assertionMethod = highlightAssert(
Map.of(FILE_A, Map.of("cm:content", List.of("The quick brown fox jumps over the lazy <em>dog</em>.")),
FILE_B, Map.of("cm:content", List.of("The lazy <em>dog</em> sleeps under the quick brown fox. The middle of the document is quite long:\nLorem",
"\nea consequuntur impedit aut eaque enim aut atque rerum.\nThe end of the document mentions the <em>dog</em> again!\n"))));
searchQueryService.expectAllResultsFromQuery(searchRequest, user, assertionMethod);
}
@Test(groups = {TestGroup.SEARCH})
public void testHighlightsInTwoFields()
{
STEP("Search for files with 'file' in the name that mention 'middle'");
String query = "cm:content:middle AND cm:name:file AND SITE:" + site.getId();
SearchRequest searchRequest = req("afts", query);
RestRequestHighlightModel highlightModel = RestRequestHighlightModel.builder().fields(List.of("cm:name", "cm:content")).build();
searchRequest.setHighlight(highlightModel);
// Configure the expected highlights for each document.
Predicate<SearchNodeModel> assertionMethod = highlightAssert(
Map.of(FILE_B, Map.of("cm:name", List.of("<em>file</em>B.txt"),
"cm:content", List.of("The lazy dog sleeps under the quick brown fox. The <em>middle</em> of the document is quite long:\nLorem"))));
searchQueryService.expectAllResultsFromQuery(searchRequest, user, assertionMethod);
}
@Test(groups = {TestGroup.SEARCH})
public void testHighlightsWithCustomPrefixAndPostfix()
{
STEP("Search for files mentioning 'dog' with custom prefix and postfix");
String query = "cm:content:dog AND SITE:" + site.getId();
SearchRequest searchRequest = req("afts", query);
RestRequestHighlightModel highlightModel = RestRequestHighlightModel.builder()
.prefix("<b>")
.postfix("</b>")
.fields(List.of("cm:content"))
.build();
searchRequest.setHighlight(highlightModel);
// Configure the expected highlights for each document.
Predicate<SearchNodeModel> assertionMethod = highlightAssert(
Map.of(FILE_A, Map.of("cm:content", List.of("The quick brown fox jumps over the lazy <b>dog</b>.")),
FILE_B, Map.of("cm:content", List.of("The lazy <b>dog</b> sleeps under the quick brown fox. The middle of the document is quite long:\nLorem",
"\nea consequuntur impedit aut eaque enim aut atque rerum.\nThe end of the document mentions the <b>dog</b> again!\n"))));
searchQueryService.expectAllResultsFromQuery(searchRequest, user, assertionMethod);
}
@Test(groups = {TestGroup.SEARCH})
public void testHighlightsWithBlankPrefixAndPostfix()
{
STEP("Search for files mentioning 'middle' with blank mark as custom prefix and postfix");
String query = "cm:content:middle AND SITE:" + site.getId();
SearchRequest searchRequest = req("afts", query);
RestRequestHighlightModel highlightModel = RestRequestHighlightModel.builder()
.prefix(" ")
.postfix(" ")
.fields(List.of("cm:content"))
.build();
searchRequest.setHighlight(highlightModel);
// Configure the expected highlights for each document.
Predicate<SearchNodeModel> assertionMethod = highlightAssert(
Map.of(FILE_B, Map.of("cm:content", List.of("The lazy dog sleeps under the quick brown fox. The middle of the document is quite long:\nLorem"))));
searchQueryService.expectAllResultsFromQuery(searchRequest, user, assertionMethod);
}
@Test(groups = {TestGroup.SEARCH})
public void testHighlightsWithGeneralAndFieldSpecificPrefixesAndPostfixes()
{
STEP("Search for files with 'file' in the name that mention 'dog' with general and field specific prefixes or postfixes");
String query = "cm:content:dog AND cm:name:file AND SITE:" + site.getId();
SearchRequest searchRequest = req("afts", query);
RestRequestHighlightModel highlightModel = RestRequestHighlightModel.builder()
.prefix("¿")
.postfix("?")
.fields(
RestRequestFieldsModel.of("cm:name"),
RestRequestFieldsModel.of("cm:content", "(", ")"))
.build();
searchRequest.setHighlight(highlightModel);
// Configure the expected highlights for each document.
Predicate<SearchNodeModel> assertionMethod = highlightAssert(
Map.of(FILE_A, Map.of("cm:name", List.of("¿file?A.txt"), "cm:content", List.of("The quick brown fox jumps over the lazy (dog).")),
FILE_B, Map.of("cm:name", List.of("¿file?B.txt"), "cm:content", List.of("The lazy (dog) sleeps under the quick brown fox. The middle of the document is quite long:\nLorem",
"\nea consequuntur impedit aut eaque enim aut atque rerum.\nThe end of the document mentions the (dog) again!\n"))));
searchQueryService.expectAllResultsFromQuery(searchRequest, user, assertionMethod);
}
@Test(groups = {TestGroup.SEARCH})
public void testHighlightsWithOneFieldSpecificPrefixAndPostfix()
{
STEP("Search for files with 'file' in the name that mention 'dog' with one field specific prefix or postfix");
String query = "cm:content:dog AND cm:name:file AND SITE:" + site.getId();
SearchRequest searchRequest = req("afts", query);
RestRequestHighlightModel highlightModel = RestRequestHighlightModel.builder()
.fields(
RestRequestFieldsModel.of("cm:name"),
RestRequestFieldsModel.of("cm:content", "(", ")"))
.build();
searchRequest.setHighlight(highlightModel);
// Configure the expected highlights for each document.
Predicate<SearchNodeModel> assertionMethod = highlightAssert(
Map.of(FILE_A, Map.of("cm:name", List.of("<em>file</em>A.txt"), "cm:content", List.of("The quick brown fox jumps over the lazy (dog).")),
FILE_B, Map.of("cm:name", List.of("<em>file</em>B.txt"), "cm:content", List.of("The lazy (dog) sleeps under the quick brown fox. The middle of the document is quite long:\nLorem",
"\nea consequuntur impedit aut eaque enim aut atque rerum.\nThe end of the document mentions the (dog) again!\n"))));
searchQueryService.expectAllResultsFromQuery(searchRequest, user, assertionMethod);
}
@Test(groups = {TestGroup.SEARCH})
public void testHighlightsInTwoFieldsWithPrefixAndWithoutPostfix()
{
STEP("Search for files with 'file' in the name that mention 'middle' with custom prefix");
String query = "cm:content:middle AND cm:name:file AND SITE:" + site.getId();
SearchRequest searchRequest = req("afts", query);
RestRequestHighlightModel highlightModel = RestRequestHighlightModel.builder()
.fields(List.of("cm:name", "cm:content"))
.prefix("(")
.build();
searchRequest.setHighlight(highlightModel);
// Configure the expected highlights for each document.
Predicate<SearchNodeModel> assertionMethod = highlightAssert(
Map.of(FILE_B, Map.of("cm:name", List.of("(file</em>B.txt"),
"cm:content", List.of("The lazy dog sleeps under the quick brown fox. The (middle</em> of the document is quite long:\nLorem"))));
searchQueryService.expectAllResultsFromQuery(searchRequest, user, assertionMethod);
}
@Test(groups = {TestGroup.SEARCH})
public void testHighlightsInTwoFieldsWithoutPrefixAndWithPostfix()
{
STEP("Search for files with 'file' in the name that mention 'middle' with custom postfix");
String query = "cm:content:middle AND cm:name:file AND SITE:" + site.getId();
SearchRequest searchRequest = req("afts", query);
RestRequestHighlightModel highlightModel = RestRequestHighlightModel.builder()
.fields(List.of("cm:name", "cm:content"))
.postfix(")")
.build();
searchRequest.setHighlight(highlightModel);
// Configure the expected highlights for each document.
Predicate<SearchNodeModel> assertionMethod = highlightAssert(
Map.of(FILE_B, Map.of("cm:name", List.of("<em>file)B.txt"),
"cm:content", List.of("The lazy dog sleeps under the quick brown fox. The <em>middle) of the document is quite long:\nLorem"))));
searchQueryService.expectAllResultsFromQuery(searchRequest, user, assertionMethod);
}
@Test(groups = {TestGroup.SEARCH})
public void testHighlightsInContentWithGeneralSnippetCount()
{
STEP("Search for files mentioning 'rabbit' and expect 2 snippets in result");
String query = "cm:content:rabbit AND SITE:" + site.getId();
SearchRequest searchRequest = req("afts", query);
RestRequestHighlightModel highlightModel = RestRequestHighlightModel.builder()
.fields(RestRequestFieldsModel.of("cm:content"))
.snippetCount(2)
.build();
searchRequest.setHighlight(highlightModel);
// Configure the expected highlights for each document.
Predicate<SearchNodeModel> assertionMethod = highlightAssert(
Map.of(FILE_C, Map.of("cm:content", List.of("The <em>rabbit</em> made a ring in the center of the field. He said to the wolf, \"Now, you dance around",
" this ring, and sing just as I do.\"\n<em>Rabbit</em> made a larger ring for himself and danced around just beyond"))));
searchQueryService.expectAllResultsFromQuery(searchRequest, user, assertionMethod);
}
@Test(groups = {TestGroup.SEARCH})
public void testHighlightsInContentWithFieldSpecificSnippetCount()
{
STEP("Search for files mentioning 'rabbit' and expect 3 snippets in result");
String query = "cm:content:rabbit AND SITE:" + site.getId();
SearchRequest searchRequest = req("afts", query);
RestRequestHighlightModel highlightModel = RestRequestHighlightModel.builder()
.fields(RestRequestFieldsModel.builder().field("cm:content").snippetCount(3).build())
.snippetCount(1)
.build();
searchRequest.setHighlight(highlightModel);
// Configure the expected highlights for each document.
Predicate<SearchNodeModel> assertionMethod = highlightAssert(
Map.of(FILE_C, Map.of("cm:content", List.of("The <em>rabbit</em> made a ring in the center of the field. He said to the wolf, \"Now, you dance around",
" this ring, and sing just as I do.\"\n<em>Rabbit</em> made a larger ring for himself and danced around just beyond",
" the wolf.\nThe wolf thought that this was the finest dance he had ever seen.\nHe and the <em>rabbit</em> danced"))));
searchQueryService.expectAllResultsFromQuery(searchRequest, user, assertionMethod);
}
@Test(groups = {TestGroup.SEARCH})
public void testHighlightsInContentWithZeroSnippetCount()
{
STEP("Search for files mentioning 'rabbit' and expect default 5 snippets in result");
String query = "cm:content:rabbit AND SITE:" + site.getId();
SearchRequest searchRequest = req("afts", query);
RestRequestHighlightModel highlightModel = RestRequestHighlightModel.builder()
.fields(RestRequestFieldsModel.of("cm:content"))
.snippetCount(0)
.build();
searchRequest.setHighlight(highlightModel);
// Configure the expected highlights for each document.
Predicate<SearchNodeModel> assertionMethod = highlightAssert(
Map.of(FILE_C, Map.of("cm:content", List.of("The <em>rabbit</em> made a ring in the center of the field. He said to the wolf, \"Now, you dance around",
" this ring, and sing just as I do.\"\n<em>Rabbit</em> made a larger ring for himself and danced around just beyond",
" the wolf.\nThe wolf thought that this was the finest dance he had ever seen.\nHe and the <em>rabbit</em> danced",
" faster and faster, and sang louder and louder.\nAs the <em>rabbit</em> danced, he moved nearer and nearer",
" this.\nAt last, Brother <em>Rabbit</em> reached the edge of the field; then he jumped into the blackberry bushes"))));
searchQueryService.expectAllResultsFromQuery(searchRequest, user, assertionMethod);
}
@Test(groups = {TestGroup.SEARCH})
public void testHighlightsInContentWithNegativeSnippetCount()
{
STEP("Search for files mentioning 'rabbit' and expect default 5 snippets in result");
String query = "cm:content:rabbit AND SITE:" + site.getId();
SearchRequest searchRequest = req("afts", query);
RestRequestHighlightModel highlightModel = RestRequestHighlightModel.builder()
.fields(RestRequestFieldsModel.of("cm:content"))
.snippetCount(-1)
.build();
searchRequest.setHighlight(highlightModel);
// Configure the expected highlights for each document.
Predicate<SearchNodeModel> assertionMethod = highlightAssert(
Map.of(FILE_C, Map.of("cm:content", List.of("The <em>rabbit</em> made a ring in the center of the field. He said to the wolf, \"Now, you dance around",
" this ring, and sing just as I do.\"\n<em>Rabbit</em> made a larger ring for himself and danced around just beyond",
" the wolf.\nThe wolf thought that this was the finest dance he had ever seen.\nHe and the <em>rabbit</em> danced",
" faster and faster, and sang louder and louder.\nAs the <em>rabbit</em> danced, he moved nearer and nearer",
" this.\nAt last, Brother <em>Rabbit</em> reached the edge of the field; then he jumped into the blackberry bushes"))));
searchQueryService.expectAllResultsFromQuery(searchRequest, user, assertionMethod);
}
@Test(groups = {TestGroup.SEARCH})
public void testHighlightsInContentWithGeneralFragmentSize()
{
STEP("Search for files mentioning 'dog' and expect snippets about 10 characters long");
String query = "cm:content:dog AND SITE:" + site.getId();
SearchRequest searchRequest = req("afts", query);
RestRequestHighlightModel highlightModel = RestRequestHighlightModel.builder()
.fields(RestRequestFieldsModel.of("cm:content"))
.fragmentSize(10)
.build();
searchRequest.setHighlight(highlightModel);
// Configure the expected highlights for each document.
Predicate<SearchNodeModel> assertionMethod = highlightAssert(
Map.of(FILE_A, Map.of("cm:content", List.of(" over the lazy <em>dog</em>.")),
FILE_B, Map.of("cm:content", List.of(" <em>dog</em> sleeps", " the <em>dog</em> again!\n"))));
searchQueryService.expectAllResultsFromQuery(searchRequest, user, assertionMethod);
}
@Test(groups = {TestGroup.SEARCH})
public void testHighlightsInContentWithFieldSpecificFragmentSize()
{
STEP("Search for files mentioning 'dog' and expect snippets about 15 characters long");
String query = "cm:content:dog AND SITE:" + site.getId();
SearchRequest searchRequest = req("afts", query);
RestRequestHighlightModel highlightModel = RestRequestHighlightModel.builder()
.fields(RestRequestFieldsModel.builder().field("cm:content").fragmentSize(15).build())
.fragmentSize(10)
.build();
searchRequest.setHighlight(highlightModel);
// Configure the expected highlights for each document.
Predicate<SearchNodeModel> assertionMethod = highlightAssert(
Map.of(FILE_A, Map.of("cm:content", List.of(" over the lazy <em>dog</em>.")),
FILE_B, Map.of("cm:content", List.of("The lazy <em>dog</em>", " the <em>dog</em> again!\n"))));
searchQueryService.expectAllResultsFromQuery(searchRequest, user, assertionMethod);
}
@Test(groups = {TestGroup.SEARCH})
public void testHighlightsInContentWithZeroFragmentSize()
{
STEP("Search for files mentioning 'dog' and expect snippets having default characters length");
String query = "cm:content:dog AND SITE:" + site.getId();
SearchRequest searchRequest = req("afts", query);
RestRequestHighlightModel highlightModel = RestRequestHighlightModel.builder()
.fields(RestRequestFieldsModel.of("cm:content"))
.fragmentSize(0)
.build();
searchRequest.setHighlight(highlightModel);
// Configure the expected highlights for each document.
Predicate<SearchNodeModel> assertionMethod = highlightAssert(
Map.of(FILE_A, Map.of("cm:content", List.of("The quick brown fox jumps over the lazy <em>dog</em>.")),
FILE_B, Map.of("cm:content", List.of("The lazy <em>dog</em> sleeps under the quick brown fox. The middle of the document is quite long:\nLorem",
"\nea consequuntur impedit aut eaque enim aut atque rerum.\nThe end of the document mentions the <em>dog</em> again!\n"))));
searchQueryService.expectAllResultsFromQuery(searchRequest, user, assertionMethod);
}
@Test(groups = {TestGroup.SEARCH})
public void testHighlightsInContentWithNegativeFragmentSize()
{
STEP("Search for files mentioning 'dog' and expect snippets having default characters length");
String query = "cm:content:dog AND SITE:" + site.getId();
SearchRequest searchRequest = req("afts", query);
RestRequestHighlightModel highlightModel = RestRequestHighlightModel.builder()
.fields(RestRequestFieldsModel.of("cm:content"))
.fragmentSize(-1)
.build();
searchRequest.setHighlight(highlightModel);
// Configure the expected highlights for each document.
Predicate<SearchNodeModel> assertionMethod = highlightAssert(
Map.of(FILE_A, Map.of("cm:content", List.of("The quick brown fox jumps over the lazy <em>dog</em>.")),
FILE_B, Map.of("cm:content", List.of("The lazy <em>dog</em> sleeps under the quick brown fox. The middle of the document is quite long:\nLorem",
"\nea consequuntur impedit aut eaque enim aut atque rerum.\nThe end of the document mentions the <em>dog</em> again!\n"))));
searchQueryService.expectAllResultsFromQuery(searchRequest, user, assertionMethod);
}
/**
* Create a predicate that returns true if the highlights for a received document match the given expectation.
*
* @param allExpectedHighlights
* The expected highlights for all documents keyed by document name and then field name.
* @return The predicate.
*/
private Predicate<SearchNodeModel> highlightAssert(Map<String, Map<String, List<String>>> allExpectedHighlights)
{
return document -> {
if (!allExpectedHighlights.containsKey(document.getName()))
{
LOGGER.error("Unexpected entry in results: {}", document.getName());
return false;
}
Map<String, List<String>> expectedHighlights = allExpectedHighlights.get(document.getName());
List<ResponseHighlightModel> actualHighlights = document.getSearch().getHighlight();
Set<String> actualFields = actualHighlights.stream()
.map(ResponseHighlightModel::getField)
.collect(toSet());
if (!actualFields.equals(expectedHighlights.keySet()))
{
LOGGER.error("Unexpected field set for {}: {}", document.getName(), actualFields);
return false;
}
Set<ResponseHighlightModel> expectedHighlightResponse = new HashSet<>();
for (String expectedField : expectedHighlights.keySet())
{
ResponseHighlightModel expectedHighlight = new ResponseHighlightModel();
List<String> expectedSnippets = expectedHighlights.get(expectedField);
expectedHighlight.setField(expectedField);
expectedHighlight.setSnippets(expectedSnippets);
expectedHighlightResponse.add(expectedHighlight);
}
if (!new HashSet<>(actualHighlights).equals(expectedHighlightResponse))
{
LOGGER.error("Unexpected highlights for {}, {}", document.getName(), actualHighlights);
return false;
}
return true;
};
}
}
@@ -1,253 +0,0 @@
/*
* #%L
* Alfresco Tas Elasticsearch
* %%
* Copyright (C) 2026 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.elasticsearch;
import static java.lang.String.format;
import static org.alfresco.elasticsearch.SearchQueryService.req;
import java.util.List;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.alfresco.rest.search.SearchRequest;
import org.alfresco.tas.AlfrescoStackInitializer;
import org.alfresco.utility.data.DataContent;
import org.alfresco.utility.data.DataUser;
import org.alfresco.utility.model.ContentModel;
import org.alfresco.utility.model.FileModel;
import org.alfresco.utility.model.FileType;
import org.alfresco.utility.model.TestGroup;
import org.alfresco.utility.model.UserModel;
import org.alfresco.utility.network.ServerHealth;
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
initializers = AlfrescoStackInitializer.class)
public class ElasticsearchProximitySearchTests extends AbstractTestNGSpringContextTests
{
@Autowired
ServerHealth serverHealth;
@Autowired
DataUser dataUser;
@Autowired
DataContent dataContent;
@Autowired
SearchQueryService searchQueryService;
private UserModel testUser;
private String contentA;
private String contentB;
private String contentC;
private String contentD;
private String nameA;
private String nameB;
private String nameC;
private String nameD;
private String file_NameA_ContentABCD;
private String file_NameAB_ContentABC;
private String file_NameABC_ContentAB;
private String file_NameABCD_ContentA;
@BeforeClass(alwaysRun = true)
public void dataPreparation()
{
serverHealth.assertServerIsOnline();
nameA = uniqueString();
nameB = uniqueString();
nameC = uniqueString();
nameD = uniqueString();
contentA = uniqueString();
contentB = uniqueString();
contentC = uniqueString();
contentD = uniqueString();
file_NameA_ContentABCD = createContent(join(nameA), join(contentA, contentB, contentC, contentD));
file_NameAB_ContentABC = createContent(join(nameA, nameB), join(contentA, contentB, contentC));
file_NameABC_ContentAB = createContent(join(nameA, nameB, nameC), join(contentA, contentB));
file_NameABCD_ContentA = createContent(join(nameA, nameB, nameC, nameD), join(contentA));
testUser = dataUser.createRandomTestUser();
}
@Test(groups = {TestGroup.SEARCH, TestGroup.TAGS, TestGroup.REGRESSION})
public void testProximitySearchUsingAFTSSyntax()
{
final String AFTS = "%s *%s %s";
assertAFTS(format(AFTS, contentA, "(0)", contentB), file_NameA_ContentABCD, file_NameAB_ContentABC, file_NameABC_ContentAB);
assertAFTS(format(AFTS, contentA, "(1)", contentB), file_NameA_ContentABCD, file_NameAB_ContentABC, file_NameABC_ContentAB);
assertAFTS(format(AFTS, contentA, "(2)", contentB), file_NameA_ContentABCD, file_NameAB_ContentABC, file_NameABC_ContentAB);
assertAFTS(format(AFTS, contentA, "(3)", contentB), file_NameA_ContentABCD, file_NameAB_ContentABC, file_NameABC_ContentAB);
assertAFTS(format(AFTS, contentA, "", contentB), file_NameA_ContentABCD, file_NameAB_ContentABC, file_NameABC_ContentAB);
assertAFTS(format(AFTS, contentA, "(0)", contentC));
assertAFTS(format(AFTS, contentA, "(1)", contentC), file_NameA_ContentABCD, file_NameAB_ContentABC);
assertAFTS(format(AFTS, contentA, "(2)", contentC), file_NameA_ContentABCD, file_NameAB_ContentABC);
assertAFTS(format(AFTS, contentA, "(3)", contentC), file_NameA_ContentABCD, file_NameAB_ContentABC);
assertAFTS(format(AFTS, contentA, "", contentC), file_NameA_ContentABCD, file_NameAB_ContentABC);
assertAFTS(format(AFTS, contentA, "(0)", contentD));
assertAFTS(format(AFTS, contentA, "(1)", contentD));
assertAFTS(format(AFTS, contentA, "(2)", contentD), file_NameA_ContentABCD);
assertAFTS(format(AFTS, contentA, "(3)", contentD), file_NameA_ContentABCD);
assertAFTS(format(AFTS, contentA, "", contentD), file_NameA_ContentABCD);
}
@Test(groups = {TestGroup.SEARCH, TestGroup.TAGS, TestGroup.REGRESSION})
public void testProximitySearchUsingAFTSSyntaxForSpecificProperty()
{
final String AFTS = "cm:name:(%s *%s %s)";
assertAFTS(format(AFTS, nameA, "(0)", nameB), file_NameAB_ContentABC, file_NameABC_ContentAB, file_NameABCD_ContentA);
assertAFTS(format(AFTS, nameA, "(1)", nameB), file_NameAB_ContentABC, file_NameABC_ContentAB, file_NameABCD_ContentA);
assertAFTS(format(AFTS, nameA, "(2)", nameB), file_NameAB_ContentABC, file_NameABC_ContentAB, file_NameABCD_ContentA);
assertAFTS(format(AFTS, nameA, "(3)", nameB), file_NameAB_ContentABC, file_NameABC_ContentAB, file_NameABCD_ContentA);
assertAFTS(format(AFTS, nameA, "", nameB), file_NameAB_ContentABC, file_NameABC_ContentAB, file_NameABCD_ContentA);
assertAFTS(format(AFTS, nameA, "(0)", nameC));
assertAFTS(format(AFTS, nameA, "(1)", nameC), file_NameABC_ContentAB, file_NameABCD_ContentA);
assertAFTS(format(AFTS, nameA, "(2)", nameC), file_NameABC_ContentAB, file_NameABCD_ContentA);
assertAFTS(format(AFTS, nameA, "(3)", nameC), file_NameABC_ContentAB, file_NameABCD_ContentA);
assertAFTS(format(AFTS, nameA, "", nameC), file_NameABC_ContentAB, file_NameABCD_ContentA);
assertAFTS(format(AFTS, nameA, "(0)", nameD));
assertAFTS(format(AFTS, nameA, "(1)", nameD));
assertAFTS(format(AFTS, nameA, "(2)", nameD), file_NameABCD_ContentA);
assertAFTS(format(AFTS, nameA, "(3)", nameD), file_NameABCD_ContentA);
assertAFTS(format(AFTS, nameA, "", nameD), file_NameABCD_ContentA);
}
@Test(groups = {TestGroup.SEARCH, TestGroup.TAGS, TestGroup.REGRESSION})
public void testProximitySearchUsingLuceneSyntax()
{
final String lucene = "\"%s %s\"~%d";
assertLucene(format(lucene, contentA, contentB, 0), file_NameA_ContentABCD, file_NameAB_ContentABC, file_NameABC_ContentAB);
assertLucene(format(lucene, contentA, contentB, 1), file_NameA_ContentABCD, file_NameAB_ContentABC, file_NameABC_ContentAB);
assertLucene(format(lucene, contentA, contentB, 2), file_NameA_ContentABCD, file_NameAB_ContentABC, file_NameABC_ContentAB);
assertLucene(format(lucene, contentA, contentB, 3), file_NameA_ContentABCD, file_NameAB_ContentABC, file_NameABC_ContentAB);
assertLucene(format(lucene, contentA, contentC, 0));
assertLucene(format(lucene, contentA, contentC, 1), file_NameA_ContentABCD, file_NameAB_ContentABC);
assertLucene(format(lucene, contentA, contentC, 2), file_NameA_ContentABCD, file_NameAB_ContentABC);
assertLucene(format(lucene, contentA, contentC, 3), file_NameA_ContentABCD, file_NameAB_ContentABC);
assertLucene(format(lucene, contentA, contentD, 0));
assertLucene(format(lucene, contentA, contentD, 1));
assertLucene(format(lucene, contentA, contentD, 2), file_NameA_ContentABCD);
assertLucene(format(lucene, contentA, contentD, 3), file_NameA_ContentABCD);
}
@Test(groups = {TestGroup.SEARCH, TestGroup.TAGS, TestGroup.REGRESSION})
public void testProximitySearchUsingLuceneSyntaxForSpecificProperty()
{
final String lucene = "@cm\\:name:\"%s %s\"~%d";
assertLucene(format(lucene, nameA, nameB, 0), file_NameAB_ContentABC, file_NameABC_ContentAB, file_NameABCD_ContentA);
assertLucene(format(lucene, nameA, nameB, 1), file_NameAB_ContentABC, file_NameABC_ContentAB, file_NameABCD_ContentA);
assertLucene(format(lucene, nameA, nameB, 2), file_NameAB_ContentABC, file_NameABC_ContentAB, file_NameABCD_ContentA);
assertLucene(format(lucene, nameA, nameB, 3), file_NameAB_ContentABC, file_NameABC_ContentAB, file_NameABCD_ContentA);
assertLucene(format(lucene, nameA, nameC, 0));
assertLucene(format(lucene, nameA, nameC, 1), file_NameABC_ContentAB, file_NameABCD_ContentA);
assertLucene(format(lucene, nameA, nameC, 2), file_NameABC_ContentAB, file_NameABCD_ContentA);
assertLucene(format(lucene, nameA, nameC, 3), file_NameABC_ContentAB, file_NameABCD_ContentA);
assertLucene(format(lucene, nameA, nameD, 0));
assertLucene(format(lucene, nameA, nameD, 1));
assertLucene(format(lucene, nameA, nameD, 2), file_NameABCD_ContentA);
assertLucene(format(lucene, nameA, nameD, 3), file_NameABCD_ContentA);
}
private void assertLucene(String query, String... expected)
{
assertQueryResult("lucene", query, expected);
}
private void assertAFTS(String query, String... expected)
{
assertQueryResult("afts", query, expected);
}
private void assertQueryResult(String language, String query, String... expected)
{
final SearchRequest searchRequest = req(language, query);
if (expected.length == 0)
{
searchQueryService.expectNoResultsFromQuery(searchRequest, testUser);
}
else
{
searchQueryService.expectNodeRefsFromQuery(searchRequest, testUser, expected);
}
}
private String createContent(String filename, String content)
{
return dataContent
.usingAdmin()
.usingResource(contentRoot())
.createContent(new FileModel(filename, FileType.TEXT_PLAIN, content))
.getNodeRef();
}
private static ContentModel contentRoot()
{
final ContentModel root = new ContentModel("-root-");
root.setNodeRef(root.getName());
return root;
}
private String join(String... parts)
{
return String.join(" ", parts);
}
private static String uniqueString()
{
final UUID unique = UUID.randomUUID();
final StringBuilder result = new StringBuilder(Long.SIZE / 2);
for (long l : List.of(unique.getMostSignificantBits(), unique.getLeastSignificantBits()))
{
for (int i = Long.SIZE - 4; i >= 0; i -= 4)
{
int ch = 'a' + (byte) ((((l & (0xFL << i)) >> i)) & 0xFL);
result.append(Character.toChars(ch));
}
}
return result.toString();
}
}
@@ -1,522 +0,0 @@
/*
* #%L
* Alfresco Tas Elasticsearch
* %%
* Copyright (C) 2026 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.elasticsearch;
import static org.alfresco.elasticsearch.SearchQueryService.req;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.chemistry.opencmis.client.api.CmisObject;
import org.apache.chemistry.opencmis.client.api.Document;
import org.apache.chemistry.opencmis.client.api.Folder;
import org.apache.chemistry.opencmis.client.api.Session;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.alfresco.dataprep.AlfrescoHttpClientFactory;
import org.alfresco.dataprep.CMISUtil;
import org.alfresco.dataprep.ContentActions;
import org.alfresco.dataprep.SiteService.Visibility;
import org.alfresco.rest.core.RestWrapper;
import org.alfresco.rest.search.SearchRequest;
import org.alfresco.tas.AlfrescoStackInitializer;
import org.alfresco.utility.data.DataContent;
import org.alfresco.utility.data.DataSite;
import org.alfresco.utility.data.DataUser;
import org.alfresco.utility.model.ContentModel;
import org.alfresco.utility.model.FileModel;
import org.alfresco.utility.model.FileType;
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.alfresco.utility.network.ServerHealth;
import org.alfresco.utility.report.log.Step;
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
initializers = AlfrescoStackInitializer.class)
public class ElasticsearchSiteIndexingTests extends AbstractTestNGSpringContextTests
{
private static final Iterable<String> LANGUAGES_TO_CHECK = List.of("afts", "lucene");
private static final FileModel DOCUMENT_LIBRARY = new FileModel("documentLibrary");
private static final String FILENAME_PREFIX = "EsSiteTest";
private static final String FILE_CONTENT_CONDITION = " TEXT:" + FILENAME_PREFIX + "*";
private static final String SAMPLE_SITE_ID = "swsdp";
private static final String ALL_SITES = "_ALL_SITES_ ";
private static final String EVERYTHING = "_EVERYTHING_ ";
@Autowired
ServerHealth serverHealth;
@Autowired
DataUser dataUser;
@Autowired
DataContent dataContent;
@Autowired
public DataSite dataSite;
@Autowired
private AlfrescoHttpClientFactory alfrescoHttpClientFactory;
@Autowired
SearchQueryService searchQueryService;
@Autowired
protected RestWrapper restClient;
private UserModel testUser;
private UserModel siteCreator;
private FolderModel testFolder;
private SiteModel testSite1;
private SiteModel testSite2;
private FileModel fileNotInSite;
private FileModel file1;
private FileModel file2;
private FileModel file3;
private FileModel file4;
@BeforeClass(alwaysRun = true)
public void dataPreparation()
{
serverHealth.assertServerIsOnline();
Step.STEP("Create test users and a folder.");
testUser = dataUser.createRandomTestUser();
siteCreator = dataUser.createRandomTestUser();
testFolder = dataContent
.usingAdmin()
.usingResource(contentRoot())
.createFolder(new FolderModel(unique("FOLDER")));
}
@Test(groups = {TestGroup.SEARCH, TestGroup.SITES, TestGroup.REGRESSION})
public void testSiteUseCasesForCreateModifyDeleteSite()
{
// Remove the automatically created Sample Site
deleteSite(SAMPLE_SITE_ID);
// Sometimes this test may fail, so if previous data exists it must be deleted before the next run
Stream.of(fileNotInSite, file1, file2, file3, file4)
.filter(Objects::nonNull)
.forEach(this::deleteFile);
Stream.of(testSite1, testSite2)
.filter(Objects::nonNull)
.map(SiteModel::getId)
.forEach(this::deleteSite);
Step.STEP("Site creation use cases");
// Check there are no files within or without a site, that have the given filename prefix
assertSiteQueryResult(ALL_SITES, List.of());
assertSiteQueryResult(EVERYTHING, "AND", FILE_CONTENT_CONDITION, List.of());
// Create a file with the given filename prefix, outside any site
fileNotInSite = new FileModel(unique(FILENAME_PREFIX) + ".txt", FileType.TEXT_PLAIN, "Content for fileNotInSite");
fileNotInSite = dataContent
.usingAdmin()
.usingResource(testFolder)
.createContent(fileNotInSite);
// Search with condition of filename prefix - we should see the file using EVERYTHING but not from ALL_SITES
assertSiteQueryResult(ALL_SITES, "AND", FILE_CONTENT_CONDITION, List.of());
assertSiteQueryResult(EVERYTHING, "AND", FILE_CONTENT_CONDITION, List.of(fileNotInSite));
// No sites should exist - expect no results
assertSiteQueryResult(ALL_SITES, List.of());
assertSiteQueryResult(unique("NoSuchSite"), List.of());
// Create one empty public site - expect no results other than document library
testSite1 = createPublicSite();
assertSiteQueryResult(testSite1.getId(), List.of(DOCUMENT_LIBRARY));
assertSiteQueryResult(ALL_SITES, List.of(DOCUMENT_LIBRARY));
assertSiteQueryResult(EVERYTHING, "AND", FILE_CONTENT_CONDITION, List.of(fileNotInSite));
// Create a file in public site - expect one file plus the document library.
file1 = createContentInSite(testSite1, FILENAME_PREFIX + "test1");
assertSiteQueryResult(testSite1.getId(), List.of(DOCUMENT_LIBRARY, file1));
assertSiteQueryResult(ALL_SITES, List.of(DOCUMENT_LIBRARY, file1));
assertSiteQueryResult(EVERYTHING, "AND", FILE_CONTENT_CONDITION, List.of(fileNotInSite, file1));
// Create another file in public site - expect two files plus the document library.
file2 = createContentInSite(testSite1, FILENAME_PREFIX + "test2");
assertSiteQueryResult(testSite1.getId(), List.of(DOCUMENT_LIBRARY, file1, file2));
assertSiteQueryResult(ALL_SITES, List.of(DOCUMENT_LIBRARY, file1, file2));
assertSiteQueryResult(EVERYTHING, "AND", FILE_CONTENT_CONDITION, List.of(fileNotInSite, file1, file2));
// Create a second public site, empty - expect no results other than document library
testSite2 = createPublicSite();
assertSiteQueryResult(testSite2.getId(), List.of(DOCUMENT_LIBRARY));
assertSiteQueryResult(testSite1.getId(), List.of(DOCUMENT_LIBRARY, file1, file2));
assertSiteQueryResult(ALL_SITES, List.of(DOCUMENT_LIBRARY, file1, file2));
assertSiteQueryResult(EVERYTHING, "AND", FILE_CONTENT_CONDITION, List.of(fileNotInSite, file1, file2));
// Create a file in second public site - expect one file plus the document library.
file3 = createContentInSite(testSite2, FILENAME_PREFIX + "test3");
assertSiteQueryResult(testSite2.getId(), List.of(DOCUMENT_LIBRARY, file3));
assertSiteQueryResult(testSite1.getId(), List.of(DOCUMENT_LIBRARY, file1, file2));
assertSiteQueryResult(ALL_SITES, List.of(DOCUMENT_LIBRARY, file1, file2, file3));
assertSiteQueryResult(EVERYTHING, "AND", FILE_CONTENT_CONDITION, List.of(fileNotInSite, file1, file2, file3));
// Create another file in second public site - expect two files plus the document library.
file4 = createContentInSite(testSite2, FILENAME_PREFIX + "test4");
assertSiteQueryResult(testSite2.getId(), List.of(DOCUMENT_LIBRARY, file3, file4));
assertSiteQueryResult(testSite1.getId(), List.of(DOCUMENT_LIBRARY, file1, file2));
assertSiteQueryResult(ALL_SITES, List.of(DOCUMENT_LIBRARY, file1, file2, file3, file4));
assertSiteQueryResult(EVERYTHING, "AND", FILE_CONTENT_CONDITION, List.of(fileNotInSite, file1, file2, file3, file4));
// Test disjunction and conjunction using SITE: and SITE:
assertSiteQueryResult(testSite1.getId(), "OR", " SITE:" + testSite2.getId(), List.of(DOCUMENT_LIBRARY, file1, file2, file3, file4));
assertSiteQueryResult(testSite1.getId(), "OR", " SITE:" + unique("NoSuchSite"), List.of(DOCUMENT_LIBRARY, file1, file2));
assertSiteQueryResult(testSite1.getId(), "AND", " SITE:" + unique("NoSuchSite"), List.of());
// Test conjunction using SITE: and TEXT:
assertSiteQueryResult(testSite1.getId(), "AND", " TEXT:" + FILENAME_PREFIX + "test2*", List.of(file2));
assertSiteQueryResult(testSite1.getId(), "AND", " TEXT:" + FILENAME_PREFIX + "testX*", List.of());
assertSiteQueryResult(EVERYTHING, "AND", " TEXT:" + FILENAME_PREFIX + "test2*", List.of(file2));
assertSiteQueryResult(EVERYTHING, "AND", " TEXT:" + FILENAME_PREFIX + "testX*", List.of());
assertSiteQueryResult(ALL_SITES, "AND", " TEXT:" + FILENAME_PREFIX + "test2*", List.of(file2));
assertSiteQueryResult(ALL_SITES, "AND", " TEXT:" + FILENAME_PREFIX + "testX*", List.of());
// Test modify site
Step.STEP("Site modification use cases");
// Verify site creating user can see all files in both public sites
assertSiteQueryResult(siteCreator, testSite1.getId(), List.of(DOCUMENT_LIBRARY, file1, file2));
assertSiteQueryResult(siteCreator, testSite2.getId(), List.of(DOCUMENT_LIBRARY, file3, file4));
assertSiteQueryResult(siteCreator, ALL_SITES, List.of(DOCUMENT_LIBRARY, file1, file2, file3, file4));
assertSiteQueryResult(siteCreator, EVERYTHING, "AND", FILE_CONTENT_CONDITION, List.of(fileNotInSite, file1, file2, file3, file4));
// Verify user who is not a member of any sites can see all files in both public sites
UserModel publicUser = dataUser.createRandomTestUser();
assertSiteQueryResult(publicUser, testSite1.getId(), List.of(DOCUMENT_LIBRARY, file1, file2));
assertSiteQueryResult(publicUser, testSite2.getId(), List.of(DOCUMENT_LIBRARY, file3, file4));
assertSiteQueryResult(publicUser, ALL_SITES, List.of(DOCUMENT_LIBRARY, file1, file2, file3, file4));
assertSiteQueryResult(publicUser, EVERYTHING, "AND", FILE_CONTENT_CONDITION, List.of(fileNotInSite, file1, file2, file3, file4));
// Change first site's visibility to private - verify site creator can still see all files in both sites
dataSite.usingUser(siteCreator).updateSiteVisibility(testSite1, Visibility.PRIVATE);
assertSiteQueryResult(siteCreator, testSite1.getId(), List.of(DOCUMENT_LIBRARY, file1, file2));
assertSiteQueryResult(siteCreator, testSite2.getId(), List.of(DOCUMENT_LIBRARY, file3, file4));
assertSiteQueryResult(siteCreator, ALL_SITES, List.of(DOCUMENT_LIBRARY, file1, file2, file3, file4));
assertSiteQueryResult(siteCreator, EVERYTHING, "AND", FILE_CONTENT_CONDITION, List.of(fileNotInSite, file1, file2, file3, file4));
// Verify user who is not a member of any sites can see files in public site but not see files in private site
assertSiteQueryResult(publicUser, testSite1.getId(), List.of());
assertSiteQueryResult(publicUser, testSite2.getId(), List.of(DOCUMENT_LIBRARY, file3, file4));
assertSiteQueryResult(publicUser, ALL_SITES, List.of(DOCUMENT_LIBRARY, file3, file4));
assertSiteQueryResult(publicUser, EVERYTHING, "AND", FILE_CONTENT_CONDITION, List.of(fileNotInSite, file3, file4));
// Change site visibility back to public - user who is not a member of any sites can see files in both sites again
dataSite.usingUser(siteCreator).updateSiteVisibility(testSite1, Visibility.PUBLIC);
assertSiteQueryResult(publicUser, testSite1.getId(), List.of(DOCUMENT_LIBRARY, file1, file2));
assertSiteQueryResult(publicUser, testSite2.getId(), List.of(DOCUMENT_LIBRARY, file3, file4));
assertSiteQueryResult(publicUser, ALL_SITES, List.of(DOCUMENT_LIBRARY, file1, file2, file3, file4));
assertSiteQueryResult(publicUser, EVERYTHING, "AND", FILE_CONTENT_CONDITION, List.of(fileNotInSite, file1, file2, file3, file4));
// Test delete site
Step.STEP("Site deletion use cases");
// Delete one site - expect no results for that site
deleteSite(testSite1.getId());
assertSiteQueryResult(testSite1.getId(), List.of());
assertSiteQueryResult(testSite2.getId(), List.of(DOCUMENT_LIBRARY, file3, file4));
assertSiteQueryResult(ALL_SITES, List.of(DOCUMENT_LIBRARY, file3, file4));
assertSiteQueryResult(EVERYTHING, "AND", FILE_CONTENT_CONDITION, List.of(fileNotInSite, file3, file4));
// Delete remaining site - expect no results
deleteSite(testSite2.getId());
assertSiteQueryResult(testSite2.getId(), List.of());
assertSiteQueryResult(testSite1.getId(), List.of());
assertSiteQueryResult(ALL_SITES, List.of());
assertSiteQueryResult(EVERYTHING, "AND", FILE_CONTENT_CONDITION, List.of(fileNotInSite));
}
@Test(groups = {TestGroup.SEARCH, TestGroup.SITES, TestGroup.REGRESSION})
public void manipulatingFilesAndContentBetweenSites()
{
Step.STEP("Moving files between sites and modifying content use cases");
SiteModel publicSite1 = createPublicSite(siteCreator);
SiteModel publicSite2 = createPublicSite(siteCreator);
FileModel file5 = createContentInSite(publicSite1, "file5");
assertSiteQueryResult(publicSite1.getId(), List.of(DOCUMENT_LIBRARY, file5));
// Moving a file between sites.
moveFile(file5, publicSite1, publicSite2);
assertSiteQueryResult(publicSite1.getId(), List.of(DOCUMENT_LIBRARY));
assertSiteQueryResult(publicSite2.getId(), List.of(DOCUMENT_LIBRARY, file5));
// Moving a file out of a site.
moveFileOutsideOfSite(file5, publicSite2, testFolder);
assertSiteQueryResult(publicSite2.getId(), List.of(DOCUMENT_LIBRARY));
// Moving a file to the site
FileModel file6 = dataContent
.usingAdmin()
.usingResource(contentRoot())
.createContent(CMISUtil.DocumentType.TEXT_PLAIN);
FileModel file7 = createContentInSite(publicSite1, "file7"); // Ensuring that DocumentLibrary exists in the publicSite1
assertSiteQueryResult(publicSite1.getId(), List.of(DOCUMENT_LIBRARY, file7));
moveFileToTheSite(file6, publicSite1);
assertSiteQueryResult(publicSite1.getId(), List.of(DOCUMENT_LIBRARY, file6, file7));
// Removing file.
FileModel file8 = createContentInSite(publicSite2, "file8");
assertSiteQueryResult(publicSite2.getId(), List.of(DOCUMENT_LIBRARY, file8));
deleteFile(file8);
assertSiteQueryResult(publicSite2.getId(), List.of(DOCUMENT_LIBRARY));
// Document modification.
FileModel file9 = createContentInSite(publicSite2, "file9", "Initial Content.");
assertContentInGivenFileUnderSiteQueryResult(publicSite2.getId(), "initial", List.of(file9));
modifyDocument(file9, "Modified Content.");
assertContentInGivenFileUnderSiteQueryResult(publicSite2.getId(), "modified", List.of(file9));
// Cleanup.
deleteFiles(file6, file7, file9);
dataContent.usingAdmin().deleteSite(publicSite1);
dataContent.usingAdmin().deleteSite(publicSite2);
}
private void deleteFiles(FileModel... fileModels)
{
for (FileModel fileModel : fileModels)
{
deleteFile(fileModel);
}
}
private void moveFileToTheSite(FileModel file, SiteModel targetSite)
{
Session session = dataContent.getContentActions().getCMISSession(alfrescoHttpClientFactory.getAdminUser(), alfrescoHttpClientFactory.getAdminPassword());
ContentActions actions = dataContent.usingAdmin().getContentActions();
CmisObject objFrom = actions.getCmisObject(session, file.getName());
CmisObject objTarget = session.getObjectByPath("/Sites/" + targetSite.getId() + "/documentLibrary");
List parents;
CmisObject parent;
if (objFrom instanceof Document)
{
Document d = (Document) objFrom;
parents = d.getParents();
parent = session.getObject(((Folder) parents.get(0)).getId());
d.move(parent, objTarget);
}
else if (objFrom instanceof Folder)
{
Folder f = (Folder) objFrom;
parents = f.getParents();
parent = session.getObject(((Folder) parents.get(0)).getId());
f.move(parent, objTarget);
}
}
private void assertSiteQueryResult(String siteName, Collection<ContentModel> contentModels)
{
assertSiteQueryResult(testUser, siteName, contentModels);
}
private void assertSiteQueryResult(UserModel user, String siteName, Collection<ContentModel> contentModels)
{
final List<String> contentNames = contentModels
.stream()
.map(ContentModel::getName)
.collect(Collectors.toList());
for (final String language : LANGUAGES_TO_CHECK)
{
Step.STEP("Searching for SITE `" + siteName + "` using `" + language + "` language.");
final SearchRequest query = req(language, "SITE:" + siteName + " ");
if (contentNames.isEmpty())
{
searchQueryService.expectNoResultsFromQuery(query, user);
}
else
{
Collections.shuffle(contentNames);
searchQueryService.expectResultsFromQuery(query, user, contentNames.toArray(String[]::new));
}
}
}
private void assertSiteQueryResult(String site1Name, String operator, String condition, Collection<ContentModel> contentModels)
{
assertSiteQueryResult(testUser, site1Name, operator, condition, contentModels);
}
private void assertSiteQueryResult(UserModel user, String site1Name, String operator, String condition, Collection<ContentModel> contentModels)
{
final List<String> contentNames = contentModels
.stream()
.map(ContentModel::getName)
.collect(Collectors.toList());
for (final String language : LANGUAGES_TO_CHECK)
{
Step.STEP("Searching for SITE `" + site1Name + "` " + operator + " `" + condition + "` using `" + language + "` language.");
final SearchRequest query = req(language, "SITE:" + site1Name + " " + operator + condition);
if (contentNames.isEmpty())
{
searchQueryService.expectNoResultsFromQuery(query, user);
}
else
{
Collections.shuffle(contentNames);
searchQueryService.expectResultsFromQuery(query, user, contentNames.toArray(String[]::new));
}
}
}
private void assertContentInGivenFileUnderSiteQueryResult(String siteName, String content, Collection<ContentModel> contentModels)
{
final List<String> contentNames = contentModels
.stream()
.map(ContentModel::getName)
.collect(Collectors.toList());
for (final String language : LANGUAGES_TO_CHECK)
{
Step.STEP("Searching for SITE `" + siteName + "` and content:'" + content + "' using `" + language + "` language.");
final SearchRequest query = req(language, "SITE:" + siteName + " AND TEXT:" + content + " ");
searchQueryService.expectResultsFromQuery(query, testUser, contentNames.toArray(String[]::new));
}
}
private void modifyDocument(FileModel file, String newContent)
{
dataContent.usingUser(siteCreator).usingResource(file)
.updateContent(newContent);
}
private void deleteFile(ContentModel contentModel)
{
dataContent
.usingAdmin()
.usingResource(contentModel)
.deleteContent();
}
private void moveFile(FileModel file, SiteModel sourceSite, SiteModel targetSite)
{
moveFile(file, sourceSite, targetSite, null);
}
private void moveFile(FileModel file, SiteModel sourceSite, SiteModel targetSite, ContentModel targetFolder)
{
Session session = dataContent.getContentActions().getCMISSession(siteCreator.getUsername(), siteCreator.getPassword());
dataContent.usingUser(siteCreator).getContentActions()
.moveTo(
session,
sourceSite.getId(),
file.getName(),
targetSite.getId(),
targetFolder != null ? targetFolder.getName() : Strings.EMPTY // if empty then target folder is documentLibrary (which is default target location)
);
}
private void moveFileOutsideOfSite(FileModel file, SiteModel sourceSite, ContentModel targetContentModel)
{
Session session = dataContent.getContentActions().getCMISSession(alfrescoHttpClientFactory.getAdminUser(), alfrescoHttpClientFactory.getAdminPassword());
ContentActions actions = dataContent.usingAdmin().getContentActions();
CmisObject objFrom = actions.getCmisObject(session, sourceSite.getId(), file.getName());
CmisObject objTarget = actions.getCmisObject(session, targetContentModel.getCmisLocation());
List parents;
CmisObject parent;
if (objFrom instanceof Document)
{
Document d = (Document) objFrom;
parents = d.getParents();
parent = session.getObject(((Folder) parents.get(0)).getId());
d.move(parent, objTarget);
}
else if (objFrom instanceof Folder)
{
Folder f = (Folder) objFrom;
parents = f.getParents();
parent = session.getObject(((Folder) parents.get(0)).getId());
f.move(parent, objTarget);
}
}
private FileModel createContentInSite(SiteModel site, String fileName)
{
return createContentInSite(site, fileName, "Content for " + fileName);
}
private FileModel createContentInSite(SiteModel site, String fileName, String content)
{
final FileModel file = new FileModel(unique(fileName) + ".txt", FileType.TEXT_PLAIN, content);
return dataContent.usingUser(siteCreator)
.usingSite(site)
.createContent(file);
}
private SiteModel createPublicSite()
{
return createPublicSite(siteCreator);
}
private SiteModel createPublicSite(UserModel user)
{
SiteModel createdSite = dataSite.usingUser(user).createPublicRandomSite();
Step.STEP("Created public site '" + createdSite.getId() + "'.");
return createdSite;
}
private void deleteSite(String siteId)
{
Step.STEP("Deleting site '" + siteId + "', if it exists");
SiteModel siteToDelete = new SiteModel(siteId);
if (dataSite.usingAdmin().isSiteCreated(siteToDelete))
{
dataSite.usingAdmin().deleteSite(siteToDelete);
}
}
private static ContentModel contentRoot()
{
final ContentModel root = new ContentModel("-root-");
root.setNodeRef(root.getName());
return root;
}
private static String unique(String prefix)
{
return prefix + "-" + UUID.randomUUID();
}
}
@@ -1,279 +0,0 @@
/*
* #%L
* Alfresco Tas Elasticsearch
* %%
* Copyright (C) 2026 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.elasticsearch;
import static org.springframework.http.HttpStatus.OK;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import static org.alfresco.elasticsearch.SearchQueryService.req;
import java.util.*;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.alfresco.dataprep.AlfrescoHttpClientFactory;
import org.alfresco.rest.core.RestWrapper;
import org.alfresco.rest.model.RestTagModel;
import org.alfresco.rest.search.SearchRequest;
import org.alfresco.tas.AlfrescoStackInitializer;
import org.alfresco.utility.data.DataContent;
import org.alfresco.utility.data.DataUser;
import org.alfresco.utility.model.*;
import org.alfresco.utility.network.ServerHealth;
import org.alfresco.utility.report.log.Step;
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
initializers = AlfrescoStackInitializer.class)
public class ElasticsearchTagIndexingTests extends AbstractTestNGSpringContextTests
{
private static final Iterable<String> LANGUAGES_TO_CHECK = List.of("afts", "lucene");
@Autowired
ServerHealth serverHealth;
@Autowired
DataUser dataUser;
@Autowired
DataContent dataContent;
@Autowired
private AlfrescoHttpClientFactory alfrescoHttpClientFactory;
@Autowired
SearchQueryService searchQueryService;
@Autowired
protected RestWrapper restClient;
private UserModel testUser;
private FolderModel testFolder;
@BeforeClass(alwaysRun = true)
public void dataPreparation()
{
serverHealth.assertServerIsOnline();
testUser = dataUser.createRandomTestUser();
testFolder = dataContent
.usingAdmin()
.usingResource(contentRoot())
.createFolder(new FolderModel(unique("FOLDER")));
}
@Test(groups = {TestGroup.SEARCH, TestGroup.TAGS, TestGroup.REGRESSION})
public void testTAGUseCases()
{
final String tag1 = unique("TAG1");
// No result for not existing tag
assertTagQueryResult(tag1, List.of());
// Tag first file - tag is created - expect single result
final FileModel file1 = givenFile("test1");
tagContent(file1, tag1);
assertTagQueryResult(tag1, List.of(file1));
// Tag another file - tag is reused - expect two items in the result
final FileModel file2 = givenFile("test2");
tagContent(file2, tag1);
assertTagQueryResult(tag1, List.of(file1, file2));
final String tag2 = unique("TAG2");
// Just a sanity check - No result for the second tag
assertTagQueryResult(tag2, List.of());
// Tag second file with a new tag
final String tag2Id = tagContent(file2, tag2);
assertTagQueryResult(tag1, List.of(file1, file2));
assertTagQueryResult(tag2, List.of(file2));
// Tag new file with the second tag
final FileModel file3 = givenFile("test3");
tagContent(file3, tag2);
assertTagQueryResult(tag1, List.of(file1, file2));
assertTagQueryResult(tag2, List.of(file2, file3));
// test disjunction and conjunction
assertTagQueryResult(tag1, "OR", tag2, List.of(file1, file2, file3));
assertTagQueryResult(tag1, "AND", tag2, List.of(file2));
final String unknownTag = unique("unknown");
assertTagQueryResult(tag1, "OR", unknownTag, List.of(file1, file2));
assertTagQueryResult(tag1, "AND", unknownTag, List.of());
// Delete file
deleteFile(file1);
assertTagQueryResult(tag1, List.of(file2));
assertTagQueryResult(tag2, List.of(file2, file3));
// Rename tag
final String newTag2 = unique("NEW-TAG2");
assertTagQueryResult(newTag2, List.of());
renameTag(tag2Id, newTag2);
assertTagQueryResult(tag1, List.of(file2));
assertTagQueryResult(newTag2, List.of(file2, file3));
assertTagQueryResult(tag2, List.of());
// Delete tag
deleteTag(file3, newTag2);
assertTagQueryResult(tag1, List.of(file2));
assertTagQueryResult(tag2, List.of());
assertTagQueryResult(newTag2, List.of(file2));
}
private void assertTagQueryResult(String tag1Name, String operator, String tag2Name, Collection<ContentModel> contentModels)
{
final List<String> contentNames = contentModels
.stream()
.map(ContentModel::getName)
.collect(Collectors.toList());
for (final String tag1 : tagVariantsToCheck(tag1Name))
{
for (final String tag2 : tagVariantsToCheck(tag2Name))
{
for (final String language : LANGUAGES_TO_CHECK)
{
Step.STEP("Searching for TAG `" + tag1 + "` " + operator + " `" + tag2 + "` using `" + language + "` language.");
final SearchRequest query = req(language, "TAG:" + tag1 + " " + operator + " TAG:" + tag2);
if (contentNames.isEmpty())
{
searchQueryService.expectNoResultsFromQuery(query, testUser);
}
else
{
Collections.shuffle(contentNames);
searchQueryService.expectResultsFromQuery(query, testUser, contentNames.toArray(String[]::new));
}
}
}
}
}
private void assertTagQueryResult(String tagName, Collection<ContentModel> contentModels)
{
final List<String> contentNames = contentModels
.stream()
.map(ContentModel::getName)
.collect(Collectors.toList());
for (final String tag : tagVariantsToCheck(tagName))
{
for (final String language : LANGUAGES_TO_CHECK)
{
Step.STEP("Searching for TAG `" + tag + "` using `" + language + "` language.");
final SearchRequest query = req(language, "TAG:" + tag);
if (contentNames.isEmpty())
{
searchQueryService.expectNoResultsFromQuery(query, testUser);
}
else
{
Collections.shuffle(contentNames);
searchQueryService.expectResultsFromQuery(query, testUser, contentNames.toArray(String[]::new));
}
}
}
}
private Iterable<String> tagVariantsToCheck(String tagName)
{
return List.of(
tagName.toLowerCase(Locale.ROOT),
tagName.toUpperCase(Locale.ROOT),
"\"" + tagName.toLowerCase(Locale.ROOT) + "\"",
"\"" + tagName.toUpperCase(Locale.ROOT) + "\"");
}
private FileModel givenFile(final String fileName)
{
final FileModel file = new FileModel(unique(fileName), FileType.TEXT_PLAIN, "Content for " + fileName);
return dataContent
.usingAdmin()
.usingResource(testFolder)
.createContent(file);
}
private String tagContent(ContentModel contentModel, String tag)
{
var tagModel = new TagModel(tag);
dataContent
.usingAdmin()
.usingResource(contentModel)
.addTagToContent(tagModel);
final String tagNodeId = dataContent.getContentActions().getTagNodeRef(
dataContent.getCurrentUser().getUsername(), dataContent.getCurrentUser().getPassword(),
contentModel.getCmisLocation(), tag);
assertNotNull(tagNodeId, "Tag node ref must exist.");
return tagNodeId;
}
private void deleteFile(ContentModel contentModel)
{
dataContent
.usingAdmin()
.usingResource(contentModel)
.deleteContent();
}
private void deleteTag(ContentModel contentModel, String tagName)
{
boolean deleted = dataContent
.usingAdmin()
.getContentActions()
.removeTag(
dataContent.getCurrentUser().getUsername(), dataContent.getCurrentUser().getPassword(),
contentModel.getCmisLocation(), tagName);
assertTrue(deleted, "Tag should be deleted.");
}
private void renameTag(String tagId, String newName)
{
RestTagModel tag = RestTagModel.builder().id(tagId).create();
RestTagModel update = restClient.authenticateUser(dataContent.getAdminUser()).withCoreAPI().usingTag(tag).update(newName);
restClient.assertStatusCodeIs(OK);
}
private static ContentModel contentRoot()
{
final ContentModel root = new ContentModel("-root-");
root.setNodeRef(root.getName());
return root;
}
private static String unique(String prefix)
{
return prefix + "-" + UUID.randomUUID();
}
}
@@ -1,316 +0,0 @@
/*
* #%L
* Alfresco Tas Elasticsearch
* %%
* Copyright (C) 2026 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.elasticsearch;
import static org.alfresco.elasticsearch.SearchQueryService.req;
import static org.alfresco.utility.data.RandomData.getRandomFile;
import static org.alfresco.utility.data.RandomData.getRandomName;
import static org.alfresco.utility.report.log.Step.STEP;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.alfresco.rest.model.RestTagModel;
import org.alfresco.rest.search.RestRequestDefaultsModel;
import org.alfresco.rest.search.SearchRequest;
import org.alfresco.tas.AlfrescoStackInitializer;
import org.alfresco.utility.Utility;
import org.alfresco.utility.data.DataContent;
import org.alfresco.utility.data.DataUser;
import org.alfresco.utility.model.ContentModel;
import org.alfresco.utility.model.FileModel;
import org.alfresco.utility.model.FileType;
import org.alfresco.utility.model.FolderModel;
import org.alfresco.utility.model.TestGroup;
import org.alfresco.utility.model.UserModel;
import org.alfresco.utility.network.ServerHealth;
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
initializers = AlfrescoStackInitializer.class)
public class ElasticsearchTemplateSearchTests extends AbstractTestNGSpringContextTests
{
private static final String SEARCH_TERM = "sample";
@Autowired
private ServerHealth serverHealth;
@Autowired
private DataUser dataUser;
@Autowired
private DataContent dataContent;
@Autowired
private SearchQueryService searchQueryService;
private UserModel testUser;
private FolderModel testParentFolder;
/** Pre-built ANCESTOR clause for the test parent folder. */
private String ancestorClause;
private ContentModel fileWithTermInName;
private ContentModel fileWithPhraseInContent;
private ContentModel fileWithTermInTitle;
private ContentModel fileWithTermInDescription;
private ContentModel fileWithTermInTag;
private ContentModel fileWithDifferentTermInName;
private ContentModel folderWithTermInName;
@BeforeClass(alwaysRun = true)
public void dataPreparation() throws Exception
{
serverHealth.assertServerIsOnline();
STEP("Create a dedicated parent folder so all queries can be scoped via ANCESTOR");
testParentFolder = createTestParentFolder();
ancestorClause = "ANCESTOR:\"" + testParentFolder.getNodeRef() + "\"";
STEP("Create a test user and few files and folders containing searched term in name, title, description, content and tag");
fileWithTermInName = createFile(SEARCH_TERM + ".txt", "some text");
fileWithPhraseInContent = createFile(getRandomFile(FileType.TEXT_PLAIN), "Dummy " + SEARCH_TERM + " irrelevant text");
fileWithTermInTitle = createRandomFileWithTitle(SEARCH_TERM);
fileWithTermInDescription = createRandomFileWithDescription(SEARCH_TERM);
fileWithTermInTag = createRandomFileWithTag(SEARCH_TERM);
fileWithDifferentTermInName = createFile("dummy.txt", "content without phrase");
folderWithTermInName = createFolder(SEARCH_TERM);
testUser = dataUser.createRandomTestUser();
STEP("Wait for the batch indexer to index the last-created node under the test parent folder");
SearchRequest probe = req("afts", ancestorClause + " AND TYPE:'cm:folder'", Map.of());
Utility.sleep(500, 60000, () -> searchQueryService.expectNodeRefsFromQuery(
probe, dataUser.getAdminUser(), folderWithTermInName.getNodeRef()));
}
@AfterClass
public void dataCleanup()
{
STEP("Clean up the test parent folder (cascades to all children) and the test user");
if (testParentFolder != null)
{
dataContent.usingAdmin().usingResource(testParentFolder).deleteContent();
}
if (testUser != null)
{
dataUser.deleteUser(testUser);
}
}
@Test(groups = {TestGroup.SEARCH})
public void testAftsQuery_simpleTemplate()
{
STEP("Search for files by name using simple template with one property");
Map<String, String> templates = Map.of("_NODE", "%cm:name");
String query = ancestorClause + " AND TYPE:'cm:content' AND _NODE:" + SEARCH_TERM;
SearchRequest request = req("afts", query, templates);
searchQueryService.expectNodeRefsFromQuery(request, testUser, fileWithTermInName.getNodeRef());
}
@Test(groups = {TestGroup.SEARCH})
public void testAftsQuery_simpleTemplateWithPhrase()
{
STEP("Search for files containing specific phrase using simple template with one property");
Map<String, String> templates = Map.of("_NODE", "%TEXT");
String query = ancestorClause + " AND TYPE:'cm:content' AND _NODE:\"" + SEARCH_TERM + " irrelevant\"";
SearchRequest request = req("afts", query, templates);
searchQueryService.expectNodeRefsFromQuery(request, testUser, fileWithPhraseInContent.getNodeRef());
}
@Test(groups = {TestGroup.SEARCH})
public void testAftsQuery_templateWithTwoParameters()
{
STEP("Search for files using template containing multiple properties");
Map<String, String> templates = Map.of("_NODE", "%(cm:name cm:title)");
String query = ancestorClause + " AND TYPE:'cm:content' AND _NODE:" + SEARCH_TERM;
SearchRequest request = req("afts", query, templates);
searchQueryService.expectNodeRefsFromQuery(request, testUser, fileWithTermInName.getNodeRef(), fileWithTermInTitle.getNodeRef());
}
@Test(groups = {TestGroup.SEARCH})
public void testAftsQuery_nestedTemplate()
{
Map<String, String> templates = Map.of(
"_NODE", "%(cm:name cm:title)",
"_NODET", "%(_NODEX TAG)",
"_NODEX", "%(_NODE cm:description)"
);
STEP("Search for files using more complex two-level nested template containing multiple properties");
String query = ancestorClause + " AND TYPE:'cm:content' AND _NODEX:" + SEARCH_TERM;
SearchRequest request = req("afts", query, templates);
searchQueryService.expectNodeRefsFromQuery(request, testUser,
fileWithTermInName.getNodeRef(), fileWithTermInDescription.getNodeRef(), fileWithTermInTitle.getNodeRef());
STEP("Search for files using three-level nested template containing multiple properties");
String queryIncludingTag = ancestorClause + " AND TYPE:'cm:content' AND _NODET:" + SEARCH_TERM;
SearchRequest requestIncludingTag = req("afts", queryIncludingTag, templates);
searchQueryService.expectNodeRefsFromQuery(requestIncludingTag, testUser,
fileWithTermInName.getNodeRef(), fileWithTermInDescription.getNodeRef(), fileWithTermInTitle.getNodeRef(), fileWithTermInTag.getNodeRef());
}
@Test(groups = {TestGroup.SEARCH})
public void testAftsQuery_templateNameAsQueryDefaultFieldName()
{
STEP("Search for files using template containing multiple properties, as a default search field");
Map<String, String> templates = Map.of("_NODE", "%(cm:name cm:title)");
// ANCESTOR must be its own clause; only the bare term uses the default field name.
String query = ancestorClause + " AND TYPE:'cm:content' AND " + SEARCH_TERM;
SearchRequest request = req("afts", query, templates);
request.setDefaults(RestRequestDefaultsModel.builder().defaultFieldName("_NODE").create());
searchQueryService.expectNodeRefsFromQuery(request, testUser, fileWithTermInName.getNodeRef(), fileWithTermInTitle.getNodeRef());
}
@Test(groups = {TestGroup.SEARCH})
public void testAftsQuery_templateWithFixedValue()
{
STEP("Search for files and folders using template containing multiple properties, including a fixed one");
Map<String, String> templates = Map.of("_NODE", "%cm:name AND TYPE:'cm:folder'");
String query = ancestorClause + " AND _NODE:" + SEARCH_TERM;
SearchRequest request = req("afts", query, templates);
searchQueryService.expectNodeRefsFromQuery(request, testUser, folderWithTermInName.getNodeRef());
}
@Test(groups = {TestGroup.SEARCH})
public void testAftsQuery_boostedTemplate()
{
STEP("Search for files using templates and boosts, where second term has higher priority ");
Map<String, String> templates = Map.of("_NODE", "%cm:name");
String query = ancestorClause + " AND TYPE:'cm:content' AND (_NODE:" + SEARCH_TERM + "^0.5 OR _NODE:dummy^2)";
SearchRequest request = req("afts", query, templates);
searchQueryService.expectResultsInOrder(request, testUser, fileWithDifferentTermInName.getName(), fileWithTermInName.getName());
STEP("Search for files using templates and boosts, where first term has higher priority ");
String queryInvertedBoost = ancestorClause + " AND TYPE:'cm:content' AND (_NODE:" + SEARCH_TERM + "^2 OR _NODE:dummy^0.5)";
SearchRequest requestInvertedBoost = req("afts", queryInvertedBoost, templates);
searchQueryService.expectResultsInOrder(requestInvertedBoost, testUser, fileWithTermInName.getName(), fileWithDifferentTermInName.getName());
}
@Test(groups = {TestGroup.SEARCH})
public void testAftsQuery_expandedTemplate()
{
Map<String, String> templates = Map.of("_NODE", "%cm:name");
String query = ancestorClause + " AND TYPE:'cm:content' AND ~_NODE:" + SEARCH_TERM;
SearchRequest request = req("afts", query, templates);
searchQueryService.expectNodeRefsFromQuery(request, testUser, fileWithTermInName.getNodeRef());
}
private FolderModel createTestParentFolder()
{
ContentModel contentRoot = new ContentModel("-root-");
contentRoot.setNodeRef(contentRoot.getName());
FolderModel parent = new FolderModel(getRandomName("templateSearchTests-"));
return dataContent
.usingAdmin()
.usingResource(contentRoot)
.createFolder(parent);
}
private ContentModel createRandomFileWithTitle(String title)
{
return createRandomFile(title, null, null);
}
private ContentModel createRandomFileWithDescription(String description)
{
return createRandomFile(null, description, null);
}
private ContentModel createRandomFileWithTag(String tag)
{
return createRandomFile(null, null, tag);
}
private ContentModel createRandomFile(String title, String description, String tag)
{
return createFile(getRandomFile(FileType.TEXT_PLAIN), getRandomName("dummy text "), title, description, tag);
}
private ContentModel createFile(String filename, String content)
{
return createFile(filename, content, null, null, null);
}
private ContentModel createFile(String filename, String content, String title, String description, String tag)
{
FileModel fileModel = new FileModel(filename, FileType.TEXT_PLAIN, content);
fileModel.setTitle(title);
fileModel.setDescription(description);
// Create inside the dedicated parent folder so queries scoped with ANCESTOR will pick this up
// (and so bootstrap / cross-class content does NOT match those queries).
FileModel file = dataContent
.usingAdmin()
.usingResource(testParentFolder)
.createContent(fileModel);
if (StringUtils.isNotBlank(tag))
{
dataContent
.usingAdmin()
.usingResource(file)
.addTagToContent(RestTagModel.builder().tag(tag).create());
}
return file;
}
private ContentModel createFolder(String folderName)
{
return createFolder(folderName, null, null, null);
}
private ContentModel createFolder(String folderName, String title, String description, String tag)
{
FolderModel folderModel = new FolderModel(folderName, title, description);
FolderModel folder = dataContent
.usingAdmin()
.usingResource(testParentFolder)
.createFolder(folderModel);
if (StringUtils.isNotBlank(tag))
{
dataContent
.usingAdmin()
.usingResource(folder)
.addTagToContent(RestTagModel.builder().tag(tag).create());
}
return folder;
}
}
@@ -1,198 +0,0 @@
/*
* #%L
* Alfresco Tas Elasticsearch
* %%
* Copyright (C) 2026 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.elasticsearch;
import static org.alfresco.elasticsearch.SearchQueryService.req;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.alfresco.tas.AlfrescoStackInitializer;
import org.alfresco.utility.data.DataContent;
import org.alfresco.utility.data.DataSite;
import org.alfresco.utility.data.DataUser;
import org.alfresco.utility.model.FileModel;
import org.alfresco.utility.model.FileType;
import org.alfresco.utility.model.SiteModel;
import org.alfresco.utility.model.TestGroup;
import org.alfresco.utility.model.UserModel;
import org.alfresco.utility.network.ServerHealth;
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
initializers = AlfrescoStackInitializer.class)
/**
* Tests verifying a range of simple AFTS queries using the cm:name field.
*/
public class ElasticsearchTokenisationTests extends AbstractTestNGSpringContextTests
{
private static final String ALPHABETIC_NO_SPACE = "TestFileabc.txt";
private static final String NUMERIC_NO_SPACE = "TestFile123.txt";
private static final String ALPHABETIC_WITH_SPACE = "TestFile abc.txt";
private static final String NUMERIC_WITH_SPACE = "TestFile 123.txt";
@Autowired
private DataUser dataUser;
@Autowired
private DataContent dataContent;
@Autowired
private DataSite dataSite;
@Autowired
private ServerHealth serverHealth;
@Autowired
protected SearchQueryService searchQueryService;
private UserModel user;
private SiteModel siteModel;
/**
* Create a site containing four documents.
*/
@BeforeClass(alwaysRun = true)
public void dataPreparation()
{
serverHealth.assertServerIsOnline();
user = dataUser.createRandomTestUser();
siteModel = dataSite.usingUser(user).createPrivateRandomSite();
createContent(ALPHABETIC_NO_SPACE, "", siteModel, user);
createContent(NUMERIC_NO_SPACE, "", siteModel, user);
createContent(ALPHABETIC_WITH_SPACE, "", siteModel, user);
createContent(NUMERIC_WITH_SPACE, "", siteModel, user);
}
/** Check that searching for "TestFileabc.txt" only returns that file. */
@Test(groups = TestGroup.SEARCH)
public void searchNoSpaceAlphabetic()
{
searchQueryService.expectResultsFromQuery(req("name:\"TestFileabc.txt\""), user, ALPHABETIC_NO_SPACE);
}
/** Check that searching for "TestFile abc.txt" only returns that file. */
@Test(groups = TestGroup.SEARCH)
public void searchWithSpaceAlphabetic()
{
searchQueryService.expectResultsFromQuery(req("name:\"TestFile abc.txt\""), user, ALPHABETIC_WITH_SPACE);
}
/** Check that searching for "TestFile123.txt" returns both files with numbers in. */
@Test(groups = TestGroup.SEARCH)
public void searchNoSpaceNumeric()
{
searchQueryService.expectResultsFromQuery(req("name:\"TestFile123.txt\""), user, NUMERIC_NO_SPACE, NUMERIC_WITH_SPACE);
}
/** Check that searching for "TestFile 123.txt" returns both files with numbers in. */
@Test(groups = TestGroup.SEARCH)
public void searchWithSpaceNumeric()
{
searchQueryService.expectResultsFromQuery(req("name:\"TestFile 123.txt\""), user, NUMERIC_NO_SPACE, NUMERIC_WITH_SPACE);
}
/** Check that searching for "TestFile" doesn't return TestFileabc.txt. */
@Test(groups = TestGroup.SEARCH)
public void searchBaseFileName()
{
searchQueryService.expectResultsFromQuery(req("name:\"TestFile\""), user, NUMERIC_NO_SPACE, ALPHABETIC_WITH_SPACE, NUMERIC_WITH_SPACE);
}
/** Check that searching for TestFileabc.txt with no quotes returns just that file. */
@Test(groups = TestGroup.SEARCH)
public void searchNoQuotesNoSpaceAlphabetic()
{
searchQueryService.expectResultsFromQuery(req("name:TestFileabc.txt"), user, ALPHABETIC_NO_SPACE);
}
/** Check that searching for TestFile123.txt with no quotes returns both files with numbers in. */
@Test(groups = TestGroup.SEARCH)
public void searchNoQuotesNoSpaceNumeric()
{
searchQueryService.expectResultsFromQuery(req("name:TestFile123.txt"), user, NUMERIC_NO_SPACE, NUMERIC_WITH_SPACE);
}
/** Check that searching for TestFile doesn't return TestFileabc.txt. */
@Test(groups = TestGroup.SEARCH)
public void searchBaseFileNameNoQuotes()
{
searchQueryService.expectResultsFromQuery(req("name:TestFile"), user, NUMERIC_NO_SPACE, ALPHABETIC_WITH_SPACE, NUMERIC_WITH_SPACE);
}
/** Check that including an escaped space (_x0020_) in double quotes means that nothing is matched. */
@Test(groups = TestGroup.SEARCH)
public void searchEncodedSpaceInQuotes()
{
searchQueryService.expectNoResultsFromQuery(req("name:\"TestFile_x0020_abc.txt\""), user);
}
/** Check that searching for 'TestFileabc.txt' only returns that file. */
@Test(groups = TestGroup.SEARCH)
public void searchNoSpaceAlphabeticSingleQuote()
{
searchQueryService.expectResultsFromQuery(req("name:'TestFileabc.txt'"), user, ALPHABETIC_NO_SPACE);
}
/** Check that searching for 'TestFile abc.txt' only returns that file. */
@Test(groups = TestGroup.SEARCH)
public void searchWithSpaceAlphabeticSingleQuote()
{
searchQueryService.expectResultsFromQuery(req("name:'TestFile abc.txt'"), user, ALPHABETIC_WITH_SPACE);
}
/** Check that searching for 'TestFile123.txt' returns both files with numbers in. */
@Test(groups = TestGroup.SEARCH)
public void searchNoSpaceNumericSingleQuote()
{
searchQueryService.expectResultsFromQuery(req("name:'TestFile123.txt'"), user, NUMERIC_NO_SPACE, NUMERIC_WITH_SPACE);
}
/** Check that searching for 'TestFile 123.txt' returns both files with numbers in. */
@Test(groups = TestGroup.SEARCH)
public void searchWithSpaceNumericSingleQuote()
{
searchQueryService.expectResultsFromQuery(req("name:'TestFile 123.txt'"), user, NUMERIC_NO_SPACE, NUMERIC_WITH_SPACE);
}
/** Check that searching for 'TestFile' doesn't return TestFileabc.txt. */
@Test(groups = TestGroup.SEARCH)
public void searchBaseFileNameSingleQuote()
{
searchQueryService.expectResultsFromQuery(req("name:'TestFile'"), user, NUMERIC_NO_SPACE, ALPHABETIC_WITH_SPACE, NUMERIC_WITH_SPACE);
}
private FileModel createContent(String filename, String content, SiteModel site, UserModel user)
{
FileModel fileModel = new FileModel(filename, FileType.TEXT_PLAIN, content);
return dataContent.usingUser(user).usingSite(site)
.createContent(fileModel);
}
}
@@ -1,192 +0,0 @@
/*
* #%L
* Alfresco Tas Elasticsearch
* %%
* Copyright (C) 2026 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.elasticsearch;
import static java.lang.String.format;
import static org.alfresco.elasticsearch.SearchQueryService.req;
import static org.alfresco.utility.report.log.Step.STEP;
import org.springframework.beans.factory.annotation.Autowired;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.alfresco.rest.repo.resource.Categories;
import org.alfresco.rest.search.SearchRequest;
import org.alfresco.utility.model.TestGroup;
/**
* Tests verifying indexing of secondary children and ANCESTOR and CATEGORY index in Elasticsearch.
*/
@SuppressWarnings({"PMD.JUnitTestsShouldIncludeAssert", "PMD.MethodNamingConventions", "PMD.LocalVariableNamingConventions"}) // these are testng tests and use searchQueryService.expectResultsFromQuery for assertion
public class NodeWithCategoryIndexingTests extends NodesSecondaryChildrenRelatedTests
{
@Autowired
private Categories categories;
/* A --- B (folders) \____ \ K --- L (categories) */
@BeforeClass(alwaysRun = true)
@Override
public void dataPreparation()
{
super.dataPreparation();
// given
STEP("Create nested folders in site's Document Library.");
folders.add().nestedRandomFolders(A, B).create();
STEP("Create nested categories.");
categories.add().nestedRandomCategories(K, L).create();
STEP("Link folders to category.");
folders.modify(A).linkTo(categories.get(L));
}
@Test(groups = TestGroup.SEARCH)
public void testParentQueryAgainstCategory()
{
// then
STEP("Verify that searching by PARENT and category will find one descendant node: categoryL.");
SearchRequest query = req("PARENT:" + categories.get(K).getId());
searchQueryService.expectResultsFromQuery(query, testUser, categories.get(L).getName());
}
@Test(groups = TestGroup.SEARCH)
public void testParentQueryAgainstFolder()
{
// then
STEP("Verify that searching by PARENT and category will find one descendant node: folderA.");
SearchRequest query = req("PARENT:" + categories.get(L).getId());
searchQueryService.expectResultsFromQuery(query, testUser, folders.get(A).getName());
}
@Test(groups = TestGroup.SEARCH)
public void testParentQueryAgainstFolderAfterCategoryDeletion()
{
// given
STEP("Create nested folders in site's Document Library.");
folders.add().randomFolder(C).create();
STEP("Create nested categories.");
categories.add().randomCategory(M).create();
STEP("Link folders to category.");
folders.modify(C).linkTo(categories.get(M));
// when
STEP("Verify that searching by PARENT and category will find one descendant node: folderC.");
SearchRequest query = req("PARENT:" + categories.get(M).getId());
searchQueryService.expectResultsFromQuery(query, testUser, folders.get(C).getName());
// then
STEP("Delete categoryM.");
categories.delete(categories.get(M));
STEP("Verify that searching by PARENT and deleted category will find no descendant nodes.");
searchQueryService.expectResultsFromQuery(query, testUser);
}
@Test(groups = TestGroup.SEARCH, enabled = false) // re-enable after ACS-6588, ACS-6592
public void testParentQueryAgainstFolderAfterParentCategoryDeletion()
{
// given
STEP("Create nested folders in site's Document Library.");
folders.add().randomFolder(X).create();
STEP("Create nested categories.");
categories.add().nestedRandomCategories(P, Q).create();
STEP("Link folders to category.");
folders.modify(X).linkTo(categories.get(Q));
// when
STEP("Verify that searching by PARENT and category will find one descendant node: folderC.");
SearchRequest query = req("PARENT:" + categories.get(Q).getId());
searchQueryService.expectResultsFromQuery(query, testUser, folders.get(X).getName());
// then
STEP("Delete categoryP.");
categories.delete(categories.get(P));
STEP("Verify that searching by PARENT and deleted category will find no descendant nodes.");
searchQueryService.expectResultsFromQuery(query, testUser);
}
@Test(groups = TestGroup.SEARCH)
public void testSearchByPath()
{
// given
String Kname = categories.get(K).getName();
String Lname = categories.get(L).getName();
String Aname = folders.get(A).getName();
// then
STEP("Verify that searching by PATH and category will find: folderA");
SearchRequest query = req(format("PATH:'/cm:categoryRoot/cm:generalclassifiable/cm:%s/cm:%s/cm:%s'", Kname, Lname, Aname));
searchQueryService.expectResultsFromQuery(query, testUser, folders.get(A).getName());
}
@Test(groups = TestGroup.SEARCH)
public void ensureCategoriesAreNotTransitive_lookingForExactChildren()
{
// given
String Kname = categories.get(K).getName();
String Lname = categories.get(L).getName();
String Aname = folders.get(A).getName();
String Bname = folders.get(B).getName();
// then
STEP("Verify that searching by PATH for nested folder will return no results (Dependency to category is not transitive)");
SearchRequest query = req(format("PATH:'/cm:categoryRoot/cm:generalclassifiable/cm:%s/cm:%s/cm:%s/cm:%s'", Kname, Lname, Aname, Bname));
searchQueryService.expectNoResultsFromQuery(query, testUser);
}
@Test(groups = TestGroup.SEARCH)
public void ensureCategoriesAreNotTransitive_lookingForAllDescendants()
{
// given
String Kname = categories.get(K).getName();
String Lname = categories.get(L).getName();
// then
STEP("Verify that searching by PATH and category will find: folderA");
SearchRequest query = req(format("PATH:'/cm:categoryRoot/cm:generalclassifiable/cm:%s/cm:%s//*'", Kname, Lname));
searchQueryService.expectResultsFromQuery(query, testUser, folders.get(A).getName());
}
@Test(groups = TestGroup.SEARCH)
public void ensureCategoriesAreNotTransitive_lookingForAllDescendants_byParentCategory()
{
// given
String Kname = categories.get(K).getName();
// then
STEP("Verify that searching by PATH and category will find: folderA");
SearchRequest query = req(format("PATH:'/cm:categoryRoot/cm:generalclassifiable/cm:%s//*'", Kname));
searchQueryService.expectResultsFromQuery(query, testUser, categories.get(L).getName(), folders.get(A).getName());
}
}
@@ -1,368 +0,0 @@
/*
* #%L
* Alfresco Tas Elasticsearch
* %%
* Copyright (C) 2026 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.elasticsearch;
import static org.alfresco.elasticsearch.SearchQueryService.req;
import static org.alfresco.utility.report.log.Step.STEP;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.alfresco.rest.search.SearchRequest;
import org.alfresco.utility.Utility;
import org.alfresco.utility.model.FileModel;
import org.alfresco.utility.model.FolderModel;
import org.alfresco.utility.model.TestGroup;
/**
* Tests verifying indexing of secondary children and ANCESTOR index in Elasticsearch.
*/
@SuppressWarnings({"PMD.JUnitTestsShouldIncludeAssert"}) // these are TAS E2E tests and use searchQueryService.expectResultsFromQuery for assertion
public class NodesSecondaryAncestorIndexingTests extends NodesSecondaryChildrenRelatedTests
{
private FileModel fileInP;
/**
* Creates a user and a private site containing below hierarchy of folders.
*
* <pre>
* Site
* DL (Document Library)
* += fA += fB += fC (folderC)
* / / |
* + + +
* += fK += fL += fM
* | +
* + |
* += fX += fY += fZ
* += fP += file -+ fA
* += fQ
* += fR
* += fS
* </pre>
*
* Parent += Child - primary parent-child relationship Parent +- Child - secondary parent-child relationship
*/
@BeforeClass(alwaysRun = true)
@Override
public void dataPreparation()
{
super.dataPreparation();
// given
STEP("Create few sets of nested folders in site's Document Library.");
folders.add().nestedRandomFolders(A, B, C).create();
folders.add().nestedRandomFolders(K, L, M).create();
folders.add().nestedRandomFolders(X, Y, Z).create();
folders.add().randomFolders(P, Q, R, S).create();
fileInP = folders.modify(P).add().randomFile().create();
STEP("Create few secondary parent-child relationships.");
folders.modify(K).add().secondaryContent(folders.get(B));
folders.modify(X).add().secondaryContent(folders.get(K));
folders.modify(L).add().secondaryContent(folders.get(C), folders.get(Y));
folders.modify(M).add().secondaryContent(folders.get(C));
folders.modify(A).add().secondaryContent(fileInP);
}
@Test(groups = TestGroup.SEARCH)
public void testSecondaryAncestorWithNodeHavingOneSecondaryChild()
{
// then
STEP("Verify that searching by ANCESTOR and folderM will find one descendant node: folderC.");
SearchRequest query = req("ANCESTOR:" + folders.get(M).getNodeRef());
searchQueryService.expectResultsFromQuery(query, testUser,
folders.get(C).getName());
}
@Test(groups = TestGroup.SEARCH)
public void testSecondaryAncestorWithNodeHavingTwoSecondaryChildren()
{
// then
STEP("Verify that searching by ANCESTOR and folderL will find nodes: folderM, folderC, folderY and folderZ.");
SearchRequest queryAncestorL = req("ANCESTOR:" + folders.get(L).getNodeRef());
searchQueryService.expectResultsFromQuery(queryAncestorL, testUser,
// primary descendant
folders.get(M).getName(),
// secondary descendants
folders.get(C).getName(),
folders.get(Y).getName(),
folders.get(Z).getName());
}
@Test(groups = TestGroup.SEARCH)
public void testSecondaryAncestorWithDocumentAsSecondaryChild()
{
// then
STEP("Verify that searching by ANCESTOR and folderA will find nodes: folderB, folderC and fileInP.");
SearchRequest query = req("ANCESTOR:" + folders.get(A).getNodeRef());
searchQueryService.expectResultsFromQuery(query, testUser,
// primary descendants
folders.get(B).getName(),
folders.get(C).getName(),
// secondary descendant
fileInP.getName());
}
@Test(groups = TestGroup.SEARCH)
public void testSecondaryAncestorWithNodeHavingComplexSecondaryRelationship()
{
// then
STEP("Verify that all descendant of folderX can be found.");
SearchRequest query = req("ANCESTOR:" + folders.get(X).getNodeRef());
searchQueryService.expectResultsFromQuery(query, testUser,
// primary descendants
folders.get(Y).getName(),
folders.get(Z).getName(),
// secondary descendants
folders.get(B).getName(),
folders.get(C).getName(),
folders.get(K).getName(),
folders.get(L).getName(),
folders.get(M).getName());
}
/**
* Verify that removing secondary parent-child relationship will result in updating ES index: ANCESTOR. Test changes below folders hierarchy:
*
* <pre>
* DL
* += fQ
* +
* |
* += fR
* </pre>
*
* into:
*
* <pre>
* DL += fQ += fR
* </pre>
*/
@Test(groups = TestGroup.SEARCH)
public void testSecondaryAncestorWithDeletedSecondaryRelationship()
{
// given
STEP("Add to folderQ a secondary child folderR and verify if it can be found using ANCESTOR index and secondary child node reference.");
folders.modify(Q).add().secondaryContent(folders.get(R));
STEP("Verify that searching by ANCESTOR and folderQ will find secondary descendant node: folderR.");
SearchRequest query = req("ANCESTOR:" + folders.get(Q).getNodeRef());
searchQueryService.expectResultsFromQuery(query, testUser,
// secondary descendant
folders.get(R).getName());
// when
STEP("Delete the secondary parent-child relationship between folderQ and FolderR.");
folders.modify(Q).remove().secondaryContent(folders.get(R));
// then
STEP("Verify that folderQ cannot be found by ANCESTOR and folderQ anymore.");
searchQueryService.expectNoResultsFromQuery(query, testUser);
}
/**
* Verify that removing a node D (fD) having a secondary children relationship will remove the relationships and update ANCESTOR index in ES. Test changes below folders hierarchy from:
*
* <pre>
* DL
* += fQ
* +
* |
* += fE += fF
* +
* |
* += fR
* </pre>
*
* into:
*
* <pre>
* DL += fQ += fR
* </pre>
*/
@Test(groups = TestGroup.SEARCH)
public void testSecondaryAncestorWithDeletedSecondaryParentNode()
{
// given
STEP("Create two nested folders (E and F) in Document Library.");
FolderModel folderE = folders.add().randomFolder("E").create();
FolderModel folderF = folders.modify(folderE).add().randomFolder("F").create();
STEP("Make folderE a secondary children of folderQ and folderR a secondary children of folderE.");
folders.modify(Q).add().secondaryContent(folderE);
folders.modify(folderE).add().secondaryContent(folders.get(R));
STEP("Verify that searching by ANCESTOR and folderQ will find its secondary descendant: folderE, folderF and folderR.");
SearchRequest queryAncestorQ = req("ANCESTOR:" + folders.get(Q).getNodeRef());
searchQueryService.expectResultsFromQuery(queryAncestorQ, testUser,
// secondary descendants
folderE.getName(),
folderF.getName(),
folders.get(R).getName());
// when
STEP("Delete folderE with its content.");
folders.modify(folderE).delete();
// then
STEP("Verify that searching by ANCESTOR and folderQ will not find any nodes.");
searchQueryService.expectNoResultsFromQuery(queryAncestorQ, testUser);
}
/**
* Verify that moving folderD (fD) containing secondary children from hierarchy:
*
* <pre>
* DL
* += fQ += fD +- fP += file
* += fR
* </pre>
*
* to:
*
* <pre>
* DL
* += fQ
* += fR += fD +- fP += file
* </pre>
*
* will update ANCESTOR index in ES.
*/
@Test(groups = TestGroup.SEARCH)
public void testSecondaryAncestorWithMovedSecondaryParentNode()
{
// given
STEP("Create folderD inside folderQ, and add folderP to D as a secondary child.");
FolderModel folderD = folders.modify(Q).add().randomFolder("D").create();
folders.modify(folderD).add().secondaryContent(folders.get(P));
STEP("Verify that searching by ANCESTOR and folderQ will find its primary and secondary descendant nodes: folderD, folderP and file.");
SearchRequest queryAncestorQ = req("ANCESTOR:" + folders.get(Q).getNodeRef());
searchQueryService.expectResultsFromQuery(queryAncestorQ, testUser,
// primary descendant
folderD.getName(),
// secondary descendants
folders.get(P).getName(),
fileInP.getName());
STEP("Verify that searching by ANCESTOR and folderR will not find any descendant nodes.");
SearchRequest queryAncestorR = req("ANCESTOR:" + folders.get(R).getNodeRef());
searchQueryService.expectNoResultsFromQuery(queryAncestorR, testUser);
// when
STEP("Move folderD from folderQ to folderR.");
folders.modify(folderD).moveTo(folders.get(R));
// then
STEP("Verify that search result for ANCESTOR and folderQ will not find any descendant anymore.");
searchQueryService.expectNoResultsFromQuery(queryAncestorQ, testUser);
STEP("Verify that searching by ANCESTOR and folderR will find its primary and secondary descendant nodes: folderD, folderP and file.");
searchQueryService.expectResultsFromQuery(queryAncestorR, testUser,
// primary descendant
folderD.getName(),
// secondary descendants
folders.get(P).getName(),
fileInP.getName());
STEP("Clean-up - delete folderD.");
folders.modify(folderD).delete();
}
/**
* Verify that copying folder will also result in copying folder's secondary children and update ANCESTOR index in ES. Test changes below folders hierarchy:
*
* <pre>
* DL
* += fS += fG += fH
* +
* /
* += fP += file
* += fT
* </pre>
*
* into:
*
* <pre>
* DL
* += fS += fG += fH
* +
* /
* += fP += file
* \
* +
* += fT += fG-c += fH-c
* </pre>
*/
@Test(groups = TestGroup.SEARCH)
public void testSecondaryAncestorWithCopiedSecondaryParentNode() throws InterruptedException
{
// given
STEP("Create nested folders (G and H) inside folderS and folderT in Document Library. Make folderP a secondary child of folderG.");
FolderModel folderG = folders.modify(S).add().randomFolder("G").create();
FolderModel folderH = folders.modify(folderG).add().randomFolder("H").create();
FolderModel folderT = folders.add().randomFolder("T").create();
folders.modify(folderG).add().secondaryContent(folders.get(P));
STEP("Verify that searching by ANCESTOR and folderS will find its descendant nodes: folderG, folderH, folderP and file in P.");
SearchRequest queryAncestorS = req("ANCESTOR:" + folders.get(S).getNodeRef());
Utility.sleep(500, 60000, () -> searchQueryService.expectResultsFromQuery(queryAncestorS, testUser,
// primary descendants
folderG.getName(),
folderH.getName(),
// secondary descendants
folders.get(P).getName(),
fileInP.getName()));
STEP("Verify that searching by ANCESTOR and folderT will not find any nodes.");
SearchRequest queryAncestorT = req("ANCESTOR:" + folderT.getNodeRef());
searchQueryService.expectNoResultsFromQuery(queryAncestorT, testUser);
// when
STEP("Copy folderG with its content to folderT.");
FolderModel folderGCopy = folders.modify(folderG).copyTo(folderT);
// then
STEP("Verify that searching by ANCESTOR and folderS will find its descendant nodes: folderG, folderH, folderP and file in P.");
searchQueryService.expectResultsFromQuery(queryAncestorS, testUser,
// primary descendants
folderG.getName(),
folderH.getName(),
// secondary descendants
folders.get(P).getName(),
fileInP.getName());
STEP("Verify that searching by ANCESTOR and folderT will find its descendant nodes: folderG-copy, folderH-copy, folderP, file.");
searchQueryService.expectResultsFromQuery(queryAncestorT, testUser,
// primary descendants
folderGCopy.getName(),
folderH.getName(), // the same name as folderH-copy
// secondary descendants
folders.get(P).getName(),
fileInP.getName());
STEP("Clean-up - delete folderG and folderT (with G's copy).");
folders.modify(folderG).delete();
folders.modify(folderT).delete();
}
}
@@ -1,90 +0,0 @@
/*
* #%L
* Alfresco Tas Elasticsearch
* %%
* Copyright (C) 2026 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.elasticsearch;
import static org.alfresco.utility.report.log.Step.STEP;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.alfresco.rest.core.RestWrapper;
import org.alfresco.rest.repo.resource.Folders;
import org.alfresco.tas.AlfrescoStackInitializer;
import org.alfresco.utility.data.DataContent;
import org.alfresco.utility.data.DataSite;
import org.alfresco.utility.data.DataUser;
import org.alfresco.utility.model.SiteModel;
import org.alfresco.utility.model.UserModel;
import org.alfresco.utility.network.ServerHealth;
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
initializers = AlfrescoStackInitializer.class)
public abstract class NodesSecondaryChildrenRelatedTests extends AbstractTestNGSpringContextTests
{
@SuppressWarnings("PMD.OneDeclarationPerLine")
protected static final String A = "A", B = "B", C = "C", K = "K", L = "L", M = "M", P = "P", Q = "Q", R = "R", S = "S", X = "X", Y = "Y", Z = "Z";
@Autowired
private ServerHealth serverHealth;
@Autowired
private DataUser dataUser;
@Autowired
private DataSite dataSite;
@Autowired
protected DataContent dataContent;
@Autowired
protected RestWrapper restClient;
@Autowired
protected SearchQueryService searchQueryService;
protected UserModel testUser;
protected SiteModel testSite;
protected Folders folders;
@BeforeClass(alwaysRun = true)
public void dataPreparation()
{
STEP("Verify environment health.");
serverHealth.isServerReachable();
serverHealth.assertServerIsOnline();
STEP("Create a test user and private site.");
testUser = dataUser.createRandomTestUser();
testSite = dataSite.usingUser(testUser).createPrivateRandomSite();
folders = new Folders(dataContent, restClient, testUser, testSite);
}
@AfterClass(alwaysRun = true)
public void dataCleanUp()
{
STEP("Clean up data after tests.");
dataSite.usingUser(testUser).deleteSite(testSite);
dataUser.usingAdmin().deleteUser(testUser);
}
}
@@ -1,361 +0,0 @@
/*
* #%L
* Alfresco Tas Elasticsearch
* %%
* Copyright (C) 2026 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.elasticsearch;
import static org.alfresco.elasticsearch.SearchQueryService.req;
import static org.alfresco.utility.report.log.Step.STEP;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.alfresco.rest.search.SearchRequest;
import org.alfresco.utility.model.FileModel;
import org.alfresco.utility.model.FolderModel;
import org.alfresco.utility.model.TestGroup;
/**
* Tests verifying indexing of secondary children and PARENT index in Elasticsearch.
*/
@SuppressWarnings({"PMD.JUnitTestsShouldIncludeAssert"}) // these are TAS E2E tests and use searchQueryService.expectResultsFromQuery for assertion
public class NodesSecondaryParentIndexingTests extends NodesSecondaryChildrenRelatedTests
{
private FileModel fileInP;
/**
* Creates a user and a private site containing below hierarchy of folders.
*
* <pre>
* Site
* DL (Document Library)
* += fA += fB += fC (folderC)
* / / |
* + + +
* += fK += fL += fM
* | +
* + |
* += fX += fY += fZ
* += fP += file -+ fA
* += fQ
* += fR
* += fS
* </pre>
*
* Parent += Child - primary parent-child relationship Parent +- Child - secondary parent-child relationship
*/
@BeforeClass(alwaysRun = true)
@Override
public void dataPreparation()
{
super.dataPreparation();
// given
STEP("Create few sets of nested folders in site's Document Library.");
folders.add().nestedRandomFolders(A, B, C).create();
folders.add().nestedRandomFolders(K, L, M).create();
folders.add().nestedRandomFolders(X, Y, Z).create();
folders.add().randomFolders(P, Q, R, S).create();
fileInP = folders.modify(P).add().randomFile().create();
STEP("Create few secondary parent-child relationships.");
folders.modify(K).add().secondaryContent(folders.get(B));
folders.modify(X).add().secondaryContent(folders.get(K));
folders.modify(L).add().secondaryContent(folders.get(C), folders.get(Y));
folders.modify(M).add().secondaryContent(folders.get(C));
folders.modify(A).add().secondaryContent(fileInP);
}
@Test(groups = TestGroup.SEARCH)
public void testSecondaryParentWithNodeHavingOneSecondaryChild()
{
// then
STEP("Verify that searching by PARENT and folderM will find node folderC.");
SearchRequest query = req("PARENT:" + folders.get(M).getNodeRef());
searchQueryService.expectResultsFromQuery(query, testUser,
folders.get(C).getName());
}
@Test(groups = TestGroup.SEARCH)
public void testSecondaryParentWithNodeHavingTwoSecondaryChildren()
{
// then
STEP("Verify that searching by PARENT and folderL will find nodes: folderM, FolderC and folderY.");
SearchRequest query = req("PARENT:" + folders.get(L).getNodeRef());
searchQueryService.expectResultsFromQuery(query, testUser,
// primary child
folders.get(M).getName(),
// secondary children
folders.get(C).getName(),
folders.get(Y).getName());
}
@Test(groups = TestGroup.SEARCH)
public void testSecondaryParentWithDocumentAsSecondaryChild()
{
// then
STEP("Verify that searching by PARENT and folderA will find nodes: folderB and file.");
SearchRequest query = req("PARENT:" + folders.get(A).getNodeRef());
searchQueryService.expectResultsFromQuery(query, testUser,
// primary child
folders.get(B).getName(),
// secondary child
fileInP.getName());
}
/**
* Verify that removing secondary parent-child relationship will result in updating ES index: PARENT. Test changes below folders hierarchy:
*
* <pre>
* DL
* += fQ
* +
* |
* += fR
* </pre>
*
* into:
*
* <pre>
* DL += fQ += fR
* </pre>
*/
@Test(groups = TestGroup.SEARCH)
public void testSecondaryParentWithDeletedSecondaryRelationship()
{
// given
STEP("Add to folderQ a secondary child folderR and verify if it can be found using PARENT index and secondary parent node reference.");
folders.modify(Q).add().secondaryContent(folders.get(R));
SearchRequest query = req("PARENT:" + folders.get(Q).getNodeRef());
searchQueryService.expectResultsFromQuery(query, testUser,
// secondary child
folders.get(R).getName());
// when
STEP("Delete the secondary parent relationship between folderQ and FolderR and verify that folderR cannot be found by PARENT and folderQ anymore.");
folders.modify(Q).remove().secondaryContent(folders.get(R));
// then
searchQueryService.expectNoResultsFromQuery(query, testUser);
}
/**
* Verify that removing a node D (fD) having a secondary children relationship will remove the relationships and update PARENT index in ES. Test changes below folders hierarchy from:
*
* <pre>
* DL
* += fQ
* +
* |
* += fE += fF
* +
* |
* += fR
* </pre>
*
* into:
*
* <pre>
* DL += fQ += fR
* </pre>
*/
@Test(groups = TestGroup.SEARCH)
public void testSecondaryParentWithDeletedSecondaryParentNode()
{
// given
STEP("Create two nested folders (E and F) in Document Library.");
FolderModel folderE = folders.add().randomFolder("E").create();
FolderModel folderF = folders.modify(folderE).add().randomFolder("F").create();
STEP("Make folderE a secondary children of folderQ and folderR a secondary children of folderE.");
folders.modify(Q).add().secondaryContent(folderE);
folders.modify(folderE).add().secondaryContent(folders.get(R));
STEP("Verify that searching by PARENT and folderQ will find its secondary child: folderE.");
SearchRequest queryParentQ = req("PARENT:" + folders.get(Q).getNodeRef());
searchQueryService.expectResultsFromQuery(queryParentQ, testUser,
// secondary child
folderE.getName());
STEP("Verify that searching by PARENT and folderE will find its primary and secondary children: folderF and folderR.");
SearchRequest queryParentD = req("PARENT:" + folderE.getNodeRef());
searchQueryService.expectResultsFromQuery(queryParentD, testUser,
// primary child
folderF.getName(),
// secondary child
folders.get(R).getName());
// when
STEP("Delete folderE and verify that PARENT was updated for nodes folderQ and folderR.");
folders.modify(folderE).delete();
// then
searchQueryService.expectNoResultsFromQuery(queryParentQ, testUser);
searchQueryService.expectNoResultsFromQuery(queryParentD, testUser);
}
/**
* Verify that moving folder D (fD) containing secondary children from hierarchy:
*
* <pre>
* DL
* += fQ += fD +- fP += file
* += fR
* </pre>
*
* to:
*
* <pre>
* DL
* += fQ
* += fR += fD +- fP += file
* </pre>
*
* will update PARENT index in ES.
*/
@Test(groups = TestGroup.SEARCH)
public void testSecondaryParentWithMovedSecondaryParentNode()
{
// given
STEP("Create folderD inside folderQ, and add folderP to D as a secondary child.");
FolderModel folderD = folders.modify(Q).add().randomFolder("D").create();
folders.modify(folderD).add().secondaryContent(folders.get(P));
STEP("Verify that searching by PARENT and folderD will find node folderP.");
SearchRequest queryParentD = req("PARENT:" + folderD.getNodeRef());
searchQueryService.expectResultsFromQuery(queryParentD, testUser,
// secondary child
folders.get(P).getName());
STEP("Verify that searching by PARENT and folderQ will find node folderD.");
SearchRequest queryParentQ = req("PARENT:" + folders.get(Q).getNodeRef());
searchQueryService.expectResultsFromQuery(queryParentQ, testUser,
// primary child
folderD.getName());
// when
STEP("Move folderD from folderQ to folderR.");
folders.modify(folderD).moveTo(folders.get(R));
// then
STEP("Verify that search result for PARENT and folderD didn't change.");
searchQueryService.expectResultsFromQuery(queryParentD, testUser,
// secondary child
folders.get(P).getName());
STEP("Verify that searching by PARENT and folderQ doesn't return any node anymore.");
searchQueryService.expectNoResultsFromQuery(queryParentQ, testUser);
STEP("Verify that searching by PARENT and folderR will find node folderD.");
SearchRequest queryParentR = req("PARENT:" + folders.get(R).getNodeRef());
searchQueryService.expectResultsFromQuery(queryParentR, testUser,
// primary child
folderD.getName());
STEP("Clean-up - delete folderD.");
folders.modify(folderD).delete();
}
/**
* Verify that copying folder will also result in copying folder's secondary children and update PARENT index in ES. Test changes below folders hierarchy:
*
* <pre>
* DL
* += fS += fG += fH
* +
* /
* += fP += file
* += fT
* </pre>
*
* into:
*
* <pre>
* DL
* += fS += fG += fH
* +
* /
* += fP += file
* \
* +
* += fT += fG-c += fH-c
* </pre>
*/
@Test(groups = TestGroup.SEARCH)
public void testSecondaryParentWithCopiedSecondaryParentNode()
{
// given
STEP("Create nested folders (G and H) inside folderS and folderT in Document Library. Make folderP a secondary child of folderG.");
FolderModel folderG = folders.modify(S).add().randomFolder("G").create();
FolderModel folderH = folders.modify(folderG).add().randomFolder("H").create();
FolderModel folderT = folders.add().randomFolder("T").create();
folders.modify(folderG).add().secondaryContent(folders.get(P));
STEP("Verify that searching by PARENT and folderG will find nodes: folderH, folderP and file.");
SearchRequest queryParentG = req("PARENT:" + folderG.getNodeRef());
searchQueryService.expectResultsFromQuery(queryParentG, testUser,
// primary child
folderH.getName(),
// secondary child
folders.get(P).getName());
STEP("Verify that searching by PARENT and folderS will find nodes: folderG, folderH, folderP and file.");
SearchRequest queryParentS = req("PARENT:" + folders.get(S).getNodeRef());
searchQueryService.expectResultsFromQuery(queryParentS, testUser,
// primary child
folderG.getName());
STEP("Verify that searching by PARENT and folderP will find node: file.");
SearchRequest queryParentP = req("PARENT:" + folders.get(P).getNodeRef());
searchQueryService.expectResultsFromQuery(queryParentP, testUser,
// primary child
fileInP.getName());
// when
STEP("Copy folderG with its content to folderT.");
FolderModel folderGCopy = folders.modify(folderG).copyTo(folderT);
// then
STEP("Verify that search result for PARENT and folderS didn't change.");
searchQueryService.expectResultsFromQuery(queryParentS, testUser,
// primary child
folderG.getName());
STEP("Verify that searching by PARENT and folderS/folderG will find nodes: folderH, folderP and file in P.");
searchQueryService.expectResultsFromQuery(queryParentG, testUser,
// primary child
folderH.getName(),
// secondary child
folders.get(P).getName());
STEP("Verify that folderG was copied with secondary parent-child relationship and PARENT reflects that - search by folderT/folderGCopy should find nodes: folderH, folderP and file in P.");
SearchRequest queryParentGCopy = req("PARENT:" + folderGCopy.getNodeRef());
searchQueryService.expectResultsFromQuery(queryParentGCopy, testUser,
// primary child
folderH.getName(), // name is the same as folderH-copy
// secondary child
folders.get(P).getName());
STEP("Verify that this time searching by PARENT and folderP will find node: file.");
searchQueryService.expectResultsFromQuery(queryParentP, testUser,
// primary child
fileInP.getName());
STEP("Clean-up - delete folderG and folderT (with G's copy).");
folders.modify(folderG).delete();
folders.modify(folderT).delete();
}
}
@@ -1,392 +0,0 @@
/*
* #%L
* Alfresco Tas Elasticsearch
* %%
* Copyright (C) 2026 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.elasticsearch;
import static org.alfresco.elasticsearch.SearchQueryService.req;
import static org.alfresco.utility.report.log.Step.STEP;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.alfresco.rest.search.SearchRequest;
import org.alfresco.utility.model.FileModel;
import org.alfresco.utility.model.FolderModel;
import org.alfresco.utility.model.TestGroup;
/**
* Tests verifying indexing of secondary children and PATH index in Elasticsearch.
*/
@SuppressWarnings({"PMD.JUnitTestsShouldIncludeAssert"}) // these are TAS E2E tests and use searchQueryService.expectResultsFromQuery for assertion
public class NodesSecondaryPathIndexingTests extends NodesSecondaryChildrenRelatedTests
{
private FileModel fileInP;
/**
* Creates a user and a private site containing below hierarchy of folders.
*
* <pre>
* Site
* DL (Document Library)
* += fA += fB += fC (folderC)
* / / |
* + + +
* += fK += fL += fM
* | +
* + |
* += fX += fY += fZ
* += fP += file -+ fA
* += fQ
* += fR
* += fS
* </pre>
*
* Parent += Child - primary parent-child relationship Parent +- Child - secondary parent-child relationship
*/
@BeforeClass(alwaysRun = true)
@Override
public void dataPreparation()
{
super.dataPreparation();
// given
STEP("Create few sets of nested folders in site's Document Library.");
folders.add().nestedRandomFolders(A, B, C).create();
folders.add().nestedRandomFolders(K, L, M).create();
folders.add().nestedRandomFolders(X, Y, Z).create();
folders.add().randomFolders(P, Q, R, S).create();
fileInP = folders.modify(P).add().randomFile().create();
STEP("Create few secondary parent-child relationships.");
folders.modify(K).add().secondaryContent(folders.get(B));
folders.modify(X).add().secondaryContent(folders.get(K));
folders.modify(L).add().secondaryContent(folders.get(C), folders.get(Y));
folders.modify(M).add().secondaryContent(folders.get(C));
folders.modify(A).add().secondaryContent(fileInP);
STEP("Add to folderQ a secondary child folderR.");
folders.modify(Q).add().secondaryContent(folders.get(R));
// when
STEP("Delete the secondary parent relationship between folderQ and FolderR.");
folders.modify(Q).remove().secondaryContent(folders.get(R));
}
@Test(groups = TestGroup.SEARCH)
public void testSecondaryPathWithNodeHavingOneSecondaryChild()
{
// then
STEP("Verify that folderC can be found by secondary PATH using secondary parent folderM.");
SearchRequest query = req("PATH:\"//cm:" + folders.get(M).getName() + "//*\"");
searchQueryService.expectResultsFromQuery(query, testUser,
// secondary path
folders.get(C).getName());
}
@Test(groups = TestGroup.SEARCH)
public void testSecondaryPathWithNodeHavingOnePrimaryAndTwoSecondaryChildren()
{
// then
STEP("Verify that primary and secondary children of folderL can be found using PATH index.");
SearchRequest query = req("PATH:\"//cm:" + folders.get(L).getName() + "//*\"");
searchQueryService.expectResultsFromQuery(query, testUser,
// primary path
folders.get(M).getName(),
// secondary path
folders.get(C).getName(),
folders.get(Y).getName(),
folders.get(Z).getName());
}
@Test(groups = TestGroup.SEARCH)
public void testSecondaryPathWithNodeHavingDocumentAsSecondaryChild()
{
// then
STEP("Verify that a file being a secondary child of folderA can be found using PATH index.");
SearchRequest query = req("PATH:\"//cm:" + folders.get(A).getName() + "//*\"");
searchQueryService.expectResultsFromQuery(query, testUser,
// primary path
folders.get(B).getName(),
folders.get(C).getName(),
// secondary path
fileInP.getName());
}
@Test(groups = TestGroup.SEARCH)
public void testSecondaryPathWithNodeHavingComplexSecondaryRelationship()
{
// then
STEP("Verify that all secondary children of folderX can be found.");
SearchRequest query = req("PATH:\"//cm:" + folders.get(X).getName() + "//*\"");
searchQueryService.expectResultsFromQuery(query, testUser,
// primary path
folders.get(Y).getName(),
folders.get(Z).getName(),
// secondary path
folders.get(B).getName(),
folders.get(C).getName(),
folders.get(K).getName(),
folders.get(L).getName(),
folders.get(M).getName());
}
/**
* Verify that removing secondary parent-child relationship will result in updating ES index: PATH. Test changes below folders hierarchy:
*
* <pre>
* DL
* += fQ
* +
* |
* += fR
* </pre>
*
* into:
*
* <pre>
* DL += fQ += fR
* </pre>
*/
@Test(groups = TestGroup.SEARCH)
public void testSecondaryPathWithDeletedSecondaryRelationship()
{
// then
STEP("Verify that folderR cannot be found by PATH and folderQ anymore.");
SearchRequest query = req("PATH:\"//cm:" + folders.get(Q).getName() + "//*\"");
searchQueryService.expectNoResultsFromQuery(query, testUser);
}
/**
* Verify that removing a node D (fD) having a secondary children relationship will remove the relationships and update PATH index in ES. Test changes below folders hierarchy from:
*
* <pre>
* DL
* += fQ
* +
* |
* += fE += fF
* +
* |
* += fR
* </pre>
*
* into:
*
* <pre>
* DL += fQ += fR
* </pre>
*/
@Test(groups = TestGroup.SEARCH)
public void testSecondaryPathWithDeletedSecondaryParentNode()
{
// given
STEP("Create two nested folders (E and F) in Document Library.");
FolderModel folderE = folders.add().randomFolder("E").create();
FolderModel folderF = folders.modify(folderE).add().randomFolder("F").create();
STEP("Make folderE a secondary children of folderQ and folderR a secondary children of folderE.");
folders.modify(Q).add().secondaryContent(folderE);
folders.modify(folderE).add().secondaryContent(folders.get(R));
STEP("Verify that searching by PATH and folderQ will find nodes: folderE, folderF and folderR.");
SearchRequest queryPathQ = req("PATH:\"//cm:" + folders.get(Q).getName() + "//*\"");
searchQueryService.expectResultsFromQuery(queryPathQ, testUser,
// secondary path
folderE.getName(),
folderF.getName(),
folders.get(R).getName());
STEP("Verify that searching by PATH and folderE will find its primary and secondary children: folderF and folderR.");
SearchRequest queryPathD = req("PATH:\"//cm:" + folderE.getName() + "//*\"");
searchQueryService.expectResultsFromQuery(queryPathD, testUser,
// primary path
folderF.getName(),
// secondary path
folders.get(R).getName());
// when
STEP("Delete folderE and verify that PATH was updated for nodes folderQ and folderR.");
folders.modify(folderE).delete();
// then
searchQueryService.expectNoResultsFromQuery(queryPathQ, testUser);
searchQueryService.expectNoResultsFromQuery(queryPathD, testUser);
}
/**
* Verify that moving folder D (fD) containing secondary children from hierarchy:
*
* <pre>
* DL
* += fQ += fD +- fP += file
* += fR
* </pre>
*
* to:
*
* <pre>
* DL
* += fQ
* += fR += fD +- fP += file
* </pre>
*
* will update PATH index in ES.
*/
@Test(groups = TestGroup.SEARCH)
public void testSecondaryPathWithMovedSecondaryParentNode()
{
// given
STEP("Create folderD inside folderQ, and add folderP as a secondary child.");
FolderModel folderD = folders.modify(Q).add().randomFolder("D").create();
folders.modify(folderD).add().secondaryContent(folders.get(P));
folders.modify(M).add().secondaryContent(folderD);
STEP("Verify that searching by PATH and folderD will find nodes: folderP and file.");
SearchRequest queryPathD = req("PATH:\"//cm:" + folderD.getName() + "//*\"");
searchQueryService.expectResultsFromQuery(queryPathD, testUser,
// secondary path
folders.get(P).getName(),
fileInP.getName());
STEP("Verify that searching by PATH and folderQ will find nodes: folderD, folderP and file.");
SearchRequest queryPathQ = req("PATH:\"//cm:" + folders.get(Q).getName() + "//*\"");
searchQueryService.expectResultsFromQuery(queryPathQ, testUser,
// primary path
folderD.getName(),
// secondary path
folders.get(P).getName(),
fileInP.getName());
// when
STEP("Move folderD from folderQ to folderR.");
folders.modify(folderD).moveTo(folders.get(R));
// then
STEP("Verify that search result for PATH and folderD didn't change.");
searchQueryService.expectResultsFromQuery(queryPathD, testUser,
// secondary path
folders.get(P).getName(),
fileInP.getName());
STEP("Verify that searching by PATH and folderQ doesn't return any node anymore.");
searchQueryService.expectNoResultsFromQuery(queryPathQ, testUser);
STEP("Verify that searching by PATH and folderR will find nodes: folderD, folderP and file.");
SearchRequest queryPathR = req("PATH:\"//cm:" + folders.get(R).getName() + "//*\"");
searchQueryService.expectResultsFromQuery(queryPathR, testUser,
// primary path
folderD.getName(),
// secondary path
folders.get(P).getName(),
fileInP.getName());
STEP("Clean-up - delete folderD.");
folders.modify(folderD).delete();
}
/**
* Verify that copying folder will also result in copying folder's secondary children and update PATH index in ES. Test changes below folders hierarchy:
*
* <pre>
* DL
* += fS += fG += fH
* +
* /
* += fP += file
* += fT
* </pre>
*
* into:
*
* <pre>
* DL
* += fS += fG += fH
* +
* /
* += fP += file
* \
* +
* += fT += fG-c += fH-c
* </pre>
*/
@Test(groups = TestGroup.SEARCH)
public void testSecondaryParentWithCopiedSecondaryParentNode()
{
// given
STEP("Create nested folders (G and H) inside folderS and folderT in Document Library. Make folderP a secondary child of folderG.");
FolderModel folderG = folders.modify(S).add().randomFolder("G").create();
FolderModel folderH = folders.modify(folderG).add().randomFolder("H").create();
FolderModel folderT = folders.add().randomFolder("T").create();
folders.modify(folderG).add().secondaryContent(folders.get(P));
STEP("Verify that searching by PATH and folderG will find nodes: folderH, folderP and file.");
SearchRequest queryPathG = req("PATH:\"//cm:" + folderG.getName() + "//*\"");
searchQueryService.expectResultsFromQuery(queryPathG, testUser,
// primary path
folderH.getName(),
// secondary path
folders.get(P).getName(),
fileInP.getName());
STEP("Verify that searching by PATH and folderS will find nodes: folderG, folderH, folderP and file.");
SearchRequest queryPathS = req("PATH:\"//cm:" + folders.get(S).getName() + "//*\"");
searchQueryService.expectResultsFromQuery(queryPathS, testUser,
// primary path
folderG.getName(),
folderH.getName(),
// secondary path
folders.get(P).getName(),
fileInP.getName());
// when
STEP("Copy folderG with its content to folderT.");
FolderModel folderGCopy = folders.modify(folderG).copyTo(folderT);
// then
STEP("Verify that search result for PATH and folderS didn't change.");
searchQueryService.expectResultsFromQuery(queryPathS, testUser,
// primary path
folderH.getName(),
folderG.getName(),
// secondary path
folders.get(P).getName(),
fileInP.getName());
STEP("Verify that searching by PATH and folderS/folderG will find nodes: folderH, folderP and file in P.");
SearchRequest queryPathSG = req("PATH:\"//cm:" + folders.get(S).getName() + "/cm:" + folderG.getName() + "//*\"");
searchQueryService.expectResultsFromQuery(queryPathSG, testUser,
// primary path
folderH.getName(),
// secondary path
folders.get(P).getName(),
fileInP.getName());
STEP("Verify that folderG was copied with secondary parent-child relationship and PATH reflects that - search by folderT/folderGCopy should find nodes: folderH, folderP and file in P.");
SearchRequest queryPathTGCopy = req("PATH:\"//cm:" + folderT.getName() + "/cm:" + folderGCopy.getName() + "//*\"");
searchQueryService.expectResultsFromQuery(queryPathTGCopy, testUser,
// primary path
folderH.getName(), // the same name as folderH-copy
// secondary path
folders.get(P).getName(),
fileInP.getName());
STEP("Clean-up - delete folderG and folderT (with G's copy).");
folders.modify(folderG).delete();
folders.modify(folderT).delete();
}
}
@@ -1,174 +0,0 @@
/*
* #%L
* Alfresco Tas Elasticsearch
* %%
* Copyright (C) 2026 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.elasticsearch;
import static org.alfresco.elasticsearch.SearchQueryService.req;
import static org.alfresco.utility.report.log.Step.STEP;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.alfresco.rest.search.SearchRequest;
import org.alfresco.utility.Utility;
import org.alfresco.utility.model.TestGroup;
@SuppressWarnings({"PMD.JUnitTestsShouldIncludeAssert"}) // these are TAS E2E tests and use searchQueryService.expectResultsFromQuery for assertion
public class PathFieldsIndexingTests extends NodesSecondaryChildrenRelatedTests
{
/**
* Creates a user and a private site containing below hierarchy of folders.
*
* <pre>
* Site
* DL (Document Library)
* += fA += fB += fC (folderC)
* / / |
* + + +
* += fK += fL += fM
* | +
* + |
* += fX += fY += fZ
* </pre>
*
* Parent += Child - primary parent-child relationship Parent +- Child - secondary parent-child relationship
*/
@BeforeClass(alwaysRun = true)
@Override
public void dataPreparation()
{
super.dataPreparation();
// given
STEP("Create some nested folders.");
folders.add().nestedRandomFolders(A, B, C).create();
folders.add().nestedRandomFolders(K, L, M).create();
folders.add().nestedRandomFolders(X, Y, Z).create();
STEP("Add some extra secondary parent relationships.");
folders.modify(K).add().secondaryContent(folders.get(B));
folders.modify(X).add().secondaryContent(folders.get(K));
folders.modify(L).add().secondaryContent(folders.get(C), folders.get(Y));
folders.modify(M).add().secondaryContent(folders.get(C));
STEP("Wait for the batch indexer to index the last secondary association (M +- C).");
SearchRequest indexingProbe = req("PARENT:" + folders.get(M).getNodeRef());
try
{
Utility.sleep(500, 60000, () -> searchQueryService.expectResultsFromQuery(
indexingProbe, testUser, folders.get(C).getName()));
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
throw new AssertionError("Interrupted while waiting for batch indexer", e);
}
}
@Test(groups = TestGroup.SEARCH)
public void testPrimaryParentField()
{
STEP("Check that only M has L as a primary parent.");
SearchRequest query = req("PRIMARYPARENT:" + folders.get(L).getNodeRef());
searchQueryService.expectResultsFromQuery(query, testUser,
folders.get(M).getName());
}
@Test(groups = TestGroup.SEARCH)
public void testParentFieldIncludesSecondaryParents()
{
STEP("Check that three nodes have L as a primary or secondary parent.");
SearchRequest query = req("PARENT:" + folders.get(L).getNodeRef());
searchQueryService.expectResultsFromQuery(query, testUser,
folders.get(C).getName(), folders.get(M).getName(), folders.get(Y).getName());
}
@Test(groups = TestGroup.SEARCH)
public void testAncestorFieldIncludesSecondaryAssociations()
{
STEP("Check which nodes have K as an ancestor.");
SearchRequest query = req("ANCESTOR:" + folders.get(K).getNodeRef());
searchQueryService.expectResultsFromQuery(query, testUser,
folders.get(B).getName(), folders.get(C).getName(), folders.get(L).getName(), folders.get(M).getName(),
folders.get(Y).getName(), folders.get(Z).getName());
}
@Test(groups = TestGroup.SEARCH)
public void testSecondaryPathWithNodeHavingOneSecondaryChild()
{
// then
STEP("Verify that folderC can be found by secondary PATH using secondary parent folderM.");
SearchRequest query = req("PATH:\"//cm:" + folders.get(M).getName() + "//*\"");
searchQueryService.expectResultsFromQuery(query, testUser,
// secondary path
folders.get(C).getName());
}
@Test(groups = TestGroup.SEARCH)
public void testSecondaryPathWithNodeHavingOnePrimaryAndTwoSecondaryChildren()
{
// then
STEP("Verify that primary and secondary children of folderL can be found using PATH index.");
SearchRequest query = req("PATH:\"//cm:" + folders.get(L).getName() + "//*\"");
searchQueryService.expectResultsFromQuery(query, testUser,
// primary path
folders.get(M).getName(),
// secondary path
folders.get(C).getName(),
folders.get(Y).getName(),
folders.get(Z).getName());
}
@Test(groups = TestGroup.SEARCH)
public void testQueryThroughSecondaryPath()
{
// then
STEP("Verify we can get direct children of a secondary paths (X-K=L) referenced in a query.");
SearchRequest query = req("PATH:\"//cm:" + folders.get(X).getName() + "//cm:" + folders.get(L).getName() + "/*\"");
searchQueryService.expectResultsFromQuery(query, testUser,
// primary path from L
folders.get(M).getName(),
// secondary path from L
folders.get(C).getName(),
folders.get(Y).getName());
}
@Test(groups = TestGroup.SEARCH)
public void testSecondaryPathWithNodeHavingComplexSecondaryRelationship()
{
// then
STEP("Verify we can find all secondary descendents (excluding direct children) of folderX.");
SearchRequest query = req("PATH:\"//cm:" + folders.get(X).getName() + "//*//*\"");
searchQueryService.expectResultsFromQuery(query, testUser,
// primary path
folders.get(Z).getName(),
// secondary path
folders.get(Y).getName(), // Y _is_ a direct primary child, but this query should only find it via the secondary path.
folders.get(B).getName(),
folders.get(C).getName(),
folders.get(L).getName(),
folders.get(M).getName());
}
}
@@ -1,271 +0,0 @@
/*
* #%L
* Alfresco Tas Elasticsearch
* %%
* Copyright (C) 2026 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.elasticsearch;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.joining;
import static org.alfresco.elasticsearch.SearchQueryService.req;
import java.util.Arrays;
import jakarta.json.Json;
import jakarta.json.JsonObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.alfresco.elasticsearch.utility.ElasticsearchRESTHelper;
import org.alfresco.rest.core.RestWrapper;
import org.alfresco.rest.model.RestCategoryModel;
import org.alfresco.rest.model.RestNodeBodyMoveCopyModel;
import org.alfresco.rest.model.RestNodeModel;
import org.alfresco.rest.search.SearchRequest;
import org.alfresco.tas.AlfrescoStackInitializer;
import org.alfresco.utility.data.DataUser;
import org.alfresco.utility.model.ContentModel;
import org.alfresco.utility.model.FileModel;
import org.alfresco.utility.model.FolderModel;
import org.alfresco.utility.model.SiteModel;
import org.alfresco.utility.model.UserModel;
import org.alfresco.utility.network.ServerHealth;
import org.alfresco.utility.report.log.Step;
/**
* Tests to check that paths are updated correctly.
*/
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
initializers = AlfrescoStackInitializer.class)
public class PathUpdateTests extends AbstractTestNGSpringContextTests
{
@Autowired
private ServerHealth serverHealth;
@Autowired
private DataUser dataUser;
@Autowired
protected RestWrapper restClient;
@Autowired
private ElasticsearchRESTHelper helper;
@Autowired
protected SearchQueryService searchQueryService;
private UserModel testUser;
private SiteModel testSite;
/**
* Create a user and a private site containing some nested folders with a document in.
*/
@BeforeClass(alwaysRun = true)
public void dataPreparation()
{
serverHealth.isServerReachable();
serverHealth.assertServerIsOnline();
Step.STEP("Create a test user and private site.");
testUser = dataUser.createRandomTestUser();
testSite = helper.createPrivateSite(testUser);
}
@Test
public void testChangeFileNameUpdatesPath()
{
Step.STEP("Create a file in the site.");
FileModel testFile = helper.createFileInSite(testUser, testSite);
Step.STEP("Update the filename.");
RestNodeModel updatedFile = renameNode(testFile);
Step.STEP("Check the path is updated");
SearchRequest query = req("PATH:\"" + pathInSite(testSite, updatedFile.getName()) + "\"");
searchQueryService.expectResultsFromQuery(query, testUser, updatedFile.getName());
}
@Test
public void testChangeFileParentUpdatesPath()
{
Step.STEP("Create a file next to a folder.");
FileModel testFile = helper.createFileInSite(testUser, testSite);
FolderModel testFolder = helper.createFolderInSite(testUser, testSite);
Step.STEP("Move the file into the folder and check the path is updated.");
moveNode(testFile, testFolder);
Step.STEP("Check the path is updated");
SearchRequest query = req("PATH:\"" + pathInSite(testSite, testFolder.getName(), testFile.getName()) + "\"");
searchQueryService.expectResultsFromQuery(query, testUser, testFile.getName());
}
@Test
public void testChangeFolderNameUpdatesPath()
{
Step.STEP("Create a file in a folder.");
FolderModel testFolder = helper.createFolderInSite(testUser, testSite);
FileModel testFile = helper.createFileInFolder(testUser, testFolder);
Step.STEP("Update the folder's name.");
RestNodeModel updatedFolder = renameNode(testFolder);
Step.STEP("Check the path is updated");
SearchRequest query = req("PATH:\"" + pathInSite(testSite, updatedFolder.getName(), testFile.getName()) + "\"");
searchQueryService.expectResultsFromQuery(query, testUser, testFile.getName());
}
@Test
public void testChangeFolderParentUpdatesPath()
{
Step.STEP("Create two folders with a file in the first.");
FolderModel firstFolder = helper.createFolderInSite(testUser, testSite);
FolderModel secondFolder = helper.createFolderInSite(testUser, testSite);
FileModel testFile = helper.createFileInFolder(testUser, firstFolder);
Step.STEP("Move the first folder into the second.");
moveNode(firstFolder, secondFolder);
Step.STEP("Check the path is updated");
SearchRequest query = req("PATH:\"" + pathInSite(testSite, secondFolder.getName(), firstFolder.getName(), testFile.getName()) + "\"");
searchQueryService.expectResultsFromQuery(query, testUser, testFile.getName());
}
@Test
public void testChangeCategoriesUpdatesPath()
{
Step.STEP("Create two categories and a file in the first.");
RestCategoryModel firstCategory = helper.createCategory();
RestCategoryModel secondCategory = helper.createCategory();
FileModel testFile = helper.createFileInSite(testUser, testSite);
helper.linkToCategory(testUser, testFile, firstCategory);
Step.STEP("Remove the file from the first category and add it to the second.");
helper.unlinkFromCategory(testUser, testFile, firstCategory);
helper.linkToCategory(testUser, testFile, secondCategory);
Step.STEP("Check there is no path for the first category.");
SearchRequest query = req("PATH:\"" + categoryPath(firstCategory.getName(), testFile.getName()) + "\"");
searchQueryService.expectNoResultsFromQuery(query, testUser);
Step.STEP("Check there is a path for the second category.");
query = req("PATH:\"" + categoryPath(secondCategory.getName(), testFile.getName()) + "\"");
searchQueryService.expectResultsFromQuery(query, testUser, testFile.getName());
}
@Test
public void testChangeCategoryNameUpdatesPath()
{
Step.STEP("Create a category and a file in it.");
RestCategoryModel category = helper.createCategory();
FileModel testFile = helper.createFileInSite(testUser, testSite);
helper.linkToCategory(testUser, testFile, category);
Step.STEP("Update the name of the category.");
String newCategoryName = category.getName() + "_updated";
restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI()
.usingCategory(category)
.updateCategory(RestCategoryModel.builder().name(newCategoryName).create());
Step.STEP("Check there is a path with the updated category name.");
SearchRequest query = req("PATH:\"" + categoryPath(newCategoryName, testFile.getName()) + "\"");
searchQueryService.expectResultsFromQuery(query, testUser, testFile.getName());
}
@Test
public void testChangeCategoryPathUpdatesPath()
{
Step.STEP("Create two nested categories and assign a file to the child.");
RestCategoryModel parentCategory = helper.createCategory();
RestCategoryModel childCategory = helper.createCategory(parentCategory);
FileModel testFile = helper.createFileInSite(testUser, testSite);
helper.linkToCategory(testUser, testFile, parentCategory, childCategory);
Step.STEP("Update the parent category name and check the file's paths are updated.");
String newCategoryName = parentCategory.getName() + "_updated";
restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI()
.usingCategory(parentCategory)
.updateCategory(RestCategoryModel.builder().name(newCategoryName).create());
Step.STEP("Check there is a path with the updated category name.");
SearchRequest query = req("PATH:\"" + categoryPath(newCategoryName, childCategory.getName(), testFile.getName()) + "\"");
searchQueryService.expectResultsFromQuery(query, testUser, testFile.getName());
}
/**
* Rename the specified node to have "_updated" on the end.
*
* @param node
* The node to update.
* @return The updated node.
*/
private RestNodeModel renameNode(ContentModel node)
{
String newName = node.getName() + "_updated";
JsonObject renameJson = Json.createObjectBuilder().add("properties",
Json.createObjectBuilder().add("cm:name", newName)).build();
return restClient.authenticateUser(testUser).withCoreAPI().usingNode(node).updateNode(renameJson.toString());
}
/**
* Move the specified node to a folder.
*
* @param node
* The node to move.
* @param targetFolder
* The folder to move the node to.
* @return The updated node.
*/
private RestNodeModel moveNode(ContentModel node, FolderModel targetFolder)
{
RestNodeBodyMoveCopyModel moveBody = new RestNodeBodyMoveCopyModel();
moveBody.setTargetParentId(targetFolder.getNodeRef());
return restClient.authenticateUser(testUser).withCoreAPI().usingNode(node).move(moveBody);
}
/**
* Create a path to a file or folder in a site.
*
* @param site
* The site object.
* @param documentLibraryNames
* The list of names of nodes from the document library to the target file or folder.
* @return An absolute path suitable for use in a path query.
*/
private String pathInSite(SiteModel site, String... documentLibraryNames)
{
return "/app:company_home/st:sites/cm:" + site.getId() + "/cm:documentLibrary/cm:" + stream(documentLibraryNames).collect(joining("/cm:"));
}
/**
* Create a path to a file or folder via the category hierarchy.
*
* @param nodeNames
* The ordered list of node names from the root category to the node that was categorised.
* @return An absolute path through the categories to the specified node.
*/
private String categoryPath(String... nodeNames)
{
return "/cm:categoryRoot/cm:generalclassifiable/cm:" + Arrays.stream(nodeNames).collect(joining("/cm:"));
}
}
@@ -1,286 +0,0 @@
/*
* #%L
* Alfresco Tas Elasticsearch
* %%
* Copyright (C) 2026 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.elasticsearch;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import com.google.common.collect.Sets;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.alfresco.rest.core.RestWrapper;
import org.alfresco.rest.search.RestRequestQueryModel;
import org.alfresco.rest.search.RestRequestTemplatesModel;
import org.alfresco.rest.search.SearchNodeModel;
import org.alfresco.rest.search.SearchRequest;
import org.alfresco.rest.search.SearchResponse;
import org.alfresco.utility.Utility;
import org.alfresco.utility.model.UserModel;
/** A class providing methods for testing search queries. */
public class SearchQueryService
{
/** Maximum time to allow for search query to return correct results. */
private static final int MAX_TIME = 10000;
@Autowired
private RestWrapper client;
/** Assert that the query returns no results. */
public void expectNoResultsFromQuery(SearchRequest searchRequest, UserModel testUser)
{
expectResultsFromQuery(searchRequest, testUser);
}
/** Assert that the query returns something, without checking exactly what it returns. */
public void expectSomeResultsFromQuery(SearchRequest searchRequest, UserModel testUser)
{
Consumer<SearchResponse> assertNotEmpty = searchResponse -> assertFalse(searchResponse.isEmpty());
expectResultsFromQuery(searchRequest, testUser, assertNotEmpty);
}
public void expectResultsInOrder(SearchRequest searchRequest, UserModel user, boolean isAscending, String... expected)
{
Consumer<SearchResponse> response = searchResponse -> assertNamesInOrder(searchResponse, isAscending, expected);
expectResultsFromQuery(searchRequest, user, response);
}
public void expectResultsInOrder(SearchRequest searchRequest, UserModel user, String... expectedOrder)
{
Consumer<SearchResponse> response = searchResponse -> assertNamesInOrder(searchResponse, expectedOrder);
expectResultsFromQuery(searchRequest, user, response);
}
public void expectResultsStartingWithOneOf(SearchRequest searchRequest, UserModel user, String... expected)
{
Consumer<SearchResponse> response = searchResponse -> {
List<String> expectedFirstElements = List.of(expected);
String actualFirstElement = searchResponse.getEntries().stream()
.map(SearchNodeModel::getModel)
.map(SearchNodeModel::getName)
.findFirst()
.orElse(null);
assertTrue(expectedFirstElements.contains(actualFirstElement), "Unexpected search results - actual first element: " + actualFirstElement + ", expected one of: " + expectedFirstElements + " |");
};
expectResultsFromQuery(searchRequest, user, response);
}
public void expectResultsFromQuery(SearchRequest searchRequest, UserModel user, String... expected)
{
Consumer<SearchResponse> assertNames = searchResponse -> assertNames(searchResponse, expected);
expectResultsFromQuery(searchRequest, user, assertNames);
}
/** Check that the specified results are all included in the result set. */
public void expectResultsInclude(SearchRequest searchRequest, UserModel user, String... expected)
{
Consumer<SearchResponse> assertNames = searchResponse -> assertNamesInclude(searchResponse, expected);
expectResultsFromQuery(searchRequest, user, assertNames);
}
public void expectNodeTypesFromQuery(SearchRequest searchRequest, UserModel user, String... expected)
{
Consumer<SearchResponse> assertNodeTypes = searchResponse -> assertNodeTypes(searchResponse, expected);
expectResultsFromQuery(searchRequest, user, assertNodeTypes);
}
public void expectNodeRefsFromQuery(SearchRequest searchRequest, UserModel user, String... expectedNodeRefs)
{
Consumer<SearchResponse> assertNames = searchResponse -> assertNodeRefs(searchResponse, expectedNodeRefs);
expectResultsFromQuery(searchRequest, user, assertNames);
}
public void expectAllResultsFromQuery(SearchRequest searchRequest, UserModel user, Predicate<SearchNodeModel> assertionMethod)
{
Function<SearchNodeModel, String> failureMessage = searchNodeModel -> "'" + searchNodeModel.getName() + "' did not satisfy predicate.";
expectAllResultsFromQuery(searchRequest, user, assertionMethod, failureMessage);
}
public void expectAllResultsFromQuery(SearchRequest searchRequest, UserModel user, Predicate<SearchNodeModel> assertionMethod, Function<SearchNodeModel, String> failureMessageFunction)
{
expectResultsFromQuery(searchRequest, user, searchResponse -> assertAllSearchResults(searchResponse, assertionMethod, failureMessageFunction));
}
public void expectTotalHitsFromQuery(SearchRequest searchRequest, UserModel user, int expected)
{
expectResultsFromQuery(searchRequest, user, searchResponse -> assertTotalHitsResults(searchResponse, expected));
}
private void expectResultsFromQuery(SearchRequest searchRequest, UserModel user, Consumer<SearchResponse> assertionMethod)
{
try
{
Utility.sleep(1000, MAX_TIME, () -> {
SearchResponse response = client.authenticateUser(user)
.withSearchAPI()
.search(searchRequest);
client.assertStatusCodeIs(HttpStatus.OK);
assertionMethod.accept(response);
});
}
catch (InterruptedException e)
{
fail("InterruptedException received while waiting for results.");
}
}
public void expectErrorFromQuery(SearchRequest searchRequest, org.alfresco.utility.model.UserModel user,
HttpStatus expectedStatusCode, String containsErrorString)
{
client.authenticateUser(user).withSearchAPI().search(searchRequest);
client.assertStatusCodeIs(expectedStatusCode);
client.assertLastError().containsSummary(containsErrorString);
}
private void assertNodeRefs(SearchResponse actual, String... expected)
{
Set<String> result = actual.getEntries().stream()
.map(SearchNodeModel::getModel)
.map(SearchNodeModel::getId)
.collect(Collectors.toSet());
Set<String> expectedList = Sets.newHashSet(expected);
assertEquals(result, expectedList, "Unexpected search results - got " + result + " expected " + expectedList);
}
private void assertNames(SearchResponse actual, String... expected)
{
Set<String> result = actual.getEntries().stream()
.map(SearchNodeModel::getModel)
.map(SearchNodeModel::getName)
.collect(Collectors.toSet());
Set<String> expectedList = Sets.newHashSet(expected);
assertEquals(result, expectedList, "Unexpected search results - got " + result + " expected " + expectedList);
}
/** Check that the given names are included in the result set. */
private void assertNamesInclude(SearchResponse actual, String... expected)
{
Set<String> expectedSet = Sets.newHashSet(expected);
// Filter to only include results that were expected.
Set<String> filteredResults = actual.getEntries().stream()
.map(SearchNodeModel::getModel)
.map(SearchNodeModel::getName)
.filter(name -> expectedSet.contains(name))
.collect(Collectors.toSet());
assertEquals(filteredResults, expectedSet, "Did not receive all the expected search results - got " + filteredResults + " from expected set " + expectedSet);
}
private List<String> orderNames(boolean isAscending, String... filename)
{
List<String> orderedNames = Arrays.stream(filename).sorted().collect(Collectors.toList());
if (!isAscending)
{
orderedNames = orderedNames.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());
}
return orderedNames;
}
private void assertNamesInOrder(SearchResponse actual, boolean isAscending, String... expected)
{
List<String> expectedInOrder = orderNames(isAscending, expected);
List<String> result = actual.getEntries().stream()
.map(SearchNodeModel::getModel)
.map(SearchNodeModel::getName)
.collect(Collectors.toList());
assertEquals(result, expectedInOrder, "Unexpected search results - got " + result + " expected " + expectedInOrder);
}
private void assertNamesInOrder(SearchResponse actual, String... expectedOrder)
{
List<String> expectedInOrder = List.of(expectedOrder);
List<String> result = actual.getEntries().stream()
.map(SearchNodeModel::getModel)
.map(SearchNodeModel::getName)
.toList();
assertEquals(result, expectedInOrder, "Unexpected search results - got " + result + " expected " + expectedInOrder);
}
private void assertNodeTypes(SearchResponse actual, String... expected)
{
Set<String> result = actual.getEntries().stream()
.map(SearchNodeModel::getModel)
.map(SearchNodeModel::getNodeType)
.collect(Collectors.toSet());
Set<String> expectedList = Sets.newHashSet(expected);
assertEquals(result, expectedList, "Unexpected search results - got " + result + " expected " + expectedList);
}
private void assertAllSearchResults(SearchResponse actual, Predicate<SearchNodeModel> assertion, Function<SearchNodeModel, String> failureMessageFunction)
{
String result = actual.getEntries().stream()
.map(SearchNodeModel::getModel)
.filter(Predicate.not(assertion))
.map(failureMessageFunction)
.collect(Collectors.joining("\n"));
assertTrue(result.isEmpty(), "assertAllSearchResults failed with these issues:\n" + result);
}
private void assertTotalHitsResults(SearchResponse actual, int expected)
{
int totalItems = actual.getPagination().getTotalItems();
assertEquals(totalItems, expected, "Unexpected totalItems results - got " + totalItems + " expected " + expected);
}
public static SearchRequest req(String query)
{
return req(null, query);
}
public static SearchRequest req(String language, String query)
{
RestRequestQueryModel restRequestQueryModel = new RestRequestQueryModel();
restRequestQueryModel.setQuery(query);
Optional.ofNullable(language).ifPresent(restRequestQueryModel::setLanguage);
return new SearchRequest(restRequestQueryModel);
}
public static SearchRequest req(String language, String query, Map<String, String> templates)
{
RestRequestQueryModel restRequestQueryModel = new RestRequestQueryModel();
restRequestQueryModel.setQuery(query);
Optional.ofNullable(language).ifPresent(restRequestQueryModel::setLanguage);
SearchRequest request = new SearchRequest(restRequestQueryModel);
List<RestRequestTemplatesModel> templatesModels = templates.entrySet().stream()
.map(entry -> RestRequestTemplatesModel.builder().name(entry.getKey()).template(entry.getValue()).create())
.toList();
request.setTemplates(templatesModels);
return request;
}
}
@@ -1,178 +0,0 @@
/*
* #%L
* Alfresco Tas Elasticsearch
* %%
* Copyright (C) 2026 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.elasticsearch.basicAuth;
import java.io.IOException;
import org.testcontainers.containers.GenericContainer;
import org.alfresco.tas.AlfrescoStackInitializer;
import org.alfresco.tas.SearchEngineType;
/**
* ACS Stack Docker Compose initializer with Basic Authentication for Search Engine service (Opensearch or Elasticsearch).
*/
public class AlfrescoStackInitializerESBasicAuth extends AlfrescoStackInitializer
{
// Default Elasticsearch credentials
private static final String SEARCH_ENGINE_USERNAME = "elastic";
private static final String SEARCH_ENGINE_PASSWORD = "bob123"; // pragma: allowlist secret
private static final String OPENSEARCH_TEST_ROLE = "test_role";
@Override
public void configureSecuritySettings(GenericContainer searchEngineContainer)
{
SearchEngineType usedEngine = getImagesConfig().getSearchEngineType();
if (SearchEngineType.OPENSEARCH_ENGINE.equals(usedEngine))
{
configureNewUser(searchEngineContainer);
}
}
private void configureNewUser(GenericContainer opensearchContainer)
{
try
{
// Using password hash for setting up test only
String passwordHash = hashPassword(opensearchContainer, SEARCH_ENGINE_PASSWORD);
addNewUser(opensearchContainer, SEARCH_ENGINE_USERNAME, passwordHash);
addNewRole(opensearchContainer, OPENSEARCH_TEST_ROLE, CUSTOM_ALFRESCO_INDEX);
addNewRoleMapping(opensearchContainer, OPENSEARCH_TEST_ROLE, SEARCH_ENGINE_USERNAME);
applyNewSecurityConfigs(opensearchContainer);
}
catch (IOException | InterruptedException e)
{
e.printStackTrace();
}
}
private String hashPassword(GenericContainer opensearchContainer, String password) throws IOException, InterruptedException
{
return opensearchContainer.execInContainer("/usr/share/opensearch/plugins/opensearch-security/tools/hash.sh", "-p", password)
.getStdout().strip();
}
private void applyNewSecurityConfigs(GenericContainer opensearchContainer) throws IOException, InterruptedException
{
opensearchContainer.execInContainer("sh", "-c", "/usr/share/opensearch/plugins/opensearch-security/tools/securityadmin.sh " +
"-cd /usr/share/opensearch/config/opensearch-security " +
"-icl -nhnv " +
"-cert /usr/share/opensearch/config/kirk.pem " +
"-cacert /usr/share/opensearch/config/root-ca.pem " +
"-key /usr/share/opensearch/config/kirk-key.pem");
}
private void addNewRoleMapping(GenericContainer opensearchContainer, String role, String username) throws IOException, InterruptedException
{
opensearchContainer.execInContainer(
"sh", "-c", "echo '\n\n" + newOpensearchRoleMapping(role, username) + "' >> /usr/share/opensearch/config/opensearch-security/roles_mapping.yml");
}
private void addNewRole(GenericContainer opensearchContainer, String role, String index) throws IOException, InterruptedException
{
opensearchContainer.execInContainer(
"sh", "-c", "echo '\n\n" + newOpensearchRule(role, index) + "' >> /usr/share/opensearch/config/opensearch-security/roles.yml");
}
private void addNewUser(GenericContainer opensearchContainer, String username, String passwordHash) throws IOException, InterruptedException
{
opensearchContainer.execInContainer(
"sh", "-c", "echo '\n\n" + newOpensearchUser(username, passwordHash) + "' >> /usr/share/opensearch/config/opensearch-security/internal_users.yml");
}
private String newOpensearchUser(String username, String passwordHash)
{
return username + ":\n" +
" hash: \"" + passwordHash + "\"\n" +
" reserved: false\n" +
" backend_roles:\n" +
" - \"all_access\"\n" +
" description: \"New user for testing purposes\"";
}
private String newOpensearchRule(String role, String indexName)
{
return role + ":\n" +
" cluster_permissions:\n" +
" - cluster_all\n" +
" index_permissions:\n" +
" - index_patterns:\n" +
" - \"" + indexName + "\"\n" +
" - \"alfresco-reindex-state\"\n" +
" allowed_actions:\n" +
" - \"*\"\n";
}
private String newOpensearchRoleMapping(String role, String user)
{
return role + ":\n" +
" reserved: true\n" +
" users:\n" +
" - \"" + user + "\"";
}
@Override
protected GenericContainer createBatchIndexingContainer()
{
GenericContainer container = super.createBatchIndexingContainer();
container.withEnv("SPRING_ELASTICSEARCH_REST_USERNAME", SEARCH_ENGINE_USERNAME);
container.withEnv("SPRING_ELASTICSEARCH_REST_PASSWORD", SEARCH_ENGINE_PASSWORD);
return container;
}
@Override
protected GenericContainer createSearchEngineContainer()
{
SearchEngineType usedEngine = getImagesConfig().getSearchEngineType();
if (SearchEngineType.OPENSEARCH_ENGINE.equals(usedEngine))
{
return super.createOpensearchContainer()
.withEnv("plugins.security.disabled", "false")
.withEnv("plugins.security.ssl.http.enabled", "false");
}
else
{
return super.createElasticContainer()
.withEnv("xpack.security.enabled", "true")
.withEnv("ELASTIC_PASSWORD", SEARCH_ENGINE_PASSWORD);
}
}
@Override
protected GenericContainer createAlfrescoContainer()
{
GenericContainer container = super.createAlfrescoContainer();
String javaOpts = (String) container.getEnvMap().get("JAVA_OPTS");
javaOpts = javaOpts + " -Delasticsearch.user=" + SEARCH_ENGINE_USERNAME + " " +
"-Delasticsearch.password=" + SEARCH_ENGINE_PASSWORD;
container.getEnvMap().put("JAVA_OPTS", javaOpts);
return container;
}
}
@@ -1,91 +0,0 @@
/*
* #%L
* Alfresco Tas Elasticsearch
* %%
* Copyright (C) 2026 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.elasticsearch.basicAuth;
import static org.alfresco.elasticsearch.SearchQueryService.req;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.alfresco.elasticsearch.SearchQueryService;
import org.alfresco.utility.data.DataContent;
import org.alfresco.utility.data.DataSite;
import org.alfresco.utility.data.DataUser;
import org.alfresco.utility.model.FileModel;
import org.alfresco.utility.model.FileType;
import org.alfresco.utility.model.SiteModel;
import org.alfresco.utility.model.TestGroup;
import org.alfresco.utility.model.UserModel;
import org.alfresco.utility.network.ServerHealth;
/**
* Basic test for Elasticsearch server with Basic Authentication. The aim of this class is to test that Basic Authentication is working as expected.
*/
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
initializers = AlfrescoStackInitializerESBasicAuth.class)
public class ElasticsearchBasicAuthTests extends AbstractTestNGSpringContextTests
{
private static final String FILE_0_NAME = "test.txt";
@Autowired
private ServerHealth serverHealth;
@Autowired
private DataUser dataUser;
@Autowired
private DataContent dataContent;
@Autowired
private DataSite dataSite;
@Autowired
private SearchQueryService searchQueryService;
private UserModel userSite1;
private SiteModel siteModel1;
@BeforeClass(alwaysRun = true)
public void dataPreparation()
{
serverHealth.assertServerIsOnline();
userSite1 = dataUser.createRandomTestUser();
siteModel1 = dataSite.usingUser(userSite1).createPrivateRandomSite();
createContent(FILE_0_NAME, "This is the first test", siteModel1, userSite1);
}
private FileModel createContent(String filename, String content, SiteModel site, UserModel user)
{
FileModel fileModel = new FileModel(filename, FileType.TEXT_PLAIN, content);
return dataContent.usingUser(user).usingSite(site)
.createContent(fileModel);
}
@Test(groups = TestGroup.SEARCH)
public void searchCanFindAFile()
{
searchQueryService.expectResultsFromQuery(req("first AND SITE:\"" + siteModel1.getId() + "\""), userSite1, FILE_0_NAME);
}
}
@@ -1,63 +0,0 @@
/*
* #%L
* Alfresco Tas Elasticsearch
* %%
* Copyright (C) 2026 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.elasticsearch.retry;
import java.util.ConcurrentModificationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.IRetryAnalyzer;
import org.testng.ITestResult;
public class RetryAnalyzer implements IRetryAnalyzer
{
private static final Logger LOGGER = LoggerFactory.getLogger(RetryAnalyzer.class);
private static final int RETRY_LIMIT = 3;
private int retryNumber = 0;
@Override
public boolean retry(ITestResult testResult)
{
retryNumber++;
Throwable throwable = testResult.getThrowable();
if (retryNumber == RETRY_LIMIT)
{
LOGGER.info("Retry limit reached: {}, shouldRetry: {}", retryNumber, false, throwable);
return false;
}
else
{
boolean shouldRetry = throwable != null
&& (throwable instanceof IllegalStateException && throwable.getMessage() != null
&& throwable.getMessage().contains("connection still allocated"))
|| (throwable instanceof ConcurrentModificationException)
|| (throwable instanceof AssertionError && throwable.getMessage() != null
&& throwable.getMessage().contains("Maximum retry period reached"));
LOGGER.info("Retry: {}, shouldRetry: {}", retryNumber, shouldRetry, throwable);
return shouldRetry;
}
}
}
@@ -1,44 +0,0 @@
/*
* #%L
* Alfresco Tas Elasticsearch
* %%
* Copyright (C) 2026 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.elasticsearch.retry;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import org.testng.IAnnotationTransformer;
import org.testng.annotations.ITestAnnotation;
/**
* Add the {@link RetryAnalyzer} to each test.
*/
public class RetryAnnotationTransformer implements IAnnotationTransformer
{
@Override
public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod)
{
annotation.setRetryAnalyzer(RetryAnalyzer.class);
}
}
@@ -1,166 +0,0 @@
/*
* #%L
* Alfresco Tas Elasticsearch
* %%
* Copyright (C) 2026 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.elasticsearch.utility;
import static org.alfresco.utility.model.FileType.TEXT_PLAIN;
import org.springframework.beans.factory.annotation.Autowired;
import org.alfresco.rest.core.RestWrapper;
import org.alfresco.rest.model.RestCategoryLinkBodyModel;
import org.alfresco.rest.model.RestCategoryModel;
import org.alfresco.utility.data.DataContent;
import org.alfresco.utility.data.DataSite;
import org.alfresco.utility.data.DataUser;
import org.alfresco.utility.data.RandomData;
import org.alfresco.utility.model.ContentModel;
import org.alfresco.utility.model.FileModel;
import org.alfresco.utility.model.FolderModel;
import org.alfresco.utility.model.SiteModel;
import org.alfresco.utility.model.UserModel;
/** Helper methods for Elasticsearch E2E tests. */
public class ElasticsearchRESTHelper
{
/** The alias for the root of the category hierarchy. */
public static final String ROOT_CATEGORY_ALIAS = "-root-";
/** The root of the category hierarchy. */
private static final RestCategoryModel ROOT_CATEGORY = RestCategoryModel.builder().id(ROOT_CATEGORY_ALIAS).name(ROOT_CATEGORY_ALIAS).create();
@Autowired
private RestWrapper client;
@Autowired
private DataSite dataSite;
@Autowired
private DataContent dataContent;
@Autowired
private DataUser dataUser;
/**
* Create a private site.
*
* @param user
* The user to use.
* @return The new site.
*/
public SiteModel createPrivateSite(UserModel user)
{
return dataSite.usingUser(user).createPrivateRandomSite();
}
/**
* Create a folder in a site.
*
* @param user
* The user to use.
* @param site
* The site to create the folder in.
* @return The new folder.
*/
public FolderModel createFolderInSite(UserModel user, SiteModel site)
{
return dataContent.usingUser(user).usingSite(site).createFolder();
}
/**
* Create a text file in a site.
*
* @param user
* The user to use.
* @param site
* The site to create the file in.
* @return The new file.
*/
public FileModel createFileInSite(UserModel user, SiteModel site)
{
FileModel fileModel = FileModel.getRandomFileModel(TEXT_PLAIN);
return dataContent.usingUser(user).usingSite(site).createContent(fileModel);
}
/**
* Create a file in a folder.
*
* @param user
* The user to use.
* @param folder
* The folder to create the file in.
* @return The new file.
*/
public FileModel createFileInFolder(UserModel user, FolderModel folder)
{
FileModel fileModel = FileModel.getRandomFileModel(TEXT_PLAIN);
return dataContent.usingUser(user).usingResource(folder).createContent(fileModel);
}
/**
* Create a category.
*
* @param ancestorCategories
* The path of categories between the root and the new category, or leave blank to create the category at the "-root-".
* @return The newly created category.
*/
public RestCategoryModel createCategory(RestCategoryModel... ancestorCategories)
{
RestCategoryModel parent = (ancestorCategories.length > 0 ? ancestorCategories[ancestorCategories.length - 1] : ROOT_CATEGORY);
return client.authenticateUser(dataUser.getAdminUser()).withCoreAPI()
.usingCategory(parent)
.createSingleCategory(RestCategoryModel.builder().name(RandomData.getRandomAlphanumeric()).create());
}
/**
* Link a file or folder to a category.
*
* @param user
* The user who should create the link.
* @param node
* The file or folder to be linked.
* @param categoryHierarchy
* The full list of categories from the root (excluding "-root-") to the category to use.
* @return The category that was linked to.
*/
public RestCategoryModel linkToCategory(UserModel user, ContentModel node, RestCategoryModel... categoryHierarchy)
{
RestCategoryModel linkedToCategory = (categoryHierarchy.length > 0 ? categoryHierarchy[categoryHierarchy.length - 1] : ROOT_CATEGORY);
return client.authenticateUser(user).withCoreAPI().usingNode(node)
.linkToCategory(RestCategoryLinkBodyModel.builder().categoryId(linkedToCategory.getId()).create());
}
/**
* Unlink a node from a category.
*
* @param user
* The user who should remove the link.
* @param node
* The node to unlink.
* @param categoryHierarchy
* The full list of categories from the root (excluding "-root-") to the category to use.
*/
public void unlinkFromCategory(UserModel user, ContentModel node, RestCategoryModel... categoryHierarchy)
{
RestCategoryModel linkedToCategory = (categoryHierarchy.length > 0 ? categoryHierarchy[categoryHierarchy.length - 1] : ROOT_CATEGORY);
client.authenticateUser(user).withCoreAPI().usingNode(node).unlinkFromCategory(linkedToCategory.getId());
}
}
@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<context:annotation-config />
<context:component-scan base-package="org.alfresco" />
<import resource="classpath:dataprep-context.xml" />
<bean id="elasticsearchRESTHelper" class="org.alfresco.elasticsearch.utility.ElasticsearchRESTHelper" />
<bean id="searchQueryService" class="org.alfresco.elasticsearch.SearchQueryService" />
</beans>
@@ -1,5 +0,0 @@
# Exact Term search is switched off by default as it introduces index size overhead.
#to enable it, please uncomment the following on a datatype or property name basis
alfresco.cross.locale.datatype.0={http://www.alfresco.org/model/dictionary/1.0}content
alfresco.cross.locale.property.0={http://www.alfresco.org/model/content/1.0}content
alfresco.cross.locale.property.1={http://www.alfresco.org/model/content/1.0}name
@@ -1,16 +0,0 @@
# Root logger option
rootLogger.level=info
rootLogger.appenderRef.stdout.ref=ConsoleAppender
###### Console appender definition #######
appender.console.type=Console
appender.console.name=ConsoleAppender
appender.console.layout.type=PatternLayout
appender.console.layout.pattern=[%t] %d{HH:mm:ss} %-5p %c{1}:%L - %replace{%m}{[\r\n]+}{}%n
logger.alfresco-elasticsearch.name=org.alfresco.elasticsearch
logger.alfresco-elasticsearch.level=info
logger.alfresco-rest-core.name=org.alfresco.rest.core
logger.alfresco-rest-core.level=warn
logger.alfresco-utility-report-log.name=org.alfresco.utility.report.log
logger.alfresco-utility-report-log.level=info
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

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